Шифрование текста в PHP и дешифрование в Python - PullRequest
0 голосов
/ 14 января 2019

Я использую следующий фрагмент кода для шифрования текста в PHP7:

$plaintext = "message to be encrypted";
$cipher = "aes-256-cbc";
$ivlen = openssl_cipher_iv_length($cipher);
$iv = "0123456789012345";
$key = "akshayakshayaksh";
$ciphertext = openssl_encrypt($plaintext, $cipher, $key, $options=0, $iv);
print $ciphertext;

Вывод: cUXDhOEGz19QEo9XDvMzXkGFmg / YQUnXEqKVpfYtUGo =

Теперь, когда я пытаюсь расшифровать это в Python3, выдает ошибку:

from Crypto.Cipher import AES
obj2 = AES.new('akshayakshayaksh', AES.MODE_CBC, '0123456789012345')
ciphertext = "cUXDhOEGz19QEo9XDvMzXkGFmg/YQUnXEqKVpfYtUGo="
obj2.decrypt(ciphertext)

Traceback (последний последний вызов):
Файл "", строка 1, в
файл "/Anaconda3/lib/python3.6/site-packages/Crypto/Cipher/blockalgo.py", строка 295, в расшифровке return self._cipher.decrypt (ciphertext) ValueError: Длина входных строк должна быть кратна 16

Я понял, что AES - это алгоритм блочного шифрования. Однако, как мне исправить мой PHP-код, чтобы он генерировал «дополненный» шифр, какие-либо подсказки?

1 Ответ

0 голосов
/ 14 января 2019

Основная проблема заключается в том, что вы используете другой размер ключа. PHP openssl_encrypt определяет размер ключа из строки алгоритма шифрования ("aes-256-cbc" в данном случае), поэтому он ожидает 256-битный ключ. Если ключ короче, он дополняется нулевыми байтами, поэтому фактический ключ, используемый openssl_encrypt, равен:

"akshayakshayaksh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"

Pycryptodome определяет размер ключа по фактическому размеру ключа, поэтому ваш код Python использует AES-128-CBC. Кроме того, как упомянуто в комментариях от kelalaka, зашифрованный текст кодируется как base64 (openssl_encrypt base64 кодирует зашифрованный текст по умолчанию - мы можем получить необработанные байты, если мы используем OPENSSL_RAW_DATA в $options). Pycryptodome не декодирует зашифрованный текст, поэтому мы должны использовать b64decode().

key = b'akshayakshayaksh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
obj2 = AES.new(key, AES.MODE_CBC, b'0123456789012345')
ciphertext = b"cUXDhOEGz19QEo9XDvMzXkGFmg/YQUnXEqKVpfYtUGo="
print(obj2.decrypt(b64decode(ciphertext)))
#b'message to be encrypted\t\t\t\t\t\t\t\t\t'

Дополнительные \t символы в конце - это заполнение - CBC требует заполнения. Pycryptodome не удаляет заполнение автоматически, но предоставляет функции заполнения в Crypto.Util.Padding.

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from base64 import b64decode

key = b'akshayakshayaksh\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0'
obj2 = AES.new(key, AES.MODE_CBC, b'0123456789012345')
ciphertext = b"cUXDhOEGz19QEo9XDvMzXkGFmg/YQUnXEqKVpfYtUGo="
plaintext = obj2.decrypt(b64decode(ciphertext))
plaintext = unpad(plaintext, AES.block_size)

Хотя PHP openssl принимает ключи произвольного размера, лучше всего использовать размер ключа, указанный в строке алгоритма, чтобы, по крайней мере, избежать путаницы. Также ключевые байты должны быть как можно более случайными.

Как отметил Мартен Бодевес в комментариях , этот ключ использует ограниченный диапазон байтов и поэтому он очень слабый. Кроме того, он создается повторением слова, что делает его уязвимым для атак по словарю (которые намного быстрее, чем атаки методом перебора).

В PHP мы можем получить криптографически безопасные случайные байты с random_bytes(),

$key = random_bytes(32);  

и в Python с os.urandom()

key = os.urandom(32)

(Вы можете использовать те же функции для создания IV; вы не должны использовать статический IV, IV должен быть непредсказуемым)

Вы также можете получить ключ из вашего пароля с помощью KDF. В этом случае важно использовать случайную соль и достаточно большое количество итераций. PHP предоставляет алгоритм PBKDF2 с функцией hash_pbkdf2, а Python с hashlib.pbkdf2_hmac.

...