关于将CryptoJS中EvpKDF实现移植到Python的问题
CryptoJS AES加密移植Python:EvpKDF拆分后结果不一致问题
核心问题根源
你遇到的结果不一致,核心原因是CryptoJS中i.toString()的默认行为和你Python中使用的输入格式不匹配:
i是CryptoJS的WordArray对象(512位SHA512哈希结果),其默认toString()方法会输出小写十六进制字符串(512位对应128个字符的0-9a-f字符串)。- 当你将这个字符串传入
CryptoJS.AES.encrypt作为密码参数时,EvpKDF的输入是该十六进制字符串的UTF-8编码字节,而非原始的512位哈希字节。 - 你拆分EvpKDF单独执行时,大概率直接使用了哈希的原始字节作为输入,导致EvpKDF生成的密钥完全不同。
修复步骤
1. 对齐EvpKDF输入格式
在Python中,按以下流程处理输入:
- 对原始密码执行1000次SHA512哈希,得到bytes类型的哈希结果。
- 将哈希结果转换为小写十六进制字符串(和CryptoJS的
toString()输出一致)。 - 将该十六进制字符串按UTF-8编码为字节数组,作为EvpKDF的输入密码。
2. 对齐EvpKDF参数
确保Python实现的EvpKDF和CryptoJS完全一致:
- 哈希函数:MD5(CryptoJS的EvpKDF默认用MD5)。
- 迭代次数:1000(和原代码配置一致)。
- 派生数据长度:需覆盖密钥+IV的总长度。原代码中
keySize=128(字节),CryptoJS默认IV长度为16字节(AES-CBC的标准IV长度),所以EvpKDF需派生128+16=144字节数据,前128字节为密钥,后16字节为IV。 - 盐:如果原代码未指定盐,CryptoJS会自动生成随机盐;若测试时用固定盐,需确保Python中使用完全相同的盐(字节一致)。
3. 对齐AES加密参数
CryptoJS的AES默认配置:
- 模式:CBC。
- 填充:Pkcs7。
- IV:由EvpKDF派生的后16字节数据(若未手动指定)。
代码示例对比
原关键JavaScript逻辑
// 生成1000次SHA512的哈希i let i = CryptoJS.SHA512("your_password"); for (let j = 0; j < 999; j++) { i = CryptoJS.SHA512(i); } // 用i的十六进制字符串作为EvpKDF输入,生成128字节密钥+16字节IV let encrypted = CryptoJS.AES.encrypt("your_plaintext", i.toString(), { keySize: 128, iterations: 1000 });
对应Python修复逻辑
import hashlib from Crypto.Cipher import AES from Crypto.Util.Padding import pad # 实现CryptoJS风格的EvpKDF(MD5哈希) def evpkdf(password, salt, key_size, iterations): derived = b"" block = b"" while len(derived) < key_size: block = hashlib.md5(block + password + salt).digest() for _ in range(iterations - 1): block = hashlib.md5(block).digest() derived += block return derived[:key_size] # 步骤1:1000次SHA512哈希 password = b"your_password" hash_bytes = password for _ in range(1000): hash_bytes = hashlib.sha512(hash_bytes).digest() # 步骤2:转换为小写十六进制字符串,再转UTF-8字节 kdf_input = hash_bytes.hex().encode("utf-8") # 步骤3:执行EvpKDF(假设盐和原JS一致,这里用示例盐) salt = b"your_salt" # 原JS中若自动生成,需提取对应盐 total_derived_size = 128 + 16 # 密钥128字节 + IV16字节 derived_data = evpkdf(kdf_input, salt, total_derived_size, 1000) key = derived_data[:128] iv = derived_data[128:] # 步骤4:AES加密(对齐CBC+Pkcs7) cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = b"your_plaintext" ciphertext = cipher.encrypt(pad(plaintext, AES.block_size))
验证要点
- 对比
i.toString()的输出和Python中hash_bytes.hex()的结果,确保完全一致(小写十六进制,无额外前缀/后缀)。 - 对比EvpKDF生成的密钥、IV字节,确保和CryptoJS中派生的结果一致(可通过在JS中打印
encrypted.key和encrypted.iv的十六进制值验证)。
内容的提问来源于stack exchange,提问作者user25361095
相关产品推荐
相关产品推荐

