为啥带有 GCM 的 AES-256 会在密文大小上增加 16 个字节?

Posted

技术标签:

【中文标题】为啥带有 GCM 的 AES-256 会在密文大小上增加 16 个字节?【英文标题】:Why AES-256 with GCM adds 16 bytes to the ciphertext size?为什么带有 GCM 的 AES-256 会在密文大小上增加 16 个字节? 【发布时间】:2021-07-05 18:51:34 【问题描述】:

我正在使用 Golang 的加密包,特别是 crypto/aes,带有 32 字节密钥(即 AES-256)和 GCM 方法(Galois/Counter 模式)。 我从一个文件中读取多个 16384 字节的块并生成一个密码块、一个 GCM 方法和一个 12 字节的随机 nonce。 然后,我将 nonce 附加到密文中,以便在解密时将它们拆分,以访问 nonce(因为 12 字节的大小是已知的)。

人们会期望生成的密文是 16384 + 12 字节 = 16396;但是,当实际加密时,我得到了 16412 字节的大小,因此添加了 16 个字节。解密每个块后,我得到 16384 的“正常”大小。

这是一个快速的例子。

block, _ := aes.NewCipher([]byte("W9FLKnyv397R82kKuFpfp6y8usGRf49a"))
gcm, _ := cipher.NewGCM(block)
nonce = make([]byte, gcm.nonceSize()) // nonceSize is 12 bytes
_, _ = io.ReadFull(rand.Reader, nonce) // populate nonce with random data


for 
    src := make([]byte, 1024 * 16) // let's hypotise this src is a chunk of a file, full of 1024 * 16 bytes, so 16384

    encryptedBytes := gcm.Seal(nonce, nonce, src, nil) // this prepends the nonce to the src, thus adding 12 bytes in front of the encrypted string

    /*
    Now, encryptedBytes should be 16384 + 12 bytes long, but it is 16384 + 12 + 16.
    If I want to decrypt a chunk of the encrypted bytes, I need to use the size of 16384 + 12 + 16 and this makes it unpractical.
    */

似乎不是因为padding(也是因为GCM不使用padding)。

那么,为什么 AES 会在我的密文中添加 16 个字节?

【问题讨论】:

【参考方案1】:

AES-GCM 提供机密性、完整性和身份验证。要提供最后两个,需要一个身份验证标签。

始终计算 16 字节的标记大小,在您的情况下它是附加的。


更多细节;

    密文大小:这始终等于明文大小,因为 AES-GCM 内部使用 CTR 模式进行加密,不需要填充。

    Nonce/IV 大小:GCM 可以接受较大的 nonce 大小(或较小的),但是建议使用 12 字节,因为它不需要额外的过程。使用 GHASH 处理除 12 字节以外的任何值;

     if len(IV) = 96 then 
         J_0 = IV || 0^311
     else 
         J_0=GHASH_H(IV||0^s+64||len(IV_64))
    

    随机数通常被添加到消息中,这是密文大小的增加。

    标签大小: GCM 始终输出 16 字节的标签大小。可以修整它,但它会降低防伪的安全性。

    标签通常附加在密文后面。

    遵守NIST Special Publication 800-38D (page 8)

    标记的比特长度,表示为 t,是一个安全参数,如附录 B 中所讨论的。通常,t 可以是以下五个值中的任何一个:128、120、112、104 或 96。对于某些应用,t 可以是 64 或 32;附录 C 给出了使用这两个标签长度的指南,包括对输入数据长度和密钥生命周期的要求。

因此你可能会看到类似(Nonce|ciphertext|tag)的输出

【讨论】:

感谢您的帮助解释!请问是否可以读取更大的块(添加 16 字节 + 12)来解密相同的块大小,假设标签始终为 16 字节且随机数始终为 12?另外,另一个问题:AES-GCM 可以在分块加密时添加单个标签和随机数吗?而不是为每个块添加一个随机数和一个标签,在加入它们之后,只需加入块,然后添加一个随机数和一个标签。 首先,必须be very careful when using GCM。你的cmets对我来说不清楚,你能详细说明一下吗?您是否尝试进行流式传输并且记录大小是您的问题?在这种情况下,是some threats that you may consider。另外,ibsodium's secretstream API does this internally. 感谢有关 GCM 的建议,我实际上正在关注所有这些建议。我正在加密大文件(1 到 16 GB),所以我在不同的 goroutine 中进行加密,以 1024 * 16 字节的小块来避免将所有内容加载到内存中,然后我将加密的块写入另一个文件。但是,当解密时,我想使用 1024 * 16 字节的相同块大小进行读取,但显然它增加了 12 + 16 字节,所以这就是我想知道大小的原因。我的意思是:因为所有块都是同一个文件的一部分,只在块中加密,我可以在文件中只放入一个随机数和一个标签吗? 很明显,当你将块大小限制为x时,你也必须考虑IV和标签。通常,您可以继续使用 IV,因此您只需要一个 IV 来存储文件的块。对于你的问题:如果块的顺序改变了怎么办?有什么威胁? 我成功地应用了我的单个 nonce/IV 的“理论”,它工作得很好:我只在文件中添加了一次 nonce,而不是每个块。我也可以为标签做吗?对于您的问题, fi 块的顺序已更改,文件的含义已更改;例如,如果它是一个图像,如果你交换中间块,你可以获得完全不同的像素组合,或者如果你交换第一个块,你将无法获得可读的图像。因此,块顺序至关重要。

以上是关于为啥带有 GCM 的 AES-256 会在密文大小上增加 16 个字节?的主要内容,如果未能解决你的问题,请参考以下文章

项目中使用libsodium扩展

使用 Java 的 AES-256-GCM 解密中的标签不匹配错误

php openssl aes-256-cbc key长度自动匹配了128的长度,为啥

Nodejs AES-256-GCM 通过 webcrypto api 解密加密的客户端消息

有没有办法过滤 aes 256 gcm 加密数据库中的数据?

golang 在golang中使用AES256 GCM加密文本