当前位置:首页 > 技术 > 正文内容

解决前后端AES加密集成中的Data must be padded错误

访客 技术 2026年7月20日 3

问题描述

在实现前后端AES加密集成时,后端Python代码抛出如下错误:

ValueError: Data must be padded to 16 byte boundary in CBC mode

这表明前端加密后的数据格式与后端期望的格式不匹配,主要问题出现在密钥派生、初始化向量(IV)处理以及数据填充方式上。

后端Python实现

后端使用OpenSSL兼容的加密方案,核心点包括:

  • 密钥派生使用EVP_BytesToKey()方法,默认采用MD5摘要算法,单次迭代,8字节盐值
  • 加密算法为AES-256-CBC,配合PKCS#7填充
  • 输出格式为Base64编码的OpenSSL格式:Salted__ + 8字节盐值 + 密文
class CryptoHandler():
    def apply_padding(self, data):
        block_size = 16
        pad_len = block_size - (len(data) % block_size)
        return data + (chr(pad_len) * pad_len)

    def remove_padding(self, data):
        return data[:-(data[-1] if isinstance(data[-1], int) else ord(data[-1]))]

    def derive_key_iv_md5(self, password, salt, length=48):
        assert len(salt) == 8
        combined = password + salt
        key = hashlib.md5(combined).digest()
        result = key
        while len(result) < length:
            key = hashlib.md5(key + combined).digest()
            result += key
        return result[:length]

    def derive_key_iv_sha256(self, password, salt, length=48):
        assert len(salt) == 8
        combined = password + salt
        key = hashlib.sha256(combined).digest()
        result = key
        while len(result) < length:
            key = hashlib.sha256(key + combined).digest()
            result += key
        return result[:length]

    # 加密函数
    def encrypt_aes(self, plaintext, passphrase, use_md5=True):
        salt = os.urandom(8)
        if use_md5:
            key_iv = self.derive_key_iv_md5(passphrase.encode(), salt)
        else:
            key_iv = self.derive_key_iv_sha256(passphrase.encode(), salt)
        aes_key = key_iv[:32]
        aes_iv = key_iv[32:]
        cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)
        padded_data = self.apply_padding(plaintext).encode()
        encrypted = cipher.encrypt(padded_data)
        return base64.b64encode(b"Salted__" + salt + encrypted).decode()

    # 解密函数
    def decrypt_aes(self, ciphertext_b64, passphrase, use_md5=True):
        raw = base64.b64decode(ciphertext_b64)
        assert raw[:8] == b"Salted__"
        salt = raw[8:16]
        if use_md5:
            key_iv = self.derive_key_iv_md5(passphrase.encode(), salt)
        else:
            key_iv = self.derive_key_iv_sha256(passphrase.encode(), salt)
        aes_key = key_iv[:32]
        aes_iv = key_iv[32:]
        cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)
        decrypted = cipher.decrypt(raw[16:])
        return self.remove_padding(decrypted).decode().strip('"')

# 使用示例
handler = CryptoHandler()
original = "需要加密传输的敏感数据"
encrypted = handler.encrypt_aes(original, "my_secret_passphrase")
print("加密结果:", encrypted)
decrypted = handler.decrypt_aes(encrypted, "my_secret_passphrase")
print("解密结果:", decrypted)

前端JavaScript兼容实现(使用CryptoJS)

CryptoJS默认兼容OpenSSL格式,因此无需手动处理密钥派生和IV,直接传入密码短语即可。根据后端使用的摘要算法选择对应配置:

方案一:MD5摘要(与bytes_to_key_md5对应)

// 注意:MD5是CryptoJS默认摘要算法,无需额外配置

const backendCipherMD5 = "U2FsdGVkX18lJwVCQIbRWqiIycIZg4LRZFHq+ORvygkE/umH1Il3m/yzgu3n9jVQhUikwXeURBW9yAjMawTk3A==";
const passphrase = "my_secret_passphrase";
const decryptedMD5 = CryptoJS.AES.decrypt(backendCipherMD5, passphrase);
console.log("解密结果(MD5):", decryptedMD5.toString(CryptoJS.enc.Utf8));

const plaintextMD5 = "需要加密传输的敏感数据";
const encryptedForBackendMD5 = CryptoJS.AES.encrypt(plaintextMD5, passphrase);
console.log("加密结果(MD5):", encryptedForBackendMD5.toString());

方案二:SHA256摘要(与bytes_to_key对应)

// 显式指定使用SHA256
CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();

const backendCipherSHA = "U2FsdGVkX189ft5ncnmOK/rJIB2fkdrfdWQCbf6DgbXkWMXw7yjX2oRXbDgZTIt4LibWBPamalnKCZl3l1VnWQ==";
const decryptedSHA = CryptoJS.AES.decrypt(backendCipherSHA, passphrase);
console.log("解密结果(SHA256):", decryptedSHA.toString(CryptoJS.enc.Utf8));

const plaintextSHA = "需要加密传输的敏感数据";
const encryptedForBackendSHA = CryptoJS.AES.encrypt(plaintextSHA, passphrase);
console.log("加密结果(SHA256):", encryptedForBackendSHA.toString());

React组件完整示例

import React from 'react';
import CryptoJS from 'crypto-js';

const SecureCommunication = () => {
    const passphrase = "my_secret_passphrase";

    const encryptToBackend = (data, useSHA256 = false) => {
        if (useSHA256) {
            CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();
        } else {
            CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.MD5.create();
        }
        const cipher = CryptoJS.AES.encrypt(data, passphrase);
        return cipher.toString();
    };

    const decryptFromBackend = (ciphertext, useSHA256 = false) => {
        if (useSHA256) {
            CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.SHA256.create();
        } else {
            CryptoJS.algo.EvpKDF.cfg.hasher = CryptoJS.algo.MD5.create();
        }
        const decrypted = CryptoJS.AES.decrypt(ciphertext, passphrase);
        return decrypted.toString(CryptoJS.enc.Utf8);
    };

    const testCommunication = () => {
        // 测试MD5方案
        const testData = "测试数据123";
        const encryptedMD5 = encryptToBackend(testData, false);
        console.log("MD5加密结果:", encryptedMD5);
        console.log("MD5解密结果:", decryptFromBackend(encryptedMD5, false));
    };

    return (
        <div>
            <button onClick={testCommunication}>测试加密通信</button>
        </div>
    );
};

export default SecureCommunication;

关键要点

  1. 兼容性根源:前后端必须使用相同的密钥派生算法(摘要类型相同)、相同的盐值处理方式和相同的填充标准(PKCS#7)
  2. 调试建议
    • 确保前后端使用完全相同的密码短语
    • 验证Base64编码/解码过程无误
    • 检查密文是否以"Salted__"开头
  3. 安全性警告:EVP_BytesToKey()搭配MD5和单次迭代是不安全的,生产环境建议升级到PBKDF2或bcrypt等现代密钥派生函数
  4. 注意事项:CryptoJS已停止维护(4.2.0为最终版本),新项目建议使用Web Crypto API或更现代的安全库

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。