k98kurz

xof_stream_cipher_b64.py

Jul 14th, 2021 (edited)
198
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 4.48 KB | None | 0 0
  1. """XOF Stream Cipher: encrypt and decrypt strings with shake256 - base64 version"""
  2.  
  3.  
  4. from base64 import b64encode, b64decode
  5. from hashlib import new
  6. from secrets import token_bytes
  7. from sys import argv
  8.  
  9.  
  10. def xor(b1: bytes, b2: bytes) -> bytes:
  11.     """XOR two equal-length byte strings together."""
  12.     b3 = bytearray()
  13.     for i in range(len(b1)):
  14.         b3.append(b1[i] ^ b2[i])
  15.  
  16.     return bytes(b3)
  17.  
  18.  
  19. def encrypt(key: bytes, plaintext: bytes) -> bytes:
  20.     """Stream cipher encrypt."""
  21.     key_bytes = new('shake256', key).digest(len(plaintext))
  22.  
  23.     return xor(plaintext, key_bytes)
  24.  
  25.  
  26. def decrypt(key: bytes, ciphertext: bytes) -> bytes:
  27.     """Stream cipher decrypt."""
  28.     key_bytes = new('shake256', key).digest(len(ciphertext))
  29.  
  30.     return xor(ciphertext, key_bytes)
  31.  
  32.  
  33. def hmac(key: bytes, message: bytes) -> bytes:
  34.     """Create an hmac according to rfc 2104 specifications."""
  35.     # set up variables
  36.     B, L = 136 , len(message)
  37.     L = L if L < 32 else 32
  38.     ipad_byte = 0x36.to_bytes(1, 'big')
  39.     opad_byte = 0x5c.to_bytes(1, 'big')
  40.     null_byte = 0x00.to_bytes(1, 'big')
  41.     ipad = b''.join([ipad_byte for i in range(B)])
  42.     opad = b''.join([opad_byte for i in range(B)])
  43.  
  44.     # if key length is greater than digest length, hash it first
  45.     key = key if len(key) <= L else new('shake256', key).digest(L)
  46.  
  47.     # if key length is less than block length, pad it with null bytes
  48.     key = key + b''.join(null_byte for i in range(B - len(key)))
  49.  
  50.     # compute and return the hmac
  51.     partial = new('shake256', xor(key, ipad) + message).digest(L)
  52.     return new('shake256', xor(key, opad) + partial).digest(L)
  53.  
  54.  
  55. def check_hmac(key: bytes, message: bytes, mac: bytes) -> bool:
  56.     """Check an hmac."""
  57.     # first compute the proper hmac
  58.     computed = hmac(key, message)
  59.  
  60.     # if it is the wrong length, reject
  61.     if len(mac) != len(computed):
  62.         return False
  63.  
  64.     # compute difference without revealing anything through timing attack
  65.     diff = 0
  66.     for i in range(len(mac)):
  67.         diff += mac[i] ^ computed[i]
  68.  
  69.     return diff == 0
  70.  
  71.  
  72. def seal(key: bytes, plaintext: bytes, iv_size: int = 32) -> str:
  73.     """Generate an iv, encrypt a message, and create an hmac all in one."""
  74.     iv = token_bytes(iv_size) if iv_size < len(plaintext) else token_bytes(len(plaintext))
  75.     ct = encrypt(key + iv, plaintext)
  76.     return (b64encode(iv) + b'.' + b64encode(ct) + b'.' + b64encode(hmac(key, ct))).decode()
  77.  
  78.  
  79. def unseal(key: bytes, ciphertext: str) -> bytes:
  80.     """Checks hmac, then decrypts the message."""
  81.     ciphertext = ciphertext.split('.')
  82.     iv = b64decode(ciphertext[0])
  83.     ct = b64decode(ciphertext[1])
  84.     ac = b64decode(ciphertext[2])
  85.  
  86.     if not check_hmac(key, ct, ac):
  87.         raise Exception('HMAC authentication failed')
  88.  
  89.     return decrypt(key + iv, ct)
  90.  
  91.  
  92. def main(args):
  93.     """Main function for invoking as cli tool."""
  94.     # parse arguments
  95.     mode = args[0]
  96.     key = bytes(args[1], 'utf-8')
  97.     text = args[2]
  98.  
  99.     if mode == 'encrypt' or mode == 'e':
  100.         print(b64encode(encrypt(key, bytes(text, 'utf-8'))).decode())
  101.     elif mode == 'decrypt' or mode == 'd':
  102.         print(decrypt(key, b64decode(text)).decode())
  103.     elif mode == 'seal' or mode == 's':
  104.         print(seal(key, bytes(text, 'utf-8')))
  105.     elif mode == 'open' or mode == 'o':
  106.         print(unseal(key, text).decode())
  107.     else:
  108.         print('unknown mode: ' + mode)
  109.  
  110.  
  111. if __name__ == '__main__':
  112.     if len(argv) < 4:
  113.         print('usage: ' + argv[0] + ' [encrypt|e|decrypt|d|seal|s|open|o] [key] [plaintext|ciphertext]')
  114.         exit(1)
  115.     main(argv[1:])
  116.  
  117.  
  118. def license():
  119.     """Copyleft (c) 2021 k98kurz
  120.  
  121.        Permission to use, copy, modify, and/or distribute this software
  122.        for any purpose with or without fee is hereby granted, provided
  123.        that the above copyleft notice and this permission notice appear in
  124.        all copies.
  125.  
  126.        THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
  127.        WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
  128.        WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
  129.        AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
  130.        CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  131.        OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  132.        NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  133.        CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  134.    """
  135.     return license.__doc__
  136.  
Add Comment
Please, Sign In to add comment