def decrypt_binary(ct_b64, key_hex): """Decrypt a ciphertext generated by encrypt_binary. ct_b64 (str): the ciphertext as produced by encrypt_binary. key_hex (str): the 16-bytes key in hex format used to encrypt. return (bytes): the plaintext. raise (ValueError): if the ciphertext is invalid. """ key = hex_to_bin(key_hex) try: # Convert the ciphertext from a URL-safe base64 encoding to a # bytestring, which contains both the IV (the first 16 bytes) as well # as the encrypted padded plaintext. iv_ct = b64_to_bin( ct_b64.replace('-', '+').replace('_', '/').replace('.', '=')) aes = AES.new(key, AES.MODE_CBC, iv_ct[:16]) # Get the padded plaintext. pt_pad = aes.decrypt(iv_ct[16:]) # Remove the padding. # TODO check that the padding is correct, i.e. that it contains at most # 15 bytes 0x00 preceded by a byte 0x01. pt = pt_pad.rstrip(b'\x00')[:-1] return pt except (TypeError, binascii.Error): raise ValueError('Could not decode from base64.') except ValueError: raise ValueError('Wrong AES cryptogram length.')
def decrypt_string(ct_b64, key=None): """Decrypt a ciphertext (ct_b64) encrypted with encrypt_string and return the corresponding plaintext. If key is not specified, it is obtained from the configuration. """ if key is None: key = _get_secret_key_unhex() try: # Convert the ciphertext from a URL-safe base64 encoding to a # bytestring, which contains both the IV (the first 16 bytes) as well # as the encrypted padded plaintext. iv_ct = b64_to_bin( ct_b64.replace('-', '+').replace('_', '/').replace('.', '=')) aes = AES.new(key, AES.MODE_CBC, iv_ct[:16]) # Get the padded plaintext. pt_pad = aes.decrypt(iv_ct[16:]) # Remove the padding. # TODO check that the padding is correct, i.e. that it contains at most # 15 bytes 0x00 preceded by a byte 0x01. pt = pt_pad.rstrip(b'\x00')[:-1] return pt except TypeError: raise ValueError('Could not decode from base64.') except ValueError: raise ValueError('Wrong AES cryptogram length.')
def test_invalid_alphabet(self): # binascii ignores invalid characters self.assertEqual(b64_to_bin("M\x00g.C,g\x0a"), b"\x32\x00\xa0")
def test_invalid_length(self): with self.assertRaises(binascii.Error): b64_to_bin("MgC")
def test_success(self): self.assertEqual(b64_to_bin("MgCg"), b"\x32\x00\xa0") self.assertEqual(b64_to_bin("/////w=="), b"\xFF\xFF\xFF\xFF") self.assertEqual(b64_to_bin("A" * (3000 * 4 // 3)), b"\x00" * 3000)