Python AES-256-CBC 加密到 PHP 使用 openssl_decrypt 解密 return false

Python AES-256-CBC Encrypt to PHP Decrypt with openssl_decrypt return false

我正在 python 中成功加密数据并将 url 安全数据发送到 PHP。当我尝试使用 openssl_decrypt PHP 方法解密数据时,它 returns false。我已经阅读了 2 天,知道正在寻找 none 的解决方案来解决我的问题 [这个论坛上有 2 个类似的问题,但他们没有帮助,Q1, Q2]。

加密使用python3.8和包PyCryptodome:

from Crypto.Cipher import AES
import base64
import hashlib
import os
import secrets

BLOCK = 16
PADDING = '~'
iv = secrets.token_urlsafe(16)
secretKey = secrets.token_urlsafe(32) # 32 bytes is used as AES 256 expects 256 bits
encoding = 'latin-1'
s = 'very secret data' # secret to be encrypted

k = hashlib.sha256(secretKey.encode(encoding)).hexdigest()[:32].encode(encoding) # key hashed and encoded
pad = s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
cipher = AES.new(key=k, mode=AES.MODE_CBC, IV=hashlib.sha256(iv.encode(encoding)).hexdigest()[:16].encode(encoding)) # cipher object

encrypted_secret = base64.urlsafe_b64encode(cipher.encrypt(pad.encode(encoding)))
print("Key: ", secretKey, "\n", "IV:",iv, "\n", "Encrypted:", encrypted_secret.decode(encoding))

Outputs[0]:

Key:  cdLTCHW95FjHF6ESu2Rkm-90AUbzDBv71HrdshsEx3k 
 IV: b16ezD5O05EDNovsqLExUg 
 Encrypted: RJqA3-n6d7hmKgj7biiwUKD0SjRjm__4f42P06R-qO8=

*我解码了加密的秘密结果,因为它是作为 json 编码对象发送的,而 json 的编码器抱怨其中有字节对象。

在PHP中解密是更少的代码(为了测试,我复制了密钥和iv,但每次都是新创建的iv,密钥存储在不同的服务器上):

$key = 'cdLTCHW95FjHF6ESu2Rkm-90AUbzDBv71HrdshsEx3k';
$iv = 'b16ezD5O05EDNovsqLExUg';
$method = "AES-256-CBC";
$blocksize = 16;
$padwith = '~';
$decoded_secret = 'RJqA3-n6d7hmKgj7biiwUKD0SjRjm__4f42P06R-qO8='; // secret is sent as string

$hashKey = substr(hash('sha256', $key), 0, 32);
$iv_len = openssl_cipher_iv_length($method);
$iv_hash = substr(hash('sha256', $iv), 0, $iv_len); // cipher expects an IV of precisely 16 bytes, padding with \0

$decrypted_secret = openssl_decrypt($decoded_secret, $method, $hashKey, OPENSSL_RAW_DATA, $iv_hash);

$what = rtrim($decrypted_secret, $padwith);
var_dump($decrypted_secret, $what);

Outputs[1]: bool(false) string(0) ""

我试过在 python 中设置 utf-8 编码,在 php 中用 base64 解码加密字符串,将块大小更改为 32,而不是使用 hashlib 在 python 端,但 none 的更改给出了预期的解密结果。

有人可以给我解决这个问题的线索吗?

你实际上需要两条线索,所以我们称之为假期奖金:-)

  • 你的数据在 urlsafe-base64 中,但是你的 php 没有解码它,所以它试图解密完全错误的数据。 php (AFAICS) 不直接支持urlsafe,所以你需要转换它;然后你可以解码它并传递给 openssl_decrypt,或者简单地调用 openssl_decrypt 而没有 OPENSSL_RAW_DATA 因为它默认为(传统的又名 MIME 或 PEM 或 PGP或 XML) base64.

  • 您的加密不使用 PKCS5/7 填充,但 OpenSSL 默认使用它,因此您的解密失败。你需要使用 OPENSSL_ZERO_PADDING ,在这种情况下变成空操作,然后(就像你已经做的那样)自己做 unpad。

对于此代码中未注释或注释的行,它有效:

<?php
$key = 'cdLTCHW95FjHF6ESu2Rkm-90AUbzDBv71HrdshsEx3k';
$iv = 'b16ezD5O05EDNovsqLExUg';
$method = "AES-256-CBC";
$blocksize = 16; // not used
$padwith = '~';
$secret = 'RJqA3-n6d7hmKgj7biiwUKD0SjRjm__4f42P06R-qO8='; // secret is sent as string
$fixed_secret = str_replace(array('-','_'),array('+','/'),$secret);
$decoded_secret = base64_decode($fixed_secret); // maybe used

$hashKey = substr(hash('sha256', $key), 0, 32);
$iv_len = openssl_cipher_iv_length($method);
$iv_hash = substr(hash('sha256', $iv), 0, $iv_len);

$decrypted_secret = openssl_decrypt($fixed_secret, $method, $hashKey, OPENSSL_ZERO_PADDING, $iv_hash);
//$decrypted_secret = openssl_decrypt($decoded_secret, $method, $hashKey, OPENSSL_RAW_DATA+OPENSSL_ZERO_PADDING, $iv_hash);

$what = rtrim($decrypted_secret, $padwith);
var_dump($decrypted_secret, $what);
?>
->
string(32) "very secret data~~~~~~~~~~~~~~~~"
string(16) "very secret data"