Advertisement
k98kurz

xof_stream_cipher.py

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