openssl:通过密码恢复密钥和 IV

信息安全 加密 密码 哈希 AES openssl
2021-08-09 14:57:56

大量文件被加密

    openssl enc -aes-256-cbc -pass pass:MYPASSWORD

Openssl 应该从密码中派生 key+IV。我想知道 MYPASSWORD 的 key+IV 等价物那可能吗?

我知道我的密码。我可以解密然后使用新的已知密钥 + IV 重新加密:

    openssl enc -d -aes-256-cbc -pass pass:MYPASSWORD
    openssl enc -aes-256-cbc -K MYKEY -IV MYIV

但问题是数据量相当大。

4个回答

那里openssl enc描述了命令行选项的用法下面,我将回答你的问题,但不要忘记看一下我的文本的最后一部分,我会看看幕后发生的事情。这是有启发性的。


OpenSSL 使用加盐密钥派生算法。salt是加密时产生的一段随机字节,存放在文件头中;解密后,从标头中检索盐,并根据提供的密码和盐重新计算密钥和 IV

在命令行中,您可以使用-P选项(大写 P)打印 salt、key 和 IV,然后退出。您还可以使用-p(小写的 P)打印盐、密钥和 IV,然后继续进行加密。首先试试这个:

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -P

如果您多次运行此命令,您会注意到每次调用都返回不同的值!这是因为,在没有标志-d的情况下,每次都会进行加密并生成随机盐。由于盐有所不同,因此密钥和 IV 也有所不同。因此,该标志在加密时不是很有用;但是,可以使用该标志。让我们再试一次; 这一次,我们有了要加密的文件让我们运行这个:openssl enc-P-pfoo_clearfoo_enc

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -p -in foo_clear -out foo_enc

此命令将加密文件(从而创建foo_enc打印出如下内容:

salt=A68D6E406A087F05
key=E7C8836AD32C688444E3928F69F046715F8B33AF2E52A6E67A626B586DE8024E
iv=B9F128D827203729BE52A834CC0890B7

这些值是实际用于加密文件的 salt、key 和 IV。

如果我想在之后取回它们,我可以将-Pflag 与 flag 结合使用-d

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -d -P -in foo_enc

每次都会打印与上述相同的盐、密钥和 IV。怎么会这样?那是因为这次我们在解密,所以foo_enc读取了 的头部,并检索了盐。对于给定的盐值,密码到密钥和 IV 的派生是确定性的。

此外,即使文件很长,这种 key-and-IV 检索也很快,因为该-P标志阻止了实际解密;它读取标题,但停在那里。

或者,您可以使用标志指定盐值-S,或使用 完全停用盐-nosalt完全不建议使用无盐加密,因为它可能允许使用预先计算的表加速密码破解(相同的密码总是产生相同的密钥和 IV)。如果您提供盐值,那么您将负责生成适当的盐,即尝试使它们尽可能独特(实际上,您必须随机生成它们)。最好让openssl处理它,因为有足够的空间让静默失败(“静默”意味着“弱且可破解,但代码仍然有效,因此您在测试期间不会检测到问题”)。


OpenSSL 使用的加密格式是非标准的:它是“OpenSSL 所做的”,如果所有版本的 OpenSSL 趋于一致,那么除了 OpenSSL 源代码之外,仍然没有描述这种格式的参考文档。标头格式相当简单:

magic value (8 bytes): the bytes 53 61 6c 74 65 64 5f 5f
salt value (8 bytes)

因此有一个固定的 16 字节标头,以 string 的 ASCII 编码开头Salted__,然后是 salt 本身。就这样!没有加密算法的指示;你应该自己跟踪。

将密码和盐转换为密钥和 IV 的过程没有记录,但源代码显示它调用了 OpenSSL 特定的EVP_BytesToKey()函数,该函数使用自定义密钥派生函数 (KDF),并带有一些重复的散列。这是一个非标准且未经严格审查的构造(!),它依赖于可疑声誉的 MD5 哈希函数(!!);可以在命令行上使用未记录 -md的标志 (!!!) 更改该功能;“迭代计数”由enc命令设置为1,不能更改(!!!!)。这意味着密钥的前 16 个字节将等于MD5(password||salt),仅此而已。

这个太弱了!任何知道如何在 PC 上编写代码的人都可以尝试破解这样的方案,并且将能够每秒“尝试”数千万个潜在密码(使用 GPU 可以实现数亿个密码)。如果您使用openssl enc,请确保您的密码具有很高的熵!(即高于通常推荐的;目标是至少 80 位)。或者,最好根本不使用它;相反,选择更健壮的东西(GnuPG,在对密码进行对称加密时,使用更强大的 KDF 和底层哈希函数的多次迭代)。

你试过这个-P选项吗?

openssl enc -aes-256-cbc -pass pass:MYPASSWORD -P

salt=28C5AD65428E4FD2
key=215202893B8CFEE68E733F69C55AE4C7BED7B2A06533774F3EA0894880E585E6
iv =186DE986FC69F8E47ED692B24D940BED

我想知道那个 MYPASSWORD 的 key+IV 等价物

您只需要复制 中的密钥派生函数EVP_BytesToKey,这相当简单。

如果您不指定-md(或指定-md md5),则使用的 KDF 基于 MD5,但前 16 个字节是使用 PKCS#5 PBKDF1 派生的,迭代计数为1. 然后使用 中指定的密钥派生算法对其进行扩展,EVP_BytesToKey以满足密钥和 IV 的大小要求

-md如果您确实使用并选择了除 MD5 以外的哈希函数(例如)来指定底层哈希函数-md sha256,那么 OpenSSL 将在中指定 KDF EVP_BytesToKey(而不是 PBKDF1)。

Python 3 中的实现(需要passlib):

import hashlib, binascii
from passlib.utils.pbkdf2 import pbkdf1

def hasher(algo, data):
    hashes = {'md5': hashlib.md5, 'sha256': hashlib.sha256,
    'sha512': hashlib.sha512}
    h = hashes[algo]()
    h.update(data)

    return h.digest()

# pwd and salt must be bytes objects
def openssl_kdf(algo, pwd, salt, key_size, iv_size):
    if algo == 'md5':
        temp = pbkdf1(pwd, salt, 1, 16, 'md5')
    else:
        temp = b''

    fd = temp    
    while len(fd) < key_size + iv_size:
        temp = hasher(algo, temp + pwd + salt)
        fd += temp

    key = fd[0:key_size]
    iv = fd[key_size:key_size+iv_size]

    print('salt=' + binascii.hexlify(salt).decode('ascii').upper())
    print('key=' + binascii.hexlify(key).decode('ascii').upper())
    print('iv=' + binascii.hexlify(iv).decode('ascii').upper())

    return key, iv

示例(使用 OpenSSL 0.9.8 和 1.0.1 测试):

openssl_kdf('md5', b'test', b'\xF6\x81\x8C\xAE\x13\x18\x72\xBD', 32, 16)
# generates the same output as:
openssl enc -aes-256-cbc -P -pass pass:test -S F6818CAE131872BD 

openssl_kdf('sha256', b'test', b'\xF6\x81\x8C\xAE\x13\x18\x72\xBD', 32, 16)
# generates the same output as:
openssl enc -aes-256-cbc -P -pass pass:test -S F6818CAE131872BD -md SHA256

在最近的 openssl(也使用 OpenSSL 1.1.0h 2018 年 3 月 27 日测试)中,验证似乎很容易和直接:

    openssl version
OpenSSL 1.0.2k-fips  26 Jan 2017

    echo -n salt1234 | od -A n -t x1 | perl -lpe's,\s+,,g'
73616c7431323334

    openssl enc -e -aes-128-cbc -pass pass:123 -S 73616c7431323334 -P -md sha256
salt=73616C7431323334
key=5A7C52236BAEAE1A92A6B2D1E50C43ED
iv =9F629A34588A4006FE1C7E8FC664B5EC

    echo -n 123salt1234 | openssl dgst -sha256 -binary | hexdump -Cv
00000000  5a 7c 52 23 6b ae ae 1a  92 a6 b2 d1 e5 0c 43 ed  |Z|R#k.........C.|
00000010  9f 62 9a 34 58 8a 40 06  fe 1c 7e 8f c6 64 b5 ec  |.b.4X.@...~..d..|
00000020

如果仔细观察,-P输出与 hexdump 输出相匹配。

结论:enc除了学习和探索之外,不要用于任何其他用途,它只是一个不错的玩具(以防你还不知道)。