OpenSSL AES 加密的盐在哪里?

信息安全 加密 密码学 AES openssl
2021-08-12 13:29:18

我有兴趣了解 OpenSSL 如何以及在何处将生成的盐插入 AES 加密数据。为什么?我在 Java 类中加密数据,需要保证我可以使用 OpenSSL 来解密它们。

例如,假设我有这个使用密码生成的加密 base64 字符串"abc"

U2FsdGVkX1+tfvgUkjErP6j2kUAVwWZzNlaAmTqhzTk= 

# generated with "openssl enc -aes-256-cbc -a"

要解密它,我们可以使用:

echo U2FsdGVkX1+tfvgUkjErP6j2kUAVwWZzNlaAmTqhzTk= | openssl enc -d -a -aes-256-cbc -p

# enc -d
#     decryption
# -a 
#     input is base64
# -aes-256-cbc 
#     the aes algorithm used in encryption
# -p 
#     print salt, key and iv params

使用密码运行此"abc"命令将导致:

salt=AD7EF81492312B3F
key=DEC1F5A1E5EAAA7DD539BBCFCEB1BB18868B974186ED056C27046ADD3A752C8B
iv =95A770DE9E0130E77C8E5D796D1B4EF5
Polaco

现在,我们知道 AES 解密数据需要密钥和初始化向量。

在 OpenSSL 的情况下,手册说密钥是从密码短语和盐生成的,初始化向量是从密钥本身派生的(如果没有手动指定)。这意味着生成的数据不需要有 IV,但它确实需要有盐,否则将永远无法正确生成解密密钥。

所以,关键是,盐在哪里以及它是如何插入到结果数据中的?对生成的数据进行一些基本分析(从 base64 解码并输出十六进制值),我们可以看到盐没有预先或附加到结果数据中,但不知何故它就在那里:

# salt: AD7EF81492312B3F
echo U2FsdGVkX1+tfvgUkjErP6j2kUAVwWZzNlaAmTqhzTk= | openssl enc -d -base64 | od -x                                                                                                                                                                                         
0000000 6153 746c 6465 5f5f 7ead 14f8 3192 3f2b
0000020 f6a8 4091 c115 7366 5636 9980 a13a 39cd
0000040

您可以看到盐"AD7E..."并不直接存在于加密数据中。看起来发生了一些转变。

看起来盐是一对一对地切换并插入数据中,从字节 #9 开始。这是一种常见的做法还是只有 OpenSSL 实现的东西?

# salt:                     AD7E F814 9231 2B3F
# switch pair by pair:      7EAD 14F8 3192 3F2B
# data: 6153 746c 6465 5f5f 7ead 14f8 3192 3f2b f6a8 4091 c115 7366 5636 9980 a13a 39cd

编辑

正如 Thomas Pornin 所说,这里的问题是od -x输出原始数据。因为我的电脑是 x86_64,所以数据是小端的,salt 看起来是“交换的”。我忘记了字节序是多么棘手。现在我会永远记得使用od -t x1

无论如何,我仍然想知道在第 9 个字节处插入盐是一种常见做法还是 OpenSSL 特定的实现。我还注意到第一个字节是字符Salted__

2个回答

是的,发生了转变:字节序...

查看字节 8 到 15 7ead 14f8 3192 3f2b:。那是你的盐。这是一个已知的怪癖od:它以 16 位单元little-endian解码数据,然后“以数字方式”显示它们,因此这会导致明显的字节交换。

用于od -t x1获得更好的输出。

编辑:回答您的其他问题,OpenSSL 所做的既不是标准也不是惯例;这只是“OpenSSL 一直在做的事情”。它没有很好的记录。

以前版本的 openssl 使用非常弱的密钥派生过程从密码中派生加密密钥。openssl 1.1.1 版现在支持使用 PBKDF2 和随机生成的 salt 以及 sha256 散列的多次迭代(默认为 10,000 次)进行密钥派生。此答案基于 openssl 版本 1.1.1,使用 PBKDF2 和随机生成的盐和 10,000 次 sha256 迭代从密码中派生密钥(和 iv)。

首先,使用openssl加密一些明文,使用上面描述的密钥推导过程:

echo -n 'this is the plaintext' | openssl aes-256-cbc -e -salt -pbkdf2 -iter 10000 -out ciphertext.enc -p

有关上述 openssl 命令中使用的选项的更多信息,请参阅https://www.openssl.org/docs/man1.1.1/man1/openssl-enc.html-p 选项用于使 openssl 显示 salt、key 和 iv。输入密码后,-p 选项产生:

salt=7EFCC65B38A0ACAA
key=5A81937CD1FBB6A32C2DB9BDB2AAE5CB47D82198ED861C0C1AF6CAA18B21295E
iv =D829763E72F2DFEEBEFAA30E12E29266

xxd 可用于显示 openssl 生成的密文文件中的字节:

xxd ciphertext.enc 

这会产生:

00000000: 5361 6c74 6564 5f5f 7efc c65b 38a0 acaa  Salted__~..[8...
00000010: 5d4d 957f 380e 71ba 4a1c 0913 43ee 5791  ]M..8.q.J...C.W.
00000020: 0c29 c5c8 9f0c 1c0e f8d5 c453 e7c1 b3b6  .).........S....

如您所见,文件的前 8 个字节是字符串 'Salted__' 的 ascii 代码。然后,接下来的八个字节是盐本身,与上面用于加密明文的 openssl 命令中的 -p 选项产生的输出中显示的相同。

以下命令可用于解密密文文件:

openssl aes-256-cbc -d -salt -pbkdf2 -iter 10000 -in ciphertext.enc -p

同样,-p 选项用于显示 salt、key 和 iv。输入密码后,-p 选项显示与之前相同的 salt、key 和 iv。然后,密文被解密,产生原始明文:

salt=0E2FC30932D8371F
key=88DA1F925B6B64A36844FC0EC33A2DBB01B6F72C98AE36602217D00E126AC237
iv =1D5F234D86FA64C208A59FC1BA3AC915
this is the plaintext

对于它的价值,iv 是由 PBKDF2 函数生成的,以及密钥。PBKDF2 函数生成 48 个字节。字节 0-31 是关键。字节 32-47 是 iv。有关更多信息,请参阅https://crypto.stackexchange.com/questions/3298/is-there-a-standard-for-openssl-interoperable-aes-encryption/79855#79855