如何将 AES 模式从一种迁移到另一种?
How to migrate AES mode from one to another?
我有 2 个函数:encrypt/decrypt 由 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'
但是,由于那些encrypt/decrypt功能已经被广泛使用了一段时间,提出了一个问题:如何在不影响原始加密数据的情况下迁移到新模式?
PS:我的草稿思路是这样的逻辑:可以的话用GCM模式解密数据,否则用ECB模式解密;并稍后在 GCM 模式下对所有这些进行加密。但是据我所知,这个想法行不通,因为我没有办法提出错误。 通过这种方式,是否可以知道加密数据的AES模式?
def decrypt(data, key):
try:
# decrypt data by key in GCM
except GCMDecryptFailedError:
# decrypt data by key in ECB
return decrypted_data
这里的错误是直接使用密码算法,没有指定具体的协议,并附带协议版本号。幸运的是,有一些方法可以解决这个问题,指定一个特定的协议。
密码学的一个技巧是不可能随机生成特定内容,因为生成特定值的机会会逐位减少。所以你可以做的是在你的密文前面加上例如一个 128 / 16 字节的位值。对于每条消息,您在密文开头生成此值的机会是 2 的 128 次方(如果消息本身对于特定密钥不是随机的,则更少)。换句话说,猜测 AES-128 密钥的可能性很小;我们称这样的机会为"negligible"。这个技巧当然取决于使用随机密钥的 ECB 加密的输出也是随机的。
但是,将来您可能希望包含一个或多个字节作为协议版本指示符。所以你可以发送例如字节值 01
作为新版本,然后是 16 字节 魔法值 ,然后是 GCM 的随机随机数、密文和 GCM 身份验证标签(如果还没有包含在 GCM 密文中)。一旦你摆脱了协议的 ECB 版本(版本 00
未在你的消息中指明),那么你就可以摆脱魔法和 re-purpose 协议 header 中的 16 个字节您的消息,适用于协议 2 或更高版本。
如果你想生成一个漂亮的魔法,那么你可以使用任何一种 16 字节的字符串,比如 ASCII 格式的 "Protocol 1, GCM:"
(不带引号)。如果您想使用更大的字符串,您也可以使用散列的最左边的 128 位。
所以最初你的逻辑是 pseudo-code:
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.
无论您做什么:在单独的文档中写下您的协议,然后从您的代码中引用它。您可以在源代码中引用您的简单协议作为简单查找。
我有 2 个函数:encrypt/decrypt 由 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'
但是,由于那些encrypt/decrypt功能已经被广泛使用了一段时间,提出了一个问题:如何在不影响原始加密数据的情况下迁移到新模式?
PS:我的草稿思路是这样的逻辑:可以的话用GCM模式解密数据,否则用ECB模式解密;并稍后在 GCM 模式下对所有这些进行加密。但是据我所知,这个想法行不通,因为我没有办法提出错误。 通过这种方式,是否可以知道加密数据的AES模式?
def decrypt(data, key):
try:
# decrypt data by key in GCM
except GCMDecryptFailedError:
# decrypt data by key in ECB
return decrypted_data
这里的错误是直接使用密码算法,没有指定具体的协议,并附带协议版本号。幸运的是,有一些方法可以解决这个问题,指定一个特定的协议。
密码学的一个技巧是不可能随机生成特定内容,因为生成特定值的机会会逐位减少。所以你可以做的是在你的密文前面加上例如一个 128 / 16 字节的位值。对于每条消息,您在密文开头生成此值的机会是 2 的 128 次方(如果消息本身对于特定密钥不是随机的,则更少)。换句话说,猜测 AES-128 密钥的可能性很小;我们称这样的机会为"negligible"。这个技巧当然取决于使用随机密钥的 ECB 加密的输出也是随机的。
但是,将来您可能希望包含一个或多个字节作为协议版本指示符。所以你可以发送例如字节值 01
作为新版本,然后是 16 字节 魔法值 ,然后是 GCM 的随机随机数、密文和 GCM 身份验证标签(如果还没有包含在 GCM 密文中)。一旦你摆脱了协议的 ECB 版本(版本 00
未在你的消息中指明),那么你就可以摆脱魔法和 re-purpose 协议 header 中的 16 个字节您的消息,适用于协议 2 或更高版本。
如果你想生成一个漂亮的魔法,那么你可以使用任何一种 16 字节的字符串,比如 ASCII 格式的 "Protocol 1, GCM:"
(不带引号)。如果您想使用更大的字符串,您也可以使用散列的最左边的 128 位。
所以最初你的逻辑是 pseudo-code:
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.
无论您做什么:在单独的文档中写下您的协议,然后从您的代码中引用它。您可以在源代码中引用您的简单协议作为简单查找。