You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 17:38:07