AES/CBC和AES/ECB加密后的数据大小

80

我想知道AES加密后数据的大小,以便我可以避免对我的AES后数据(在磁盘或内存中)进行缓冲,主要是为了知道大小。

我使用128位AES、javax.crypto.Cipherjavax.crypto.CipherInputStream 进行加密。

通过对各种输入大小进行一些测试,发现以下计算得出的加密后大小是正确的:

long size = input_Size_In_Bytes; 
long post_AES_Size = size + (16 - (size % 16));

但我不确定上面的公式是否适用于所有可能的输入大小。

有没有一种方法可以在加密数据之前预先计算出其大小 - 而无需缓冲加密后的数据(在磁盘或内存中)以了解其加密后的大小?

9个回答

113

AES的块长度固定为16字节,无论密钥长度如何。假设你使用PKCS 5/7填充,请使用以下公式:

 cipherLen = clearLen + 16 - (clearLen mod 16)
请注意,如果明文长度是块大小的倍数,那么需要整个新块进行填充。例如,如果您的明文是16字节,则密文将占用32字节。

您可能想要将IV(初始向量)与密文一起存储。在这种情况下,您需要为IV再添加16个字节。


9
为什么需要为一个明文块添加一个全新的块,它已经是16字节的倍数了呢?谢谢。 - Durin
7
没有至少一个比特的填充,该块没有终点。 - Shane Chin
9
@Durin的问题很好。原因是没有区分明文例如10 0F 0E ... 02 01和填充后的明文10 0F 0E ... 02 + 填充 01 的方法。这就是为什么总是进行填充的原因。如果明文大小已知“out of band”,或者已知该值不以00结尾(例如ASCII字符串),有时会使用零填充到块大小。 - Maarten Bodewes
4
用零填充和用空格填充不是标准化的模式。Bouncy总是进行填充,即使是用零。PHP则不然。无论是用零还是用空格填充都不是确定性填充模式。它们能够工作的原因是明文(以字节为单位)具有已知的长度,或者明文具有预定的格式(例如只包含可打印的ASCII字符)。但是,如果这些条件不满足,零填充可能会失败,比如说UTF16LE文本以“00”结尾(这很可能发生)。换句话说,这些填充模式存在,但它们对输入施加了一定的限制。 - Maarten Bodewes
3
不,那是因为答案确实是错误的。这只是一种快速计算上限的方法。当然,对于Java,你现在可以直接查询你的Cipher实例以获取正确的长度。在stackoverflow上,赞数几乎没有任何作用。 - Maarten Bodewes
显示剩余3条评论

36

AES作为块密码,不会改变大小。输入大小始终等于输出大小。

但是,作为块密码的AES需要输入大小为块大小(16个字节)的倍数。因此,会使用填充方案,如流行的PKCS5。因此,你加密数据的大小取决于所使用的填充方案。但与此同时,所有众所周知的填充方案都会将大小舍入到下一个模块16的大小(因为AES具有16个字节的块大小)。


2
有一些填充方案不需要改变数据大小。 - usr
6
不需要改变数据大小的工作模式是存在的(尽管通常需要 IV 和/或身份验证标记作为开销)。填充模式的定义是使密码输入数据更大。 - Maarten Bodewes

10

这取决于您使用AES的模式。对于大多数块状模式(如ECB和CBC),您提供的信息是准确的。但是,在CFB模式下(例如),您基本上只是使用AES生成一个字节流,并将其与输入的字节进行XOR运算。在这种情况下,输出的大小可以保持与输入的大小相同,而不必像您所提供的那样舍入到下一个块大小。


5

一般来说,对于分块加密:

密文 = 明文 + 块大小 - (明文长度 MOD 块大小)

密文的大小计算为将明文扩展到下一个块的大小。如果使用填充并且明文的大小恰好是块大小的整数倍,则会添加一个包含填充信息的额外块。

AES使用16字节的块大小,因此产生的加密结果如下:

密文 = 明文 + 16 - (明文长度 MOD 16)

来源: http://www.obviex.com/articles/CiphertextSize.pdf

注意:

  1. 密文和明文分别表示密文大小和明文大小。

4

AES密码始终在16字节(128位)块上运行。如果输入字节数不是16的倍数,则会进行填充。这就是为什么在您的计算中出现了16这个“神奇数字”的原因。您所拥有的应该适用于所有输入大小。


请注意,即使输入长度是16的倍数,至少也会添加一个填充字节。 - Jeff G

1
AES工作在128位(16字节)块中,并将明文块转换为相同长度的密文块。如果最后一个块短于16个字节,则会填充该块,因此您的公式是正确的。

0

如果您的输入长度小于int的最大大小,您可以使用{{link1:Cipher.getOutputSize(int)}}。


0
long post_AES_Size = size + (16 - (size % 16));

cipherLen = (clearLen/16 + 1) * 16

@zz-coder和@OP提到的是一样的。

int(clearLen/16) + 1) * 16
= ((clearLen - clearLen % 16) / 16 + 1) * 16
= clearLen - clearLen % 16 + 16;
= clearLen + (16  - clearLen % 16)

-1

有一些存储加密信息的方法可以避免需要填充,只要数据大小至少等于块大小。一个小困难是,如果允许数据大小小于块大小,并且必须能够重构数据的精确大小,即使对于小块,输出也必须比输入大至少一个比特位,无论数据大小如何。

要理解这个问题,请意识到,长度为N字节的可能文件有256^N个,不超过N字节长的可能文件数是256^N加上不超过N-1字节长的可能文件数(有一个可能的零字节长文件和257个不超过一个字节长的可能文件)。

如果块大小为16字节,则最多有256 ^ 16 + 256 ^ 14 + 256 ^ 13等可能的输入文件长度不超过16字节,但只有256 ^ 16个可能的输出文件长度不超过16字节(因为输出文件不能短于16字节)。因此,至少一些可能的16字节输入文件必须增长。假设它们将变成17字节。有256 ^ 17个可能的十七字节输出文件;如果其中任何一个用于处理16字节或更少的输入,则没有足够的可用来处理所有可能的十七字节输入文件。无论输入可以变得多大,都必须增加某些该大小或更大的文件。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接