前后端AES加密兼容问题:前端加密后后端解密报填充错误
解决前后端AES加密兼容问题(Python后端 + React/CryptoJS前端)
核心问题分析
后端解密时报ValueError: Data must be padded to 16 byte boundary in CBC mode,本质是前后端在密钥/IV推导、数据编码拼接逻辑上不匹配:
- 前端用SHA256做密钥推导,后端用的是MD5版本的
bytes_to_key_md5方法 - 前端将salt、密文转成Hex字符串拼接,后端期望直接拼接原始字节流
- 前端加密/解密时的编码转换错误,导致后端拿到的密文长度不符合CBC模式的16字节块要求
前端代码修改步骤
1. 替换密钥推导算法为MD5
后端使用bytes_to_key_md5生成密钥和IV,前端需要完全对齐该逻辑:
- 保持
data = 密码短语 + salt的拼接顺序 - 循环用MD5哈希生成足够长度的密钥+IV,避免字符编码差异,全程用WordArray处理字节
2. 修正加密结果拼接逻辑
后端加密结果格式为b"Salted__" + salt + 加密字节,再Base64编码。前端需直接拼接WordArray,避免转成Hex字符串:
- 用
CryptoJS.enc.Latin1.parse生成"Salted__"的字节数组 - 拼接前缀、salt、密文后直接转Base64,不要用
btoa(易出现编码丢失)
3. 修正解密时的编码处理
解密时直接用CryptoJS解析Base64,操作原始WordArray截取salt和密文:
- 避免将Base64解码后的字节转成字符串再截取,防止字节信息丢失
4. 统一填充方式
后端自定义的pad/unpad逻辑属于PKCS7填充,和CryptoJS的CryptoJS.pad.Pkcs7完全兼容,保留前端自动填充/解填充即可
修正后的前端代码
import React from "react"; import CryptoJS from 'crypto-js'; const DecryptEncrypt = () => { // 完全对齐后端bytes_to_key_md5逻辑 function bytesToKeyMd5(passphrase, salt, output = 48) { if (salt.sigBytes !== 8) { throw new Error('Salt must be 8 bytes long.'); } const passphraseBytes = CryptoJS.enc.Latin1.parse(passphrase); let data = passphraseBytes.concat(salt); let key = CryptoJS.MD5(data); let finalKey = key; while (finalKey.sigBytes < output) { data = key.concat(data); key = CryptoJS.MD5(data); finalKey = finalKey.concat(key); } return finalKey; } const decryptData = (encryptedData, key) => { // 直接解析Base64为WordArray const encryptedWordArray = CryptoJS.enc.Base64.parse(encryptedData); // 验证前缀格式 const prefix = encryptedWordArray.slice(0, 8).toString(CryptoJS.enc.Latin1); if (prefix !== "Salted__") { throw new Error("Invalid encrypted data format"); } // 截取salt和密文 const salt = encryptedWordArray.slice(8, 16); const ciphertext = encryptedWordArray.slice(16); // 推导密钥和IV const keyIv = bytesToKeyMd5(key, salt, 32 + 16); const keyBytes = keyIv.slice(0, 32); const iv = keyIv.slice(32, 32 + 16); // 解密并自动解填充 const decrypted = CryptoJS.AES.decrypt( { ciphertext: ciphertext }, keyBytes, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); return decrypted.toString(CryptoJS.enc.Utf8); }; const encryptData = (data, key) => { // 生成8字节随机salt const salt = CryptoJS.lib.WordArray.random(8); // 推导密钥和IV const keyIv = bytesToKeyMd5(key, salt, 32 + 16); const keyBytes = keyIv.slice(0, 32); const iv = keyIv.slice(32, 32 + 16); // 加密并自动填充 const encrypted = CryptoJS.AES.encrypt( data, keyBytes, { iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 } ); // 拼接前缀+salt+密文,转Base64 const prefix = CryptoJS.enc.Latin1.parse("Salted__"); const encryptedWordArray = prefix.concat(salt).concat(encrypted.ciphertext); return encryptedWordArray.toString(CryptoJS.enc.Base64); }; const dataToEncrypt = 'Data to be sent over network'; const encryptionKey = "<secret passpharse value>"; const encryptedData = encryptData(dataToEncrypt, encryptionKey); console.log("Encrypted data:", encryptedData); const decryptedData = decryptData(encryptedData, encryptionKey); console.log("Decrypted data:", decryptedData); return (<> Check </>); } export default DecryptEncrypt;
验证要点
- 确保前后端的密码短语完全一致
- 前端加密后的Base64字符串可直接传入后端
decrypt方法测试,应能正确解密 - 后端加密的结果,前端也可通过
decryptData正确解密
内容的提问来源于stack exchange,提问作者Vishal Kharde
相关产品推荐
相关产品推荐

