不喜欢广告? 无广告 今天

AES加密模式详解 为什么GCM在大多数情况下优于CBC(以及在什么情况下不适用)

发布日期

仅AES算法并不构成完整的加密方案。加密模式——CBC、CTR、GCM、ECB——决定了你的加密是否真正安全。以下是开发者需要的实际分析。

AES加密模式详解:为什么GCM在大多数情况下优于CBC(以及在哪些情况下不适用)1
广告 移除?

当文档告诉你“使用AES-256”时,这只是不完整的建议。AES是一种分组密码——它每次加密一个128位的分组。 工作模式 正是决定AES如何处理超过16字节的实际数据,以及如何保护你免受能够观察或篡改密文的攻击者的影响。如果这一点搞错了,你就会得到“加密”数据,这些数据会泄露你的文件结构,或者系统会容易受到位翻转攻击。

完整的算法规范看起来是 AES-256-GCMAES-128-CBC ——密钥大小后接模式。本文将介绍每个模式的实际作用,解释为什么GCM是默认选择,以及在什么情况下你可能合理地选择其他模式。

首先,为什么ECB只是一个梗而不是单纯的玩笑

ECB(电子书本模式)是最直接的模式。每个16字节的明文分组都使用相同的密钥独立加密。这意味着相同的明文分组会产生相同的密文分组。

经典的演示是ECB加密的Linux企鹅(Tux)。位图是逐块加密的,但由于图像中大面积是单一颜色,加密后的版本仍然清晰地显示出企鹅的轮廓。加密在数学上是有效的——数据被打乱了——但 模式 仍然被保留。

在实际应用中,每当你的明文存在重复时,这种情况就会产生影响:具有共同前缀的数据库行、文件头、填充字段等。ECB会泄露结构。在新代码中,ECB没有任何合法的使用场景。如果你维护使用ECB的代码:请替换它。

值得了解的模式

CBC——大多数遗留代码使用的模式

密文分组链式异或每个明文分组与前一个密文分组,然后再进行加密。这解决了ECB模式的问题——相同的明文分组会产生不同的密文,因为链式结构使得每个分组的加密都依赖于前面的内容。

CBC需要一个初始化向量(IV)——一个随机的16字节值,用于为第一个分组启动链式结构。IV不需要保密,但必须是随机的,并且必须与密文一起传输。

CBC的问题在于: 它提供 机密性 但不提供 完整性。一个能够修改传输中密文的攻击者可以以可预测的方式翻转解密输出中的特定位。这就是填充预言攻击的基础,这些攻击已经破坏了真实系统(POODLE、BEAST、Lucky Thirteen)。如果你在没有单独的MAC(消息认证码)来验证完整性的情况下使用CBC,你就会面临风险。这种模式被称为“加密后加MAC”——先加密,然后对密文进行HMAC,再在解密前验证MAC。如果顺序搞错,这就是一个经典错误。

CBC也是顺序性的——你无法并行化加密(每个分组都依赖于前一个密文分组),而解密只能部分并行化。

CTR——从分组密码中获得流密码行为

计数器模式将AES转变为流密码。它不是直接加密明文,而是加密连续的计数器值,并将结果与明文进行异或。这意味着CTR模式不需要填充(它适用于任意长度的数据),在加密和解密方面都完全可并行化,并且允许在密文中的任意位置进行随机访问解密。

CTR使用一次性密钥(nonce)而不是完整的IV。对于给定密钥,每个消息的nonce必须是唯一的——如果使用相同的密钥和nonce,就会泄露两个明文的异或结果,这是灾难性的。与CBC不同,CTR也没有完整性保护。同样的情况:如果你想要抗篡改能力,必须使用“加密后加MAC”。

当需要流密码特性时,比如处理大文件、需要随机访问解密、高吞吐量场景中并行性很重要时,CTR是合适的,并且你必须在MAC层单独处理。

GCM——认证加密,一步到位

伽罗瓦/计数器模式是CTR模式加上内置的认证标签(GHASH)。你一次性获得机密性和完整性。认证标签——通常为128位——允许接收方在解密前验证密文是否被篡改。不需要单独的HMAC步骤,也无需担心“加密后加MAC”的顺序错误。

GCM还支持 附加认证数据(AAD) ——这些数据需要认证但不需要加密。这在头信息、元数据或任何需要完整性保护但不需要保密的数据中非常有用。AAD与密文一起验证认证标签。

GCM的默认nonce为96位(12字节)。与CTR一样,nonce重复是致命的——使用相同的密钥和nonce在GCM下会泄露明文和认证密钥(H),完全破坏安全性。始终使用密码学安全的随机数生成器生成nonce;除非你有精心设计的分布式计数器方案,否则不要从递增计数器派生nonce。

GCM是可并行化的,不需要填充。它是TLS 1.3、Signal协议和大多数现代加密库的标准推荐。如果你在编写新代码,GCM是你的默认选择。

模式对比

模式认证可并行化需要填充nonce/IV重复风险结论
ECB是的是的N/A(无IV)永远不要使用
CBC不(添加MAC)加密:否 / 解密:是是的中等仅用于遗留系统
CTR不(添加MAC)是的关键——重复会导致明文泄露小众用途
GCM是的(内置)是的关键——重复会导致完全破解默认选择

Node.js中的AES-256-GCM

这里是一个使用Node内置 crypto 模块的完整加密/解密实现——无需依赖项:

const crypto = require('crypto');

const ALGORITHM = 'aes-256-gcm';
const KEY_LENGTH = 32; // 256 bits
const NONCE_LENGTH = 12; // 96 bits — GCM default
const TAG_LENGTH = 16; // 128-bit auth tag

function encrypt(plaintext, key) {
  const nonce = crypto.randomBytes(NONCE_LENGTH);
  const cipher = crypto.createCipheriv(ALGORITHM, key, nonce, {
    authTagLength: TAG_LENGTH,
  });

  const encrypted = Buffer.concat([
    cipher.update(plaintext, 'utf8'),
    cipher.final(),
  ]);
  const tag = cipher.getAuthTag();

  // Prepend nonce + tag to ciphertext for storage/transmission
  return Buffer.concat([nonce, tag, encrypted]);
}

function decrypt(ciphertext, key) {
  const nonce = ciphertext.subarray(0, NONCE_LENGTH);
  const tag = ciphertext.subarray(NONCE_LENGTH, NONCE_LENGTH + TAG_LENGTH);
  const data = ciphertext.subarray(NONCE_LENGTH + TAG_LENGTH);

  const decipher = crypto.createDecipheriv(ALGORITHM, key, nonce, {
    authTagLength: TAG_LENGTH,
  });
  decipher.setAuthTag(tag);

  // Throws if auth tag doesn't match — do not catch this silently
  return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8');
}

// Generate a key (do this once; store it securely)
const key = crypto.randomBytes(KEY_LENGTH);

const message = 'Hello, authenticated encryption.';
const ciphertext = encrypt(message, key);
console.log('Encrypted:', ciphertext.toString('hex'));

const plaintext = decrypt(ciphertext, key);
console.log('Decrypted:', plaintext); // Hello, authenticated encryption.

关于这段代码,有几个值得注意的点:

  • 每次加密调用都生成一个新的noncecrypto.randomBytes 是密码学安全的。不要用计数器替代,除非你理解分布式唯一性问题。
  • nonce和认证标签与密文一起存储 ——它们需要随加密数据一起传输。nonce是公开的;标签是公开的。只有密钥是保密的。
  • decipher.final() 在认证失败时抛出异常 ——这是正确的行为。不要静默捕获并返回部分明文。认证检查失败意味着数据被篡改或密钥错误。
  • 密钥管理是难点crypto.randomBytes(32) 可以给你一个良好的密钥,但你存储和轮换密钥的位置比算法选择更重要。使用密钥管理服务,而不是硬编码常量。

想要交互式测试加密模式吗? IO Tools’ AES加密/解密工具 让你可以在浏览器中使用CBC和GCM模式进行加密和解密——适用于验证互操作性或调试集成。要生成一个密码学安全的256位密钥,请使用 AES密钥生成器.

IV和nonce:真正重要的规则

nonce/IV的误用导致的实际系统漏洞比算法选择更多。规则如下:

  • 始终使用CSPRNG生成nonce/IVcrypto.randomBytes() 在Node中, os.urandom() 在Python中, SecureRandom 在Java中。不是 Math.random()。不是时间戳。不是递增计数器,除非是经过严格唯一性保证的全局计数器。
  • GCM的nonce重复会完全破坏认证 ——使用相同的密钥和nonce时,GCM会暴露认证密钥H。攻击者可以为任意密文伪造认证标签。这不是理论上的:Forbidden攻击就利用了这一点,并已用于实际实现中。
  • CBC的IV重复虽然后果较轻但仍不理想 ——选择明文攻击变得可行。在实践中,每次消息都应生成一个新的IV。
  • 不要从消息内容派生nonce ——依赖可预测数据的确定性nonce会产生可预测的模式。使用随机性。

当CBC或CTR仍然是正确选择时

GCM并非总是最佳选择:

  • 与遗留系统的互操作性 ——如果你正在与仅支持AES-CBC的系统集成,你将使用CBC。明确文档化MAC要求,并正确实现它(使用HMAC-SHA256进行加密后加MAC)。
  • 受限环境且没有GHASH硬件加速 ——GCM的认证步骤使用GHASH,其计算开销比在没有专用硬件加速的设备上直接执行分组密码操作更大。一些嵌入式目标中,如果存在CTR+CMAC但没有GHASH,则可能合理选择CTR模式。
  • 需要支持大文件的流式加密,且需要可寻址的解密 ——CTR的随机访问特性在需要解密位置N而无需读取位置0到N-1时非常有用。GCM理论上支持这一点,但验证认证标签需要先处理整个密文,这违背了该特性。
  • 磁盘加密 ——全盘加密使用XTS模式(本文未涵盖),而不是GCM,因为XTS是为固定大小、随机访问的扇区设计的。GCM用于消息加密,而不是扇区加密。

简要版本

使用 AES-256-GCM 用于新代码。每次加密调用都生成一个新的12字节随机nonce。将nonce和认证标签与密文一起存储。将认证标签验证失败视为错误,而不是警告——当GCM指出数据无效时,绝不要返回明文。

如果你在审计现有的CBC代码:请验证“加密后加MAC”是否正确实现,检查IV是否是随机的(不重复、不递增),并计划在维护窗口允许时迁移到GCM。带有正确HMAC的CBC并非被破坏——它只是比GCM多出更多陷阱。

ECB:只需替换它。不存在任何审计路径能以“ECB在这里没问题”作为结论。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?