在 PHP 中加密文本并在 Python 中解密

Encrypt text in PHP and decrypt in 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;

Output: cUXDhOEGz19QEo9XDvMzXkGFmg/YQUnXEqKVpfYtUGo=

现在,当我尝试在 Python3 中解密时,出现错误:

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

Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/anaconda3/lib/python3.6/site-packages/Crypto/Cipher/blockalgo.py", line 295, in decrypt return self._cipher.decrypt(ciphertext) ValueError: Input strings must be a multiple of 16 in length

我知道 AES 是一种分组密码算法。但是,我应该如何修复我的 PHP 代码,以便它生成 "padded" 密码,有什么线索吗?

这里的主要问题是您使用了不同的密钥大小。 PHP 的 openssl_encrypt 根据加密算法字符串(在本例中为 "aes-256-cbc")确定密钥大小,因此它需要一个 256 位密钥。如果密钥较短,则用零字节填充,因此 openssl_encrypt 使用的实际密钥是:

"akshayakshayaksh[=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=][=10=]"

Pycryptodome 根据密钥的实际大小确定密钥大小,因此您的 Python 代码使用 AES-128-CBC。此外,正如 kelalaka 在 中提到的,密文是 base64 编码的(openssl_encrypt base64-默认对密文进行编码 - 如果我们在 [=20] 中使用 OPENSSL_RAW_DATA,我们可以获得原始字节=]). Pycryptodome 不解码密文,所以我们必须使用 b64decode()

key = b'akshayakshayaksh[=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=]'
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[=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=][=12=]'
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 接受任意大小的密钥,但最好使用算法字符串中指定的密钥大小,至少可以防止混淆。此外,密钥字节应尽可能随机。

正如 Maarten Bodewes 在 中指出的那样,此密钥使用的字节范围有限,因此非常脆弱。此外,它是通过重复一个词创建的,这使得它容易受到字典攻击(比暴力攻击快得多)。

在 PHP 中,我们可以使用 random_bytes()

获得加密安全的随机字节
$key = random_bytes(32);  

并在 Python 中与 os.urandom()

key = os.urandom(32)

(您可以使用相同的函数来创建 IV;您不应该使用静态 IV,IV 必须是不可预测的)

您还可以使用 KDF 从您的密码中导出密钥。在这种情况下,重要的是使用随机盐和足够多的迭代次数。 PHP 使用 hash_pbkdf2 function, and Python with hashlib.pbkdf2_hmac.

提供 PBKDF2 算法

今天,当一位朋友向我指出 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)