import base64 import hashlib from Crypto import Random from Crypto.Cipher import AES from Crypto.Protocol import KDF from Crypto.Util.Padding import pad, unpad ### AES-256-CBC w/ PKCS7 padding ### # 0x28, 0x7c, 0x6a, 0xa2 # 0x2e, 0xa6, 0x46, 0x4b # 0x68, 0xef, 0x91, 0xec # 0x0e, 0x8c, 0x3e, 0x50 salt_bytes = [0x28, 0x7c, 0x6a, 0xa2, 0x2e, 0xa6, 0x46, 0x4b, 0x68, 0xef, 0x91, 0xec, 0x0e, 0x8c, 0x3e, 0x50] salt = "".join(map(chr, salt_bytes)) # for debugging purposes def print_bytes(bytes): count = len(bytes) for i in range(count): byte = bytes[i] last = i == count - 1 print(hex(byte), end = "") print("\n" if last else ", ", end = "") key_size = 32 # 256 bits def encrypt_string(plain, key): # use PBKDF2 key derivation # RFC 2898: https://www.ietf.org/rfc/rfc2898.txt key = KDF.PBKDF2(key, salt, key_size) # pad text with PKCS7 # RFC 2315: https://tools.ietf.org/html/rfc2315 padded = pad(plain.encode(), AES.block_size, 'pkcs7') # generate random iv iv = Random.new().read(AES.block_size) # create cipher and encrypt padded data cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(padded) # return encoded cipher text # first 16 bytes = iv, rest is encrypted bytes return base64.b64encode(iv + encrypted) def decrypt_string(enc, key): # use PBKDF2 key derivation # RFC 2898: https://www.ietf.org/rfc/rfc2898.txt key = KDF.PBKDF2(key, salt, key_size) # decode cipher text and determine iv (first 16 bytes) enc = base64.b64decode(enc) iv = enc[:AES.block_size] # create cipher and decrypt encrypted bytes (skip first 16 bytes) cipher = AES.new(key, AES.MODE_CBC, iv) decrypted = cipher.decrypt(enc[AES.block_size:]) # unpad decrypted bytes unpadded = unpad(decrypted, AES.block_size, 'pkcs7') # convert bytes to UTF-8 encoded string and return return unpadded.decode('utf-8')