AES 解密失败的根源与正确实现方案:IV 一致性、密钥处理与编码规范

风强大大_9039

风强大大_9039

2026-06-08

820人浏览

原创

AES 解密失败的根源与正确实现方案:IV 一致性、密钥处理与编码规范

Node.js 中 AES 解密报 Bad decrypt 或 BadPaddingException,绝大多数源于加密与解密阶段 IV 不一致、密钥类型/长度不匹配或数据编码错位——本文系统梳理核心原因并提供可直接落地的修复代码。

node.js 中 aes 解密报 `bad decrypt` 或 `badpaddingexception`,绝大多数源于加密与解密阶段 iv 不一致、密钥类型/长度不匹配或数据编码错位——本文系统梳理核心原因并提供可直接落地的修复代码。

在你的 Express 后端中,decryptData 报错 bad decrypt 的根本原因非常明确:加密时每次生成新 IV(crypto.randomBytes(8).toString('hex')),但解密时却复用同一个全局 iv 变量,且该 IV 长度仅 8 字节(64 位),严重违反 AES-CBC 对 16 字节 IV 的强制要求。这导致 createDecipheriv 初始化失败,底层 OpenSSL 直接抛出填充异常(实际是 IV 验证失败,被误报为 padding error)。

Booltool
Booltool

Booltool是一款集合图片修复、抠图、增强和视频编辑能力的在线 AI 创作工具。

下载

? 关键问题逐层剖析

问题点 你的代码表现 危害 正确做法
IV 长度错误 crypto.randomBytes(8).toString('hex') → 16字符 hex 字符串 = 8字节 AES-CBC 要求 IV 必须为 16 字节(128 位);8 字节 IV 会导致 createDecipheriv 内部校验失败 crypto.randomBytes(16)(二进制 Buffer),不可 .toString('hex') 后再用
IV 复用 vs IV 绑定 全局 const iv = ... 被所有加解密共享 加密用随机 IV,解密却用固定 IV → 必然失败;且多条记录共用同一 IV 破坏语义安全性 每次加密生成独立 IV,并与密文绑定存储(如拼接);解密时先拆分 IV,再解密
密钥类型不一致 加密用 Buffer.from(key, 'hex'),解密直接传 secretKey(原始 Buffer) 若 secretKey 是 randomBytes(32) 生成的 Buffer,则加密时错误地转成了 hex 字符串再解析,造成密钥失真 密钥必须全程保持 同一 Buffer 实例;避免在 hex/string/buffer 间无损往返转换
编码链断裂 加密输出 Buffer.from(encryptedData).toString('base64')(冗余 base64 编码),解密时又 Buffer.from(..., 'base64') → toString('utf8') → cipher.update(..., 'base64', ...) 多次 base64 编解码引入乱码风险;cipher.update(data, 'base64') 要求输入是 base64 字符串,但你传入的是已解码的 UTF-8 字符串 密文应以原始二进制 Buffer 形式拼接 IV 并统一 base64 编码;解密时一次性解码,再拆分处理

✅ 正确实现:安全、可复用的 AES-256-CBC 加解密模块

const crypto = require('crypto');

// ✅ 安全密钥生成(生产环境建议用 PBKDF2 衍生)
const SECRET_KEY = crypto.randomBytes(32); // 32 字节 = AES-256

/**
 * 加密函数:每次生成新 IV,与密文拼接后 base64 编码
 * @param {string} plaintext - 明文(UTF-8 字符串)
 * @param {Buffer} key - 32 字节密钥 Buffer
 * @returns {string} base64 编码的 "IV + ciphertext"
 */
function encryptData(plaintext, key) {
  const iv = crypto.randomBytes(16); // ✅ 严格 16 字节
  const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);

  const encrypted = Buffer.concat([
    iv, // ✅ 前 16 字节为 IV
    cipher.update(plaintext, 'utf8'),
    cipher.final()
  ]);

  return encrypted.toString('base64'); // ✅ 单次 base64 编码整块数据
}

/**
 * 解密函数:从 base64 解码,拆分 IV 和密文,执行解密
 * @param {string} ciphertextB64 - encryptData 输出的 base64 字符串
 * @param {Buffer} key - 同加密密钥
 * @returns {string} 解密后的明文
 */
function decryptData(ciphertextB64, key) {
  try {
    const ivCiphertext = Buffer.from(ciphertextB64, 'base64'); // ✅ 一次性解码
    if (ivCiphertext.length  {
  try {
    const data = await UploadData.find().lean(); // .lean() 返回 plain object,避免 Mongoose 文档方法干扰

    const decryptedData = data.map(item => {
      // 注意:确保 item.fullname 等字段是 encryptData() 输出的 base64 字符串
      return {
        ...item,
        fullname: item.fullname ? decryptData(item.fullname, SECRET_KEY) : null,
        catName: item.catName ? decryptData(item.catName, SECRET_KEY) : null,
        email: item.email ? decryptData(item.email, SECRET_KEY) : null,
        contact: item.contact ? decryptData(item.contact, SECRET_KEY) : null,
        location: item.location ? decryptData(item.location, SECRET_KEY) : null,
      };
    });

    res.json(decryptedData);
  } catch (error) {
    console.error("Decryption failed:", error);
    res.status(500).json({ error: "Decryption failed. Check encryption integrity." });
  }
});

⚠️ 重要注意事项

  • IV 必须随密文持久化:数据库中存储的加密字段,必须是 encryptData() 输出的完整 base64 字符串(含 IV)。切勿单独存 IV 或丢弃 IV。
  • 密钥管理:SECRET_KEY 应通过环境变量注入(如 process.env.AES_SECRET_KEY),并确保其为 32 字节 hex 字符串(可用 Buffer.from(process.env.AES_SECRET_KEY, 'hex') 解析)。
  • 错误处理粒度:不要对单个字段解密失败就中断整个响应,应捕获并设为 null 或默认值,保障服务可用性。
  • 性能提示:高频解密场景建议将 SECRET_KEY 和算法预编译为常量,避免重复解析。

遵循以上方案,即可彻底解决 bad decrypt 问题,同时满足密码学最佳实践:唯一 IV + 完整密钥 + 无损二进制流处理。

相关文章

PHP速学视频免费教程(入门到精通)
PHP速学视频免费教程(入门到精通)

PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!

下载

相关标签:

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

相关专题

更多
js获取数组长度的方法
js获取数组长度的方法

在js中,可以利用array对象的length属性来获取数组长度,该属性可设置或返回数组中元素的数目,只需要使用“array.length”语句即可返回表示数组对象的元素个数的数值,也就是长度值。php中文网还提供JavaScript数组的相关下载、相关课程等内容,供大家免费下载使用。

2023.06.20

4666

5

js刷新当前页面
js刷新当前页面

js刷新当前页面的方法:1、reload方法,该方法强迫浏览器刷新当前页面,语法为“location.reload([bForceGet]) ”;2、replace方法,该方法通过指定URL替换当前缓存在历史里(客户端)的项目,因此当使用replace方法之后,不能通过“前进”和“后退”来访问已经被替换的URL,语法为“location.replace(URL) ”。php中文网为大家带来了js刷新当前页面的相关知识、以及相关文章等内容

2023.07.04

1169

3

js四舍五入
js四舍五入

js四舍五入的方法:1、tofixed方法,可把 Number 四舍五入为指定小数位数的数字;2、round() 方法,可把一个数字舍入为最接近的整数。php中文网为大家带来了js四舍五入的相关知识、以及相关文章等内容

2023.07.04

4604

6

js删除节点的方法
js删除节点的方法

js删除节点的方法有:1、removeChild()方法,用于从父节点中移除指定的子节点,它需要两个参数,第一个参数是要删除的子节点,第二个参数是父节点;2、parentNode.removeChild()方法,可以直接通过父节点调用来删除子节点;3、remove()方法,可以直接删除节点,而无需指定父节点;4、innerHTML属性,用于删除节点的内容。

2023.09.01

920

4

JavaScript转义字符
JavaScript转义字符

JavaScript中的转义字符是反斜杠和引号,可以在字符串中表示特殊字符或改变字符的含义。本专题为大家提供转义字符相关的文章、下载、课程内容,供大家免费下载体验。

2023.09.04

1836

5

js生成随机数的方法
js生成随机数的方法

js生成随机数的方法有:1、使用random函数生成0-1之间的随机数;2、使用random函数和特定范围来生成随机整数;3、使用random函数和round函数生成0-99之间的随机整数;4、使用random函数和其他函数生成更复杂的随机数;5、使用random函数和其他函数生成范围内的随机小数;6、使用random函数和其他函数生成范围内的随机整数或小数。

2023.09.04

3325

4

如何启用JavaScript
如何启用JavaScript

JavaScript启用方法有内联脚本、内部脚本、外部脚本和异步加载。详细介绍:1、内联脚本是将JavaScript代码直接嵌入到HTML标签中;2、内部脚本是将JavaScript代码放置在HTML文件的`<script>`标签中;3、外部脚本是将JavaScript代码放置在一个独立的文件;4、外部脚本是将JavaScript代码放置在一个独立的文件。

2023.09.12

4313

6

Js中Symbol类详解
Js中Symbol类详解

javascript中的Symbol数据类型是一种基本数据类型,用于表示独一无二的值。Symbol的特点:1、独一无二,每个Symbol值都是唯一的,不会与其他任何值相等;2、不可变性,Symbol值一旦创建,就不能修改或者重新赋值;3、隐藏性,Symbol值不会被隐式转换为其他类型;4、无法枚举,Symbol值作为对象的属性名时,默认是不可枚举的。

2023.09.20

2840

5

java访问控制修饰符介绍
java访问控制修饰符介绍

java访问控制修饰符有四种,分别是public、protected、private、默认访问修饰符。详细介绍:1、public,public是最宽松的访问控制修饰符,被修饰的类、方法和变量可以被任何其他类访问,当一个类、方法或变量被声明为public时,它们可以在任何地方被访问,无论是同一个包中的类还是不同包中的类;2、protected修饰符等等。

2023.09.20

908

7

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
PHP数字签名与加密解密
PHP数字签名与加密解密

共12课时 | 1.7万人学习

LLVM Programmer’s Manual
LLVM Programmer’s Manual

共0课时 | 0人学习

Fitten Code官方手册
Fitten Code官方手册

共0课时 | 0人学习