Шифрование текста в PHP и дешифрование в Python

Я использую следующий фрагмент кода для шифрования текста в 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, в расшифровке возвращает self._cipher.decrypt(ciphertext) ValueError: Входные строки должны быть кратны 16 в длину

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

2 ответа

Решение

Основная проблема заключается в том, что вы используете другой размер ключа. РНР 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,

Я боролся с этим сегодня, когда друг указал мне на библиотеку Fernet. У него есть версии для Python и PHP, что делает это действительно простым и надежным.

Версия PHP: https://github.com/kelvinmo/fernet-php

      <?php
require 'vendor/autoload.php';

use Fernet\Fernet;

$key = '[Base64url encoded fernet key]';
$fernet = new Fernet($key);

$token = $fernet->encode('string message');

$message = $fernet->decode('fernet token');
if ($message === null) {
    echo 'Token is not valid';
}

?>

Python: https://pypi.org/project/cryptography/

      from cryptography.fernet import Fernet
# Put this somewhere safe!
key = Fernet.generate_key()
f = Fernet(key)
token = f.encrypt(b"A really secret message. Not for prying eyes.")
f.decrypt(token)
print(token)
Другие вопросы по тегам