我用这个加密一个文件,然后用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
这似乎很好,但我有一个建议,那就是不要在加密和密钥派生中使用相同的nonce(nonce代表只使用相同nonce一次的密钥,因此如果不想使用另一个nonce(IV),可以将nonce的
md5
散列传递给加密函数)如果您对更好的安全性感兴趣。这是使用cryptography
模块进行加密的示例代码,该模块还具有使用128-bit
密钥进行加密的优点,该密钥是安全的,它会处理其余的IV
(nonce)、解密和验证(使用HMAC
完成)。因此,您上面的所有代码都可以用这几行代码进行总结,从而降低了复杂性,因此可以说是更安全的代码编辑:请注意,您使用的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是不需要的(因为在这种方法中,它对于所有文件都是相同的),那么可以考虑替代体系结构。例如,可以使用混合方案:使用公钥基础设施生成和交换随机对称密钥。在这里,所有文件都可以用相同的密钥加密,或者每个文件都可以用自己的密钥加密
但对于设计方案的更具体建议,应更详细地描述用例,例如关于文件:文件有多大?是否需要在流/块中进行处理?或者关于收件人:有多少收件人?什么与收件人一致?等等
相关问题 更多 >
编程相关推荐