OpenSSL AES 算法中 Key 和 IV 是如何生成的?
OpenSSL AES 算法中 Key 和 IV 是如何生成的? - 简书
书接上回。在《LDAP 密码加密方式初探》一文中,使用 OpenSSL 命令 AES 算法加密解密时,都用到了 Key 和 IV 参数,那么这两个参数是如何生成的呢?
仍然以 AES-256-CBC 开始探索。先准备好生成 Key 和 IV 的 passphrase:
$ echo -n "drjom(&)(&)MOJRD" > passphrase
上述回文形式的 passphrase 来自一个神秘的组织:)
将此 passphrase 传入 openssl 命令生成对应的 Key 和 IV:
$ openssl enc -aes-256-cbc -kfile passphrase -md md5 -P -salt
salt=51D9C4B24C759179
key=BBF4EA0E7A0EBD7C60CCE2024E218A53BBB69CCA65B4D0B705E37080676E5F5D
iv =8E5EC1AC2191167DF9B753BA93A1E7B8
其中的 salt 是随机生成的,因此每次执行的结果并不相同。而 Key 和 IV 的生成方法参考 SuperUser 的一个回答补充及验证如下(注意 md5sum 输出的结果与上述 openssl 命令的输出结果比较):
$ perl -e 'print pack "H", "51D9C4B24C759179"' > salt
$ cat passphrase salt > hash1_128.tmp
$ md5sum hash1_128.tmp
bbf4ea0e7a0ebd7c60cce2024e218a53 hash1_128.tmp
$ perl -e 'print pack "H", "bbf4ea0e7a0ebd7c60cce2024e218a53"' > hash1_128
$ cat hash1_128 passphrase salt > hash2_128.tmp
$ md5sum hash2_128.tmp
bbb69cca65b4d0b705e37080676e5f5d hash2_128.tmp
$ perl -e 'print pack "H*", "bbb69cca65b4d0b705e37080676e5f5d"' > hash2_128
$ cat hash2_128 passphrase salt > hash3_128.tmp
$ md5sum hash3_128.tmp
8e5ec1ac2191167df9b753ba93a1e7b8 hash3_128.tmp
可以看出,对于 AES-256-CBC 来说:
hash1_128 = MD5(Passphrase + Salt)
hash2_128 = MD5(hash1_128 + Passphrase + Salt)
hash3_128 = MD5(hash2_128 + Passphrase + Salt)
Key = hash1_128 + hash2_128
IV = hash3_128
Key 和 IV 分别就是 AES-256-CBC 的 Key 和 IV。
当没有 salt 时,上述过程仍然成立。先使用 openssl 命令带 -nosalt 选项生成 Key 和 IV:
$ openssl enc -aes-256-cbc -kfile passphrase -md md5 -P -nosalt
key=D5E483D8B90C02BD4D470BA8049E1FA61D64EB2BFA444CBF9853CDFB8B24DA7A
iv =304E9E87DB9C1C8101F605ED4DD0B9EB
分步验证如下(注意 md5sum 输出的结果与上述 openssl 命令的输出结果比较):
$ md5sum passphrase
d5e483d8b90c02bd4d470ba8049e1fa6 passphrase
$ perl -e 'print pack "H", "d5e483d8b90c02bd4d470ba8049e1fa6"' > hash1_128
$ cat hash1_128 passphrase > hash2_128.tmp
$ md5sum hash2_128.tmp
1d64eb2bfa444cbf9853cdfb8b24da7a hash2_128.tmp
$ perl -e 'print pack "H", "1d64eb2bfa444cbf9853cdfb8b24da7a"' > hash2_128
$ cat hash2_128 passphrase > hash3_128.tmp
$ md5sum hash3_128.tmp
304e9e87db9c1c8101f605ed4dd0b9eb hash3_128.tmp
也就是说:
hash1_128 = MD5(Passphrase)
hash2_128 = MD5(hash1_128 + Passphrase)
hash3_128 = MD5(hash2_128 + Passphrase)
Key = hash1_128 + hash2_128
IV = hash3_128
在此基础上,看看 AES-128-CBC 生成的 Key 和 IV 是什么样子的:
$ openssl enc -aes-128-cbc -kfile passphrase -md md5 -P -nosalt
key=D5E483D8B90C02BD4D470BA8049E1FA6
iv =1D64EB2BFA444CBF9853CDFB8B24DA7A
对比 AES-256-CBC 可以看出,AES-128-CBC 的 Key 和 IV 生成方法进一步简化(以下为没有 salt 时的情况):
hash1_128 = MD5(Passphrase)
hash2_128 = MD5(hash1_128 + Passphrase)
Key = hash1_128
IV = hash2_128
在上述验证过程中使用到 openssl 命令时,都用 -md 选项将生成 Key 和 IV 的 hash 函数指定为 md5。那么假如不指定的话,默认的 hash 函数是什么呢?
由此问答可知,从 1.1 版本开始,默认的 hash 函数由 MD5 变为 SHA256(可使用 openssl version 命令查看当前版本号),另外也可以通过修改 /etc/ssl/openssl.cnf 配置文件中的 default_md 字段指定默认的 hash 函数。
要知道,MD5 生成的 hash 是 128bit 的,而 SHA256 生成的 hash 是 256bit 的,上述 256bit Key 生成时的拼接操作是否有必要呢?
继续验证,仍然回到 AES-256-CBC 并使用 SHA256 作为 hash 函数:
$ openssl enc -aes-256-cbc -kfile passphrase -md sha256 -P -nosalt
key=53A8968B0F53CAA2D21F2694B19EDD0676AF034D4D570651B3689C7827EC84C2
iv =ED889267E14BA02167ED96E226153158
分步看看:
$ sha256sum passphrase
53a8968b0f53caa2d21f2694b19edd0676af034d4d570651b3689c7827ec84c2 passphrase
$ perl -e 'print pack "H*", "53a8968b0f53caa2d21f2694b19edd0676af034d4d570651b3689c7827ec84c2"' > hash1_256
$ cat hash1_256 passphrase > hash2_256.tmp
$ sha256sum hash2_256.tmp
ed889267e14ba02167ed96e226153158373dbeff2b1177c12906ab786dd1ebd8 hash2_256.tmp
可以看到,对 passphrase 做一次 SHA256 运算就已经是 256bit Key 了,对 Key 和 passphrase 拼接后再次做 SHA256 运算,截取前 128bit 作为 IV 的值。也就是说:
hash1_256 = SHA256(Passphrase)
hash2_256 = SHA256(hash1_256 + Passphrase)
Key = hash1_256
IV = First128bit(hash2_256)
再看看 AES-128-CBC 的情况:
$ openssl enc -aes-128-cbc -kfile passphrase -md sha256 -P -nosalt
key=53A8968B0F53CAA2D21F2694B19EDD06
iv =76AF034D4D570651B3689C7827EC84C2
对 passphrase 做一次 SHA256 运算之后,前 128bit 作为 AES-128-CBC 的 Key 值,后 128bit 作为其 IV 值。写成等式是:
hash1_256 = SHA256(Passphrase)
Key = First128bit(hash1_256)
IV = Second128bit(hash1_256)
至此,可以看出 AES 算法 Key 和 IV 的生成规律了:将 hash 结果(第一次 hash 运算时为空)、passphrase 和 salt(nosalt 时为空)拼接后循环做 hash 运算,再根据 AES 所需的 Key 和 IV 的 bit 数取值。
更进一步的,从上述生成过程可见,只要生成了足够 bit 位的值,hash 运算就停止了,这称为一个迭代,这正是 OpenSSL 为人所诟病的不足。而 GnuPG 使用了多次迭代。
小结
OpenSSL AES 算法使用的 Key 和 IV 生成规律:将 hash 结果(第一次 hash 运算时为空)、passphrase 和 salt(nosalt 时为空)拼接后循环做 hash 运算,再根据 AES 所需的 Key 和 IV 的 bit 数取值。
默认的 hash 函数,从 OpenSSL 1.1 开始由 MD5 变为 SHA256。
可以通过 /etc/ssl/openssl.cnf 的 default_md 字段修改默认的 hash 函数。
OpenSSL AES 生成 Key 和 IV 时只做一次迭代,GnuPG 使用多次迭代。
以上。
作者:yestyle
链接:https://www.jianshu.com/p/813e184b56bd
來源:简书
简书著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。
LDAP 验证算法
可以看到 Key 为 32 字节(256-bit),IV 为 16 字节。由 mcrypt(3) 得知,需要 IV 的只有 CBC 模式和 CFB 模式,而从明文「admin」长度 5 字节、密文长度 32 字节、Key 长度 32 字节这点来判断,使用的应该是 CBC 模式(CBC 模式的密文长度与 Key 长度相等,而 CFB 模式的密文长度与明文长度相等)。也就是说,具体加密算法应该是 AES-256-CBC。
将上述的 Key 和 IV 拼接后传入 openssl 命令验证一下(openssl 命令行使用说明详见 OpenSSL Wiki 页面):
$ printf admin | openssl enc -aes-256-cbc -K d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16 -iv 463ea9701fb5aa7a0fa659c162ef0af6 | hexdump -C
00000000 e2 be 20 f0 ea 2f 68 34 62 8a 28 d6 94 40 b9 01 |.. ../h4b.(..@..|
Bingo!对「admin」使用 AES-256-CBC 加密得到的值正是对抓包看到的 4r4g8OovaDRiiijWlEC5AQ== 进行 base64 解码之后的值: e2be20f0ea2f6834628a28d69440b901。
把整个加密过程串起来:
$ printf admin | openssl enc -aes-256-cbc -K d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16 -iv 463ea9701fb5aa7a0fa659c162ef0af6 | base64
4r4g8OovaDRiiijWlEC5AQ==
要知道,AES 是对称加密算法,也就是说,知道了 Key 和 IV,是可以将密文解密成明文的:
$ printf 4r4g8OovaDRiiijWlEC5AQ== | base64 -d | openssl enc -d -aes-256-cbc -K d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16 -iv 463ea9701fb5aa7a0fa659c162ef0af6
admin
最后,把加密和解密的过程写成一个简单的脚本,命名为 ldap-password.sh:
#!/bin/sh
KEY=d8d51948eaae7484e1155ed054f7699b7700c43578bb996220b9ed7ff9766d16
IV=463ea9701fb5aa7a0fa659c162ef0af6
help() {
echo "Encryption: $0 enc [PASSWORD_IN_PLAIN_TEXT]"
echo "Decryption: $0 dec [PASSWORD_IN_CIPHER]"
}
if [ $# -ne "2" ]
then
help
elif [ "$1" == "enc" ]
then
printf $2 | openssl enc -aes-256-cbc -K $KEY -iv $IV | base64
elif [ "$1" == "dec" ]
then
printf $2 | base64 -d | openssl enc -d -aes-256-cbc -K $KEY -iv $IV
printf "\n"
else
help
fi
这样加解密就方便多了:
$ ./ldap-password.sh enc admin
4r4g8OovaDRiiijWlEC5AQ==
$ ./ldap-password.sh dec 4r4g8OovaDRiiijWlEC5AQ==
admin
小结
登录时与服务器的传输是使用 HTTP 明文传输的。
密码字段是加密的,先对密码的明文使用 AES-256-CBC 加密,再使用 base64 编码生成密文。
使用 AES 加密算法时的初始化向量(IV)和密钥(Key)是硬编码的值,安全性大大折扣。