2019-03-08 12:04


I'm have problems with AES encryption, using PHP to encrypt and Python to decrypt.

For encrypting, I am using this PHP function:

function cryptpass($arg1) {

    $k = '61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6';
    $iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
    $cipher = base64_encode(openssl_encrypt($arg1,'aes-256-cbc',$k,OPENSSL_RAW_DATA,$iv));
    return urlencode(base64_encode('{"cipher":"'.$cipher.'","i":"'.base64_encode($iv).'"}'));

And I using this Python code to decrypt:

def decryptpass(info):
   key = '61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6'
   data = json.loads(base64.b64decode(info))
   iv = base64.b64decode(data.get('i'))
   cipher = AES.new(key,AES.MODE_CBC,iv)
   return cipher.decrypt(data.get('cipher'))

But when running this code, the following error occurs:

ValueError: AES key must be either 16, 24, or 32 bytes long

I understand that my key has 64 bytes, but how is PHP encrypt used it? I tried remove last 32 characters from the key, but that is not working.

  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 复制链接分享
  • 邀请回答


  • douhui4699 douhui4699 2年前

    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()
        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!");

    then in Python; with the cryptography functions defined as above:

    >>> from urllib.parse import unquote
    >>> info = unquote("eyJjaXBoZXIiOiJJdTlWZ0g4RGR4SGRRZ25xOG8yM2V3PT0iLCJpIjoiVnord3k1VlM2dG9OSHg3TUVZbCsvQT09In0%3D")
    >>> decryptpass(info)
    'Hello, world!'
    点赞 评论 复制链接分享