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

前后端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;

验证要点

  1. 确保前后端的密码短语完全一致
  2. 前端加密后的Base64字符串可直接传入后端decrypt方法测试,应能正确解密
  3. 后端加密的结果,前端也可通过decryptData正确解密

内容的提问来源于stack exchange,提问作者Vishal Kharde

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 01:50:00