这是AES GCM文件加密的良好实践吗?

2024-05-17 08:21:25 发布

您现在位置:Python中文网/ 问答频道 /正文

我用这个加密一个文件,然后用AES-GCM解密一个文件:

(如果尚未安装,请先执行pip install pycryptodome

import Crypto.Random, Crypto.Protocol.KDF, Crypto.Cipher.AES

def cipherAES_GCM(pwd, nonce):
    key = Crypto.Protocol.KDF.PBKDF2(pwd, nonce, count=100_000)
    return Crypto.Cipher.AES.new(key, Crypto.Cipher.AES.MODE_GCM, nonce=nonce)    

# encrypt
plaintext = b'HelloHelloHelloHelloHelloHelloHello'  # in reality, read from a file
key = b'mykey'
nonce = Crypto.Random.new().read(16)
c, tag = cipherAES_GCM(key, nonce).encrypt_and_digest(plaintext)
ciphertext = nonce + tag + c     # write ciphertext to disk as the "encrypted file"

# decrypt
nonce, tag, c = ciphertext[:16], ciphertext[16:32], ciphertext[32:]  # read from the "encrypted file" on disk
plain = cipherAES_GCM(key, nonce).decrypt_and_verify(c, tag).decode()
print(plain)  # HelloHelloHelloHelloHelloHelloHello

这是否被认为是一种良好的加密实践,以及此文件加密实施的潜在弱点是什么?


备注:我有10000个文件要加密。如果每次我加密一个文件时,我调用KDF(使用高count值),这将是非常低效的
更好的解决方案是:只调用KDF一次(使用nonce1),然后对每个文件执行以下操作:

nonce2 = Crypto.Random.new().read(16)
cipher, tag = AES.new(key, AES.MODE_GCM, nonce=nonce2).encrypt_and_digest(plain)

但这是否意味着我必须为每个文件将nonce1 | nonce2 | ciphertext | tag写入磁盘?这会向每个文件添加额外的16字节nonce1


Tags: 文件keynewreadtagrandomcryptononce
2条回答

这似乎很好,但我有一个建议,那就是不要在加密和密钥派生中使用相同的nonce(nonce代表只使用相同nonce一次的密钥,因此如果不想使用另一个nonce(IV),可以将nonce的md5散列传递给加密函数)如果您对更好的安全性感兴趣。这是使用cryptography模块进行加密的示例代码,该模块还具有使用128-bit密钥进行加密的优点,该密钥是安全的,它会处理其余的IV(nonce)、解密和验证(使用HMAC完成)。因此,您上面的所有代码都可以用这几行代码进行总结,从而降低了复杂性,因此可以说是更安全的代码

from cryptography.fernet import Fernet
plaintext = b"hello world"
key = Fernet.generate_key()
ctx = Fernet(key)
ciphertext = ctx.encrypt(plaintext)
print(ciphertext)
decryption = ctx.decrypt(ciphertext)
print(decryption)

编辑:请注意,您使用的nonce也会削弱密钥,因为nonce是用密文发送的,现在用于PBKDF的salt是没有意义的,现在攻击者只需猜测您的密码(假设使用默认计数),在这种情况下,这是非常简单的,暴力强制不需要超过26^5次尝试(总长度为5的小写字母总数)

改进代码的建议是为GCM应用12字节的nonce。目前使用的是一个16字节的nonce,应该对此进行更改,请参见here,和here

GCM的安全性的关键在于没有密钥/非密钥对被多次使用,here。由于在每个加密的代码中都会生成一个随机的nonce,因此可以防止此问题

您的代码也将nonce作为salt应用于密钥派生,这在原则上没有安全问题,因为这不会导致多次使用同一个密钥/nonce对here

然而,这样做的一个缺点可能是盐的长度由nonce长度决定。如果不希望这样做(例如,如果应该使用更大的salt),另一种方法是为每个加密生成随机salt,以通过KDFhere导出密钥和nonce。在这个场景中,连接的数据salt | ciphertext | tag将被传递给接收者。另一种选择是完全分离nonce和密钥生成,并为每个加密生成一个随机nonce和一个用于密钥生成的随机salt。在这种情况下,连接的数据salt | nonce | ciphertext | tag必须传递给接收方。请注意,与nonce和tag一样,salt也不是秘密,因此它可以与密文一起发送

代码应用100000的迭代计数。通常,以下情况适用:迭代计数应尽可能高,以满足您的环境的要求,同时保持可接受的性能,here。如果100000符合您环境的这一标准,那么这就可以了

您使用的连接顺序是nonce | tag | ciphertext。只要双方都知道这一点,这就不是问题。通常按照约定,使用nonce | ciphertext | tag顺序(例如Java隐式地将标记附加到密文中),如果您希望遵守此约定,也可以在代码中使用该顺序

同样重要的是,要使用最新的、维护的库,PyCryptodome就是这样(不同于它的前身,传统的PyCrypto,它根本不应该使用)

编辑:
PyCryptodome的PBKDF2实现默认使用16字节作为生成密钥的长度,对应于AES-128。对于摘要,默认情况下应用HMAC/SHA1。发布的代码使用这些标准参数,没有一个是不安全的,但是如果需要,当然可以更改这些参数。here
注意:尽管SHA1本身是不安全的,但这不适用于上下文 PBKDF2或HMAC的here。然而,为了支持SHA1从生态系统中灭绝,可以使用SHA256


编辑:(关于问题的更新):

编辑问题中呈现的用例是10000个文件的加密。为每个文件执行发布的代码,以便通过KDF生成相应数量的密钥,从而导致相应的性能损失。这被您描述为非常低效。但是,不应忘记,当前代码关注的是安全性,而不是性能。在我的回答中,我指出,例如,迭代计数是一个参数,允许在一定范围内调整性能和安全性

PBKDF(基于密码的密钥派生函数)允许从弱密码派生密钥。为了保证加密的安全性,推导时间会故意增加,这样攻击者破解弱密码的速度就不会比破解强密钥快(理想情况下)。如果派生时间缩短(例如,通过减少迭代次数或多次使用同一密钥),这通常会导致安全性降低。或者简言之,性能提高(通过更快的PBKDF)通常会降低安全性。这将为性能更高(但较弱)的解决方案带来一定的回旋余地

您建议的更有效的解决方案如下:与以前一样,a随机非为每个文件生成ce。但不是用自己的密钥加密每个文件,所有文件都用相同的密钥加密。为此,一次生成一个随机salt,该密钥通过KDF导出。这确实意味着显著的性能提升。但是,这会自动伴随着安全性的降低:如果攻击者成功获得密钥,攻击者可以解密所有文件(而不仅仅是原始场景中的一个)。但是,如果在您的安全要求范围内(这里似乎是这样),此缺点不是强制性的排除标准

性能更高的解决方案要求必须将信息salt | nonce | ciphertext | tag发送给收件人。salt很重要,不能丢失,因为接收者需要salt通过PBKDF派生密钥。一旦接收者确定了密钥,就可以使用标记对密文进行身份验证,并使用nonce对密文进行解密。如果与接收者约定每个文件将使用相同的密钥,则接收者只需通过PBKDF导出一次密钥即可。否则,必须为每个文件派生密钥

如果有16个字节的salt是不需要的(因为在这种方法中,它对于所有文件都是相同的),那么可以考虑替代体系结构。例如,可以使用混合方案:使用公钥基础设施生成和交换随机对称密钥。在这里,所有文件都可以用相同的密钥加密,或者每个文件都可以用自己的密钥加密

但对于设计方案的更具体建议,应更详细地描述用例,例如关于文件:文件有多大?是否需要在流/块中进行处理?或者关于收件人:有多少收件人?什么与收件人一致?等等

相关问题 更多 >