AES-256-CBC 加密在 Python 和 PHP 中返回不同结果,求助!

1 投票
2 回答
61 浏览
提问于 2025-04-12 06:02

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 个回答

1

问题出在你的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
1

如果你想得到相同的结果,那就把 $key = base64_decode($key_base64) 这一行去掉。我用 $key = $key_base64 也得到了正确的结果。


注意,直接使用 base64 编码作为密钥意味着每个字符或字节中最多只有 6 位是随机信息。所以如果你插入 32 个字符,密钥的实际大小就是 32 * 6 = 192 位。这已经足够用了,但明显少于 AES-256 所“承诺”的 256 位。

另一个问题是,CBC 模式并不能保证数据的完整性和真实性。


我建议你最好不要把密钥转换成字符串,直接保持它们为二进制格式。此外,如果你要用对称密钥进行加密,建议使用像 NaCL 这样的库,或者其他能提供更高级“封装”功能的库,而不仅仅是加密。如果你想用密码,应该使用一个好的基于密码的密钥派生函数(PBKDF),比如 PBKDF2 或 Argon2。

撰写回答