AES-256-CBC 加密在 Python 和 PHP 中返回不同结果,求助!
Python代码:
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import base64
def encrypt_string(input_string, key_base64, str_iv):
try:
key = key_base64.encode('utf-8')
if str_iv:
iv = base64.b64decode(str_iv)
else:
iv = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_CBC, iv)
padded_data = pad(input_string.encode(), AES.block_size)
cipher_data = cipher.encrypt(padded_data)
combined_data = iv + cipher_data
return base64.b64encode(combined_data).decode()
except Exception as e:
print(e)
if __name__ == "__main__":
key = "1bd393e7a457f9023d9ba95fffb5a2e1"
iv = "1oTOhV9xGyu1mppmWZWa5w=="
input_string = "AAAAAAA"
encrypted_data = encrypt_string(input_string, key, iv)
print("Encrypted string:", encrypted_data)`
输出结果: 加密后的字符串:1oTOhV9xGyu1mppmWZWa5+kzveiTRzRH+gRVHx+7Ad0=
PHP代码:
<?php
function encrypt_string($input_string, $key_base64, $str_iv) {
try {
$key = base64_decode($key_base64);
if ($str_iv) {
$iv = base64_decode($str_iv);
} else {
$iv = openssl_random_pseudo_bytes(16);
}
$ciphertext = openssl_encrypt($input_string, 'aes-256-cbc', $key, OPENSSL_RAW_DATA, $iv);
$combined_data = $iv . $ciphertext;
return base64_encode($combined_data);
} catch (Exception $e) {
echo $e->getMessage();
}
}
$key = "1bd393e7a457f9023d9ba95fffb5a2e1";
$iv = "1oTOhV9xGyu1mppmWZWa5w==";
$input_string = "AAAAAAA";
$encrypted_data = encrypt_string($input_string, $key, $iv);
echo "Encrypted string: " . $encrypted_data . "\n";
?>
输出结果: 加密后的字符串:1oTOhV9xGyu1mppmWZWa53Nc8rxWTultBWLvWitUICQ=
请问,有谁知道如何让这两段代码的输出结果一样吗?
2 个回答
问题出在你的PHP代码上。openssl_encrypt
这个函数需要一个passphrase
参数:
如果这个密码短于预期,它会自动用NUL字符填充;如果密码长于预期,它会自动截断。
对于AES 256来说,这个密码应该是32个字符。你的$key
变量被填充了\null
字节。
每个函数的第一行代码是不同的。
在你的Python代码中,这只是把ASCII格式的密钥从字符串转换成字节。因为初始密钥的长度是32,所以这样做是没问题的。
key = key_base64.encode('utf-8')
而在你的PHP代码中,这段代码是把密钥从base 64解码回字节,这样就把密钥缩短到了24个字节。
$key = base64_decode($key_base64);
为了让Python代码和PHP代码一致,你需要这样做:
key = base64.b64decode(key)
不过,这样在Python代码后面会出问题,因为密钥的长度只有24,而Python不会自动填充密钥。
一个解决办法是用密钥的哈希值作为密码,并把它截断到32个字符。要注意,这并不是一个好的安全做法,见下面@maarten-bodewes的评论。这里有Python和PHP的示例。我为了简单起见,去掉了异常处理。
from cryptography.hazmat.primitives.ciphers import Cipher, modes
from cryptography.hazmat.primitives.ciphers.algorithms import AES256
from cryptography.hazmat.primitives.padding import PKCS7
import hashlib
import base64
def encrypt_string(input_string: str, key: bytes, iv: bytes):
# the key must be of length 32
key_hash = hashlib.sha256(key).digest()[:32]
cipher = Cipher(AES256(key_hash), modes.CBC(iv))
pad = PKCS7(AES256.block_size).padder()
encryptor = cipher.encryptor()
cipher_data = encryptor.update(
pad.update(input_string.encode('utf-8'))
+ pad.finalize()
)
combined_data = iv + cipher_data
return base64.b64encode(combined_data)
if __name__ == '__main__':
key = b'password'
iv = b'aaaabbbbccccdddd'
input_string = "abcdefghijklmnopqrstuvwxyz"
encrypted_data = encrypt_string(input_string, key, iv)
print("Encrypted string:", encrypted_data.decode())
# prints:
# Encrypted string: YWFhYWJiYmJjY2NjZGRkZALrlaYfFmHrO6FOv64BdVueK/yYNjjgtdQx+A0eR9iv
还有PHP代码:
<?php
function encrypt_string($input_string, $key, $iv) {
// take the first 32 characters, just to be explicit
$key_hash = substr(hash('sha256', $key, true), 0, 32);
$ciphertext = openssl_encrypt(
$input_string, "aes-256-cbc", $key_hash, OPENSSL_RAW_DATA, $iv
);
$combined_data = $iv . $ciphertext;
return base64_encode($combined_data);
}
$key = 'password';
$iv = "aaaabbbbccccdddd";
$input_string = "abcdefghijklmnopqrstuvwxyz";
$encrypted_data = encrypt_string($input_string, $key, $iv);
echo "Encrypted string: " . $encrypted_data . "\n";
?>
// echos:
// Encrypted string: YWFhYWJiYmJjY2NjZGRkZALrlaYfFmHrO6FOv64BdVueK/yYNjjgtdQx+A0eR9iv
如果你想得到相同的结果,那就把 $key = base64_decode($key_base64)
这一行去掉。我用 $key = $key_base64
也得到了正确的结果。
注意,直接使用 base64 编码作为密钥意味着每个字符或字节中最多只有 6 位是随机信息。所以如果你插入 32 个字符,密钥的实际大小就是 32 * 6 = 192 位。这已经足够用了,但明显少于 AES-256 所“承诺”的 256 位。
另一个问题是,CBC 模式并不能保证数据的完整性和真实性。
我建议你最好不要把密钥转换成字符串,直接保持它们为二进制格式。此外,如果你要用对称密钥进行加密,建议使用像 NaCL 这样的库,或者其他能提供更高级“封装”功能的库,而不仅仅是加密。如果你想用密码,应该使用一个好的基于密码的密钥派生函数(PBKDF),比如 PBKDF2 或 Argon2。