为什么 openssl enc -aes-256-cbc -a -salt 会增加文件大小?

信息安全 openssl AES
2021-09-10 18:21:47

我正在使用openssl enc -aes-256-cbc -a -saltAmazon Glacier 的自动差异备份。但我注意到使用此命令几乎可以完美地将文件大小增加 35%。
据我了解,分组密码不应该改变文件大小这么多,以我目前的知识,我知道它最多会在末尾添加 16 个字节来创建填充。但这并不占我备份的 17MB+。

是什么导致了这种尺寸的增加?

日志行:

09:09:16 Created tarbal of 165 files of size            106M
09:09:50 Created /archief/2014-05-10.encrypted of size  143M

09:09:11 Created tarbal of 186 files of size            132M
09:09:52 Created /archief/2014-05-17.encrypted of size  179M
1个回答

主要增加的是-a标志,这意味着它base64对您的密文进行编码。

来自man enc

NAME
   enc - symmetric cipher routines

SYNOPSIS
   openssl enc -ciphername [-in filename] [-out filename] [-pass arg] [-e] [-d] [-a] [-A] [-k password]
   [-kfile filename] [-K key] [-iv IV] [-p] [-P] [-bufsize number] [-nopad] [-debug]

   [...]

   -a  base64 process the data. This means that if encryption is taking place the data is base64 encoded
       after encryption. If decryption is set then the input data is base64 decoded before being
       decrypted.

Base64 编码意味着对于每三个字节的二进制数据(一个字节是一个 8 位数字,意味着它的值是 0 到 2 8 -1=255),您已经被编码为四个字节的 6 位数据(值为 0 到2 6 -1=63,虽然用可打印的 ASCII 符号表示)。Base64 很方便,因为可以将 64 个值的符号选择为可打印的 ASCII 字符(例如,通常 0='A',1='B',...25='Z',26='a',. ..51='z',52='0',...,61='9',62='+',63='/' 虽然最后两个通常在不同的变体中定义不同)。注意三个字节 8*3 有 24 位,四组 base64 编码数字 6*4 也是如此。

例如,如果您的密文是三个字节(十六进制):f0 bb 5c(240, 187, 92) 二进制,则分组为三个字节的位将是:

 11110000 10111011 01011100

在 base64 中,它将是相同的位,除了分为四组,每组 6 位:

 111100 001011 101101 011100

它映射到值 60、11、45、28,在典型的 base64 表上将映射到可打印的 ASCII 字符8Ltc,这将在磁盘上占用四个字节(而不是在没有 base64 编码的情况下占用的三个字节)。

因此 base64 编码应该占大约 33% 的文件增加。它比这略多,因为 openssl 还每 64 个 base64 编码的 ascii 字符添加一个换行符(因此文本以 64 个字节换行)。这两个特征加在一起占了一般文件大小增加的 (4/3 * 65/64 - 1) = 35.4%

您的方案也有一些开销。指定-salt获取您的明文密码,并将随机的八字节盐与消息连接起来,以及Salted__指定使用盐的标头,这些也将采用 base64 编码。(盐的目的是降低攻击者为常见密码预先计算彩虹表的成本效益)。如果我在您的方案中加密了一个随机文件(将盐指定为DEADBEEFDEADBEEF使用openssl enc -aes-256-cbc -a -salt -S DEADBEEFDEADBEEF我的加密文件的第一行是

U2FsdGVkX1/erb7v3q2+7ybJfdPaLlVzOp7lKpOljvNK8ONCrgFrQpaJHQ8EqO1X

解码为(使用python):

>>> import base64
>>> base64.b64decode("U2FsdGVkX1/erb7v3q2+7ybJfdPaLlVzOp7lKpOljvNK8ONCrgFrQpaJHQ8EqO1X")
'Salted__\xde\xad\xbe\xef\xde\xad\xbe\xef&\xc9}\xd3\xda.Us:\x9e\xe5*\x93\xa5\x8e\xf3J\xf0\xe3B\xae\x01kB\x96\x89\x1d\x0f\x04\xa8\xedW'

因此,结合 base64 编码、换行符、salt、初始化向量(对于 CBC 模式)和填充(对于 AES 可以整除为 128 位块),大约 35% 的开销似乎是完全合理的。

编辑:实际上,openssl在从带盐的密码中获取密钥时,不存储初始化向量。来自man enc:“当使用其他选项之一指定密码时,IV 是从此密码生成的。”。使用它并做几个测试文件,文件大小完美匹配。salt 8 个字节加上Salted__16 个字节到文件中。该文件被填充为 16 个字节的倍数(最多添加 16 个字节)。如果您没有对文件大小进行 base64 编码,则文件大小完全匹配,然后应用base64 --wrap=64.