Расшифруйте AES256 на Python против PHP - PullRequest
1 голос
/ 08 марта 2019

У меня проблемы с шифрованием AES, с использованием PHP для шифрования и Python для дешифрования.

Для шифрования я использую эту функцию PHP:

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).'"}'));
}

И я использую этот код Python для расшифровки:

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'))

Но при запуске этого кодавозникает следующая ошибка:

ValueError: ключ AES должен иметь длину 16, 24 или 32 байта

Я понимаю, что мой ключ имеет 64 байта, но как работает PHPиспользовать шифрование?Я пытался удалить последние 32 символа из ключа, но это не работает.

1 Ответ

3 голосов
/ 08 марта 2019

Вы определяете 64-символьную клавишу;что эти 64 символа являются шестнадцатеричными цифрами, ни здесь, ни там, openssl_encrypt() не будет декодировать шестнадцатеричные символы каким-либо образом, он использует эти символы дословно.

Но AES-256 принимает ключи только с 32 байт (== 256 бит), а не 64, а openssl_encrypt() без вывода сообщений усекает ключ.С другой стороны, метод PyCrypto AES.new() явно сообщает вам, что ключ слишком длинный, предупреждая вас об ошибке, которая заключается в том, что вам, вероятно, следует сначала декодировать свой шестнадцатеричный ключ в байтах.

Вы можетеуспешно расшифровать сообщения, если вы урезаете ключ до 32 символов в Python, или , если вы преобразуете ключ из шестнадцатеричного значения в байты в обоих случаях:

$k = hex2bin('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6');
key = bytes.fromhex('61b4c705859f4158d38090c1e38e8fdc4f3d29db007f012766276aa498835cf6')

Я настоятельно рекомендуюдекодирование по усечению;32 шестнадцатеричных символа имеют намного меньшую энтропию (байты охватывают столько возможных значений, сколько число значений, закодированных в 32 шестнадцатеричных символах в квадрате , 2 в степени 256 против 2 в степени 128).

Поскольку openssl_encrypt() также base64 кодирует возвращаемое значение , вам необходимо base64-декодировать значение cipher на стороне Python:

>>> data = json.loads(base64.b64decode(info))
>>> data
{'cipher': 'Iu9VgH8DdxHdQgnq8o23ew==', 'i': 'Vz+wy5VS6toNHx7MEYl+/A=='}
# base64:   ^^^^^^^^^^^^^^^^^^^^^^^^         ^^^^^^^^^^^^^^^^^^^^^^^^

Наконец, openssl_encrypt()добавляет PKCS # 7 padding к зашифрованному сообщению, чтобы оно соответствовало размеру блока AES (16 байт), вам нужно снова удалить это заполнение на стороне Python, метод PyCrypto AES.decrypt() не подходитэто для вас:

# 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()

Обратите внимание, однако, что проект PyCrypto не видел в новом выпуске в 6 лет сейчас, и не следует больше доверять его безопасности.Вы действительно хотите использовать cryptography проект здесь вместо этого:

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()

Демонстрация, сначала в 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

, затем в Python;с функциями cryptography, определенными выше:

>>> from urllib.parse import unquote
>>> info = unquote("eyJjaXBoZXIiOiJJdTlWZ0g4RGR4SGRRZ25xOG8yM2V3PT0iLCJpIjoiVnord3k1VlM2dG9OSHg3TUVZbCsvQT09In0%3D")
>>> decryptpass(info)
'Hello, world!'
...