I have 2 functions: encrypt/decrypt implemented by 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)
The code can be executed in below:
>>> 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'
The mission here is to change the mode
from ECB to others, say GCM.
DEFAULT_MODE = modes.GCM('iv')
The code can still be executed in below:
>>> 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'
However, since those encrypt/decrypt function have been used broadly for a while, an issue is raised: How to migrate to new mode without impacting original encrypted data?
PS: my draft thought is logic like below: decrypting data in GCM mode if possible, otherwise decrypting in ECB mode; and encrypting all of them in GCM mode later. But AFAIK, this idea won't work since I have no way to raise the Error. With this approach, is that possible to know the AES mode of encrypted data?
def decrypt(data, key):
try:
# decrypt data by key in GCM
except GCMDecryptFailedError:
# decrypt data by key in ECB
return decrypted_data
The error here is to use a cryptographic algorithm directly, without specifying a specific protocol, and a protocol version number that comes with that. Fortunately, there are ways of getting out of this, and specify a specific protocol.
One of the tricks employed by cryptography is that it is impossible to generate specific content randomly simply because the chance of generating the a specific value reduces by each bit. So what you can do is to prefix your ciphertext with e.g. a 128 / 16 byte bit value. The chance that you generate this value at the start of your ciphertext is 2 to the power of 128 for each message (less if the messages are not random themselves for a specific key). In other words, the chance is as low as guessing an AES-128 key; we call such a chance "negligible". This trick of course depends on the output of ECB encryption using a random key to be random as well.
However, for the future you might want to include one or more bytes as protocol version indicator. So you could send e.g. the byte value 01
as new version, and then the 16 byte magic value, followed by the random nonce for GCM, the ciphertext and the GCM authentication tag (if that's not already included in the GCM ciphertext). Once you got rid of the ECB version of your protocol (version 00
not indicated in your messages) then you can get rid of the magic and re-purpose the 16 bytes in the protocol header of your messages, for protocol 2 or higher.
If you want to generate a nice magic then you could use any kind of 16 byte string, say "Protocol 1, GCM:"
(without the quotes) in ASCII. You could also use the leftmost 128 bits of a hash if you want to use a larger string.
So initially your logic would be, in 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)
Of course this is still a very basic protocol. But at least you can change it later on. You might want to look at more complete protocol specs, such as Fernet, CMS or - of course - TLS instead of just AES-GCM.
Whatever you do: write down your protocol in a separate document and refer to it from your code. You may quote your simple protocol in the source code as easy lookup.