You are defining a 64-character key; that those 64 characters are hex digits is neither here nor there, the openssl_encrypt()
will not decode the hex characters in any way, it uses those characters verbatim.
But, AES-256 only takes keys with 32 bytes (== 256 bits), not 64, and openssl_encrypt()
silently truncates the key. The PyCrypto AES.new()
method on the other hand, explicitly tells you that the key is too long, alerting you to your error here, which is that you should probably decode your hex key to bytes first.
You can successfully decrypt messages if you cut the key down to 32 characters in Python, or if you convert your key from hex to bytes in both cases:
$k = hex2bin('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6');
key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')
I strongly recommend decoding over truncating; 32 hex characters have a lot less entropy (the bytes cover as many possible values as the number of values encodable in 32 hex characters squared, 2 to the power 256 vs. 2 to the power 128).
Because openssl_encrypt()
also base64 encodes the return value you need to base64-decode the cipher
value on the Python side:
>>> data = json.loads(base64.b64decode(info))
>>> data
{'cipher': 'Iu9VgH8DdxHdQgnq8o23ew==', 'i': 'Vz+wy5VS6toNHx7MEYl+/A=='}
# base64: ^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
Finally, openssl_encrypt()
adds PKCS#7 padding to the encrypted message to make it fit the AES block size (16 bytes), you need to remove that padding again on the Python side, the PyCrypto AES.decrypt()
method doesn't do this for you:
# Decode from hex to create a key 256 bits (32 bytes) long:
key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')
# or, if you don't use hex2bin in PHP, truncate to 32 characters
# key = b'61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6'[:32]
def decryptpass(info):
data = json.loads(base64.b64decode(info))
iv = base64.b64decode(data['i'])
cipher = AES.new(key, AES.MODE_CBC, iv)
padded = cipher.decrypt(base64.b64decode(data['cipher']))
# manual PKCS#7 unpadding
return padded[:-padded[-1:]].decode()
Note, however, that the PyCrypto project has not seen in a new release in 6 years now, and should not be trusted to be secure anymore. You really want to use the cryptography
project here instead:
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives import padding
from cryptography.hazmat.backends import default_backend
key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')
def decrypt_aes_256(key, iv, encrypted):
decryptor = Cipher(
algorithms.AES(key), modes.CBC(iv), default_backend()
).decryptor()
unpadder = padding.PKCS7(algorithms.AES.block_size).unpadder()
decrypted = decryptor.update(encrypted) + decryptor.finalize()
return unpadder.update(decrypted) + unpadder.finalize()
def decryptpass(info):
data = json.loads(base64.b64decode(info))
iv = base64.b64decode(data['i'])
encrypted = base64.b64decode(data['cipher'])
return decrypt_aes_256(key, iv, encrypted).decode()
Demo, first in PHP:
$ php -a
Interactive shell
php > function cryptpass($arg1) {
php { $k = hex2bin('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6');
php { $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
php { $cipher = base64_encode(openssl_encrypt($arg1,'aes-256-cbc',$k,OPENSSL_RAW_DATA,$iv));
php { return urlencode(base64_encode('{"cipher":"'.$cipher.'","i":"'.base64_encode($iv).'"}'));
php { }
php > echo cryptpass("Hello, world!");
eyJjaXBoZXIiOiJJdTlWZ0g4RGR4SGRRZ25xOG8yM2V3PT0iLCJpIjoiVnord3k1VlM2dG9OSHg3TUVZbCsvQT09In0%3D
then in Python; with the cryptography
functions defined as above:
>>> from urllib.parse import unquote
>>> info = unquote("eyJjaXBoZXIiOiJJdTlWZ0g4RGR4SGRRZ25xOG8yM2V3PT0iLCJpIjoiVnord3k1VlM2dG9OSHg3TUVZbCsvQT09In0%3D")
>>> decryptpass(info)
'Hello, world!'