k98kurz

xof_stream_cipher.py

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