def encrypt_string(pt, key=None): """Encrypt the plaintext (pt) with the 16-bytes key. Moreover, it encrypts it using a random IV, so that encrypting repeatedly the same string gives different outputs. This way no analisys can made when the same number is used in different contexts. The generated string uses the alphabet { 'a', ..., 'z', 'A', ..., 'Z', '0', ..., '9', '.', '-', '_' }, so it is safe to use in URLs. If key is not specified, it is obtained from the configuration. """ if key is None: key = _get_secret_key_unhex() # Pad the plaintext to make its length become a multiple of the block size # (that is, for AES, 16 bytes), using a byte 0x01 followed by as many bytes # 0x00 as needed. If the length of the message is already a multiple of 16 # bytes, add a new block. pt_pad = bytes(pt) + b'\01' + b'\00' * (16 - (len(pt) + 1) % 16) # The IV is a random block used to differentiate messages encrypted with # the same key. An IV should never be used more than once in the lifetime # of the key. In this way encrypting the same plaintext twice will produce # different ciphertexts. iv = get_random_key() # Initialize the AES cipher with the given key and IV. aes = AES.new(key, AES.MODE_CBC, iv) ct = aes.encrypt(pt_pad) # Convert the ciphertext in a URL-safe base64 encoding ct_b64 = bin_to_b64(iv + ct)\ .replace('+', '-').replace('/', '_').replace('=', '.') return ct_b64
def encrypt_binary(pt, key_hex): """Encrypt the plaintext with the 16-bytes key. A random salt is added to avoid having the same input being encrypted to the same output. pt (bytes): the "plaintext" to encode. key_hex (str): a 16-bytes key in hex (a string of 32 hex chars). return (str): pt encrypted using the key, in a format URL-safe (more precisely, base64-encoded with alphabet "a-zA-Z0-9.-_"). """ key = hex_to_bin(key_hex) # Pad the plaintext to make its length become a multiple of the block size # (that is, for AES, 16 bytes), using a byte 0x01 followed by as many bytes # 0x00 as needed. If the length of the message is already a multiple of 16 # bytes, add a new block. pt_pad = pt + b'\01' + b'\00' * (16 - (len(pt) + 1) % 16) # The IV is a random block used to differentiate messages encrypted with # the same key. An IV should never be used more than once in the lifetime # of the key. In this way encrypting the same plaintext twice will produce # different ciphertexts. iv = get_random_key() # Initialize the AES cipher with the given key and IV. aes = AES.new(key, AES.MODE_CBC, iv) ct = aes.encrypt(pt_pad) # Convert the ciphertext in a URL-safe base64 encoding ct_b64 = bin_to_b64(iv + ct)\ .replace('+', '-').replace('/', '_').replace('=', '.') return ct_b64
def test_string(self): with self.assertRaises(TypeError): bin_to_b64("cms")
def test_success(self): self.assertEqual(bin_to_b64(b"\x32\x00\xa0"), "MgCg") self.assertEqual(bin_to_b64(b"\xFF\xFF\xFF\xFF"), "/////w==") self.assertEqual(bin_to_b64(b"\x00" * 3000), "A" * (3000 * 4 // 3))
def test_decrypt_invalid_encrypted(self): # "stuff" is not decryptable. encrypted = bin_to_b64(b"stuff") with self.assertRaises(ValueError): decrypt_binary(encrypted, self.key)