如何将 AES 模式从一种迁移到另一种?
Posted
技术标签:
【中文标题】如何将 AES 模式从一种迁移到另一种?【英文标题】:How to migrate AES mode from one to another? 【发布时间】:2019-02-14 06:16:23 【问题描述】:我有 2 个功能:由 AES 实现的加密/解密
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
DEFAULT_MODE = modes.ECB()
def encrypt(data, key):
encryptor = Cipher(algorithms.AES(key), DEFAULT_MODE, backend=default_backend()).encryptor()
return encryptor.update(data) + encryptor.finalize()
def decrypt(data, key):
if len(data) == 0 or len(data)%16 != 0:
raise ValueError
decryptor = Cipher(algorithms.AES(key), DEFAULT_MODE, backend=default_backend()).decryptor()
return decryptor.update(data)
代码可以在下面执行:
>>> encrypt(b'a'*16, b'I_got_one_key_in_32bytes_length.')
'\xab\x07\x9d\xa0\xf0\xa0g\x9ae\xd9\x10\x9e\xea2\xb4\x17'
>>> decrypt(b'\xab\x07\x9d\xa0\xf0\xa0g\x9ae\xd9\x10\x9e\xea2\xb4\x17', b'I_got_one_key_in_32bytes_length.')
'aaaaaaaaaaaaaaaa'
这里的任务是 change the mode
从 ECB 到其他人,比如 GCM。
DEFAULT_MODE = modes.GCM('iv')
代码仍然可以在下面执行:
>>> encrypt(b'a'*16, b'I_got_one_key_in_32bytes_length.')
'Y5y\xbe\xeeK\xb9\x10\xcdf\x99\xa6\x1d\xf2\xa0\x1e'
>>> decrypt(b'Y5y\xbe\xeeK\xb9\x10\xcdf\x99\xa6\x1d\xf2\xa0\x1e', b'I_got_one_key_in_32bytes_length.')
'aaaaaaaaaaaaaaaa'
但是,由于这些加密/解密功能已经广泛使用了一段时间,因此提出了一个问题:如何迁移到新模式而不影响原始加密数据?
PS:我的草稿思路是这样的逻辑:如果可能,在 GCM 模式下解密数据,否则在 ECB 模式下解密;并稍后在 GCM 模式下加密所有这些。但是 AFAIK,这个想法行不通,因为我无法提出错误。 通过这种方法,是否可以知道加密数据的AES模式?
def decrypt(data, key):
try:
# decrypt data by key in GCM
except GCMDecryptFailedError:
# decrypt data by key in ECB
return decrypted_data
【问题讨论】:
“我无法提出错误”是什么意思? ECB 模式就是这种情况,但 GCM 模式解密实际上可能会失败(因为标签不匹配)。我认为您概述的方法会很好用! @Luke Joshua Park 谢谢,这是有经验的人的肯定。我试了一下,它奏效了。我的旧加密函数需要返回一个标签,而解密函数需要一个新的标签参数。您可以在下面回答这个问题(对于没有标签或类似标识符的其他模式可能有更多详细信息)。虽然这对你来说似乎是一个极其简单的领域知识,但你确实解决了我的问题。 为您的加密数据添加一个引人注目的版本。 @EbbeM.Pedersen 什么是引人注目的?我应该将其与我在答案中指定的“大魔法值”等同起来吗? 【参考方案1】:这里的错误是直接使用加密算法,没有指定特定的协议,以及随之而来的协议版本号。幸运的是,有一些方法可以摆脱这种情况,并且指定一个特定的协议。
密码学使用的技巧之一是不可能随机生成特定内容,因为生成特定值的机会会减少每一位。所以你可以做的是在你的密文前面加上例如一个 128 / 16 字节的位值。对于每条消息,您在密文开头生成此值的机会是 2 的 128 次方(如果消息本身对于特定密钥不是随机的,则更少)。换句话说,机会就像猜测 AES-128 密钥一样低;我们称这种机会为“微不足道”。这个技巧当然取决于使用随机密钥的 ECB 加密的输出也是随机的。
但是,将来您可能希望包含一个或多个字节作为协议版本指示符。所以你可以发送例如字节值01
作为新版本,然后是 16 字节 magic value,然后是 GCM 的随机 nonce、密文和 GCM 身份验证标签(如果尚未包含在 GCM 密文中)。一旦您摆脱了协议的 ECB 版本(版本00
未在您的消息中指明),那么您就可以摆脱魔法并将消息协议标头中的 16 个字节重新用于协议 2 或更高版本.
如果你想生成一个漂亮的魔法,那么你可以使用任何类型的 16 字节字符串,比如 ASCII 格式的 "Protocol 1, GCM:"
(不带引号)。如果您想使用更大的字符串,也可以使用哈希的最左边 128 位。
所以最初你的逻辑是伪代码:
versionByte = message[0]
if message.length >= 17 && versionByte == 01h then
magic = message[1- 16]
if magic == "Protocol 1, GCM:" then
gcmDecrypt(message, 17)
else
ecbDecrypt(message, 0)
// --- include other versions here ---
else
ecbDecrypt(message, 0)
当然,这仍然是一个非常基本的协议。但至少你以后可以改变它。您可能想查看更完整的协议规范,例如 Fernet、CMS 或 - 当然 - TLS 而不仅仅是 AES-GCM。
无论您做什么:在单独的文档中写下您的协议,然后从您的代码中引用它。您可以在源代码中引用您的简单协议,以便查找。
【讨论】:
当然,伪代码可以使用表、switch语句、可配置协议、设置为要处理的版本号的变量字段等等来改进。 这听起来很合理。我还实现了一个原型来验证这个想法。我还有 2 个问题:不提供versionByte
是否有任何风险?在 GCM 中不使用tag
有什么风险吗?
可以省略版本字节并将其编码为错误的yes,但这会使后续协议变得更加困难。注意非常好地测试随机事物;边缘情况可能会伤害你百万次。为 AES-GCM 使用不同的密钥。身份验证标签是 GCM 的组成部分,是 GMAC 的结果,所以我不确定你知道你在这里问什么。以上是关于如何将 AES 模式从一种迁移到另一种?的主要内容,如果未能解决你的问题,请参考以下文章
Oracle PL/SQL 和 Shell 脚本:从一种模式到另一种模式