如何在Python中解密经PHP openssl加密的值
如何在Python中解密经PHP openssl加密的值
嘿,我来帮你搞定这个解密的问题!首先得把PHP里openssl_encrypt的默认行为摸透,这可是你解决块大小问题的关键——毕竟Python的加密库不会自动跟PHP的默认操作对齐,差一点都不行。
先理清楚你的加密流程:你是先用openssl_encrypt($key, $cipher, $passphrase, 0, $iv)把真正用来解密目标值(也就是最终要拿到的'Jimmy')的密钥给加密了,对吧?现在得先解密这个加密后的密钥,再用它去解密最终的加密值。
先给你唠唠PHP openssl_encrypt的几个默认操作,Python里必须1:1对齐:
- 密钥生成:当你传入
$passphrase(口令)时,PHP会用EVP_BytesToKey算法(默认MD5哈希、迭代1次)生成符合加密算法要求的密钥(比如AES-256需要32字节密钥) - 填充方式:默认用PKCS#7填充,也就是如果数据长度不是块大小(AES固定16字节)的整数倍,会自动补全
- 输出格式:当
options设为0时,返回的是base64编码后的字符串,不是原始字节
接下来直接上Python的实现步骤,都是我踩过坑后整理的靠谱代码:
第一步:实现PHP风格的EVP_BytesToKey密钥生成
这一步是核心,因为Python的库不会自动做这个密钥推导,得自己实现:
import hashlib import base64 from Crypto.Cipher import AES from Crypto.Util.Padding import unpad def evp_bytes_to_key(password, key_len, iv_len): # 完全对齐PHP默认的EVP_BytesToKey逻辑:MD5哈希、迭代1次 password_bytes = password.encode('utf-8') key = b'' temp = b'' while len(key) < key_len: temp = hashlib.md5(temp + password_bytes).digest() key += temp # 因为我们手动传了IV,这里只返回对应长度的密钥 return key[:key_len]
第二步:解密加密后的密钥
用上面的函数生成正确的密钥,再解密出真正用来拿'Jimmy'的明文密钥:
def decrypt_encrypted_key(encrypted_key_b64, passphrase, iv, cipher): # 针对AES-256-CBC的配置,密钥长度32字节,块大小16字节 if cipher == 'aes-256-cbc': key_len = 32 block_size = 16 else: raise ValueError(f"暂时不支持这个加密算法:{cipher}") # 生成PHP风格的密钥 derived_key = evp_bytes_to_key(passphrase, key_len, block_size) # 把base64编码的加密密钥解码成字节 encrypted_data = base64.b64decode(encrypted_key_b64) # 初始化AES-CBC解密器 cipher_obj = AES.new(derived_key, AES.MODE_CBC, iv=iv.encode('utf-8')) # 解密并去除PKCS#7填充 decrypted_key = unpad(cipher_obj.decrypt(encrypted_data), block_size) return decrypted_key.decode('utf-8') # 替换成你自己的实际参数 encrypted_key_b64 = "你的加密后密钥的base64字符串" passphrase = "你用的口令" iv = "加密时用的16字节IV" cipher_type = "aes-256-cbc" # 拿到真正的解密密钥 real_decrypt_key = decrypt_encrypted_key(encrypted_key_b64, passphrase, iv, cipher_type)
第三步:解密最终值得到'Jimmy'
现在用拿到的real_decrypt_key解密最终的加密值,这里要注意:如果最终值也是用同样的openssl_encrypt加密的,要确认IV是否一致(或者是不是用了另一个IV):
def decrypt_final_value(encrypted_value_b64, raw_key, iv, cipher): if cipher == 'aes-256-cbc': block_size = 16 # 确保密钥是AES-256要求的32字节 raw_key_bytes = raw_key.encode('utf-8') if len(raw_key_bytes) != 32: raise ValueError("AES-256需要32字节的密钥,检查下你的明文密钥长度哦") encrypted_data = base64.b64decode(encrypted_value_b64) cipher_obj = AES.new(raw_key_bytes, AES.MODE_CBC, iv=iv.encode('utf-8')) decrypted_data = unpad(cipher_obj.decrypt(encrypted_data), block_size) return decrypted_data.decode('utf-8') # 替换成你自己的最终加密值 encrypted_value_b64 = "要解密的最终值的base64字符串" final_result = decrypt_final_value(encrypted_value_b64, real_decrypt_key, iv, cipher_type) print(final_result) # 这里应该就能输出'Jimmy'啦!
几个容易踩坑的关键点,一定要注意:
- IV必须是16字节:AES-CBC的IV长度固定是16字节,PHP里传错IV会直接报错,所以你肯定是用了16字节的,Python里也要完全用同一个IV
- 编码统一用UTF-8:PHP默认用UTF-8处理字符串,Python里所有字符串转字节都要指定
utf-8,不然会出现乱码或者解密失败 - 填充必须用PKCS#7:PHP默认用这个填充方式,Python的
Crypto.Util.Padding.unpad正好支持,别用错其他填充方式 - base64解码不能忘:PHP的
openssl_encrypt默认返回base64字符串,必须先解码成字节再解密,不然肯定会出现块大小不匹配的错误
备注:内容来源于stack exchange,提问作者kfernandez
相关产品推荐
相关产品推荐

