Advanced Encryption Standard (AES) is a powerful and trustworthy cryptographic encryption tool to secure digital data. It accepts three key lengths – 128, 192, and 256 bits (16, 24 and 32 bytes, respectively).
AES cipher is abbreviated using the key length. For example, an AES cipher using a 256-bit key is abbreviated as AES 256. The longer the key, the more secure the system. Therefore, the rule of thumb is to use a 256-bit key.
AES is a symmetric encryption, meaning the same key (password or passphrase) is used for encrypting and decrypting data.
The AES algorithm works in 3 main steps:
- Step 1: Generate the key – a secret passphrase to encrypt or decrypt data. This should be kept safe because anyone with this key can decrypt your data.
- Step 2: Generate a cipher – an algorithm is used to perform encryption and decryption. There are several modes to choose from. We will cover the implementation of a few of them.
- Step 3: Encrypt or decrypt data – AES encrypts and decrypts data in 16-bytes (128 bits) blocks.
There are different modes you can use to generate cipher text in Step 2: They include:
Mode of Operation | Abbreviation | Summary |
Electronic Code Book | AES-ECB | Is often regarded as the easiest mode to implement, but it is the least secure. |
Cipher Block Chaining | AES-CBC | Is a mode that uses the output of the previous block as input to the current block. |
Cipher FeedBack | AES-CFB | Is a mode that allows for the encryption of individual bits or bytes. |
Output FeedBack | AES-OFB | Is a mode that converts the block cipher into a stream cipher. |
Counter | AES-CTR | Is a mode that uses a counter to generate the keystream for encryption. |
Galois Counter Mode | AES-GCM | Is a mode that combines AES-CTR mode with authentication to provide security. |
We will implement AES-ECB, AES-CBC, and AES-GCM in Python using the pycryptodome library. You can install the package using pip by running the command:
pip install pycryptodome
Note: pycryptodome is a fork of pycrypto. The latter is no longer maintained; therefore, install the former.
AES-ECB Encryption and Decryption in Python
ECB is the simplest and the least secure AES encryption algorithm. Each 16-byte (128 bits) block of plaintext is encrypted independently in this mode.
In this case, padding is needed to fit the data into the 16-byte blocks. Let’s see an example.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes def encrypt(plaintext, key): # Create an AES cipher object with the key and AES.MODE_ECB mode cipher = AES.new(key, AES.MODE_ECB) # Pad the plaintext and encrypt it ciphertext = cipher.encrypt(pad(plaintext, AES.block_size)) return ciphertext def decrypt(ciphertext, key): # Create an AES cipher object with the key and AES.MODE_ECB mode cipher = AES.new(key, AES.MODE_ECB) # Decrypt the ciphertext and remove the padding decrypted_data = unpad(cipher.decrypt(ciphertext), AES.block_size) return decrypted_data # Example usage plaintext = b"This is the message to be encrypted" # Generate a random 256-bit (32-byte) key # Key-length accepted: 16, 24, and 32 bytes. key = get_random_bytes(32) # Generating keys/passphrase print("Key:", key.hex()) # Encryption encrypted_data = encrypt(plaintext, key) print("Encrypted data:", encrypted_data) # Decryption decrypted_data = decrypt(encrypted_data, key) print("Decrypted data:", decrypted_data) |
Output:
Key: da131765104d989f1604621d4c5c383b547d46e9542bb000f3f5c6bc46858bd4 Encrypted data: b"\xdc,\xe2\xd5\x1a\x15\xb7\x15\xd1\xfd\\<;<\xfe\xcd\xefbII'\xcb\xfe\x00\xfb\xac\xfcO\xfc\x8a\xb325\x8b\x91I\xea\xf4\xc9\xbd\xc7\xfcw,Z!\x1bT" Decrypted data: b'This is the message to be encrypted'
Explanation:
The encrypt function takes the plaintext and the key as inputs. It creates an AES cipher object using AES.new with the key and AES.MODE_ECB mode. The plaintext is padded using pad from Crypto.Util.Padding to ensure its length is a multiple of the AES block size (32). The padded plaintext is then encrypted using the cipher object, resulting in the ciphertext.
The decrypt function takes the ciphertext and the key as inputs. It creates an AES cipher object using AES.new with the key and AES.MODE_ECB mode. The ciphertext is decrypted using the cipher object. The padding is removed from the decrypted data using unpad from Crypto.Util.Padding, resulting in the original plaintext.
Implementation of AES-CBC Encryption and Decryption in Python
Each plaintext block (except the first block) is XORed (bitwise exclusive OR) with the previous ciphertext block before encryption. The XOR operation introduces diffusion to ensure identical plaintext blocks produce different ciphertext blocks, enhancing security.
An Initialization Vector (IV) is also needed for CBC. IV is a random value that serves as the initial input to the encryption algorithm. IV ensures that each encryption has a different ciphertext result.
Here is the implementation of the CBC encryption algorithm.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad from Crypto.Random import get_random_bytes def encrypt(plaintext, key): # Generate a random initialization vector (IV) iv = get_random_bytes(AES.block_size) # Create an AES cipher object with the key and AES.MODE_CBC mode cipher = AES.new(key, AES.MODE_CBC, iv) # Pad the plaintext and encrypt it ciphertext = cipher.encrypt(pad(plaintext, AES.block_size)) # Concatenate the IV and ciphertext encrypted_data = iv + ciphertext return encrypted_data def decrypt(ciphertext, key): # Extract the IV from the ciphertext iv = ciphertext[: AES.block_size] # Create an AES cipher object with the key, AES.MODE_CBC mode, and the extracted IV cipher = AES.new(key, AES.MODE_CBC, iv) # Decrypt the ciphertext and remove the padding decrypted_data = unpad(cipher.decrypt(ciphertext[AES.block_size :]), AES.block_size) return decrypted_data # Example usage plaintext = b"This is the message to be encrypted" # Generate a random 256-bit (32-byte) key # Key-length accepted: 16, 24, and 32 bytes. key = get_random_bytes(24) print("Key:", key.hex()) # Encryption encrypted_data = encrypt(plaintext, key) print("Encrypted data:", encrypted_data) # Decryption decrypted_data = decrypt(encrypted_data, key) print("Decrypted data:", decrypted_data) |
Output:
Key: 86e0f895f89701a174fdbe45ae82c126a3cb788111a1e1bd Encrypted data: b'U\xbe\xd6[\x12\xae\xebV\x14\xdf<\xdc^\xaf\xb3hWB\xb8#\x1c\x05E\xa6\xec\x0b\xf6\x96C\xb4x\xe6\x00\x13h\xe3\xb1*\n\xc2\x90\x8a\xba\xb0\x95[A;\xe5k\nz\xafA\x08>\xab\xf9M\xb9\xc9\xcc\xf9U' Decrypted data: b'This is the message to be encrypted'
AES-GCM Encryption Implementation in Python
This is a widely used encryption algorithm. It uses Galois Message Authentication Code (GMAC) for authentication and the Counter (CTR) encryption algorithm. One of the major selling points of this algorithm is that it allows for parallel processing, enabling efficient encryption and decryption of large amounts of data.
Here is an implementation in Python.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
from Crypto.Cipher import AES from Crypto.Random import get_random_bytes def encrypt_with_AES_GCM(message, secret_key): # Create an AES-GCM cipher object cipher = AES.new(secret_key, AES.MODE_GCM) # Encrypt the message and generate the ciphertext and authentication tag ciphertext, auth_tag = cipher.encrypt_and_digest(message) # Return the encrypted message along with the nonce and authentication tag return (ciphertext, cipher.nonce, auth_tag) def decrypt_with_AES_GCM(encrypted_message, secret_key): # Extract the ciphertext, nonce, and authentication tag from the encrypted message ciphertext, nonce, auth_tag = encrypted_message # Create an AES-GCM cipher object with the provided nonce cipher = AES.new(secret_key, AES.MODE_GCM, nonce) # Decrypt the ciphertext and verify the authenticity using the authentication tag plaintext = cipher.decrypt_and_verify(ciphertext, auth_tag) # Return the decrypted plaintext return plaintext # Generate a random 128-bit (16-byte) secret key # Accepted values 16, 24, and 32 for 128, 192 and 256-bit keys, respectively. secret_key = get_random_bytes(16) print("Secret Key:", secret_key.hex()) # Message to be encrypted using AES-GCM message = b"Password to be encrypted by AES-GCM algorithm" # Encryption encrypted_message = encrypt_with_AES_GCM(message, secret_key) print( "Encrypted Message:", { "ciphertext": encrypted_message[0].hex(), "nonce": encrypted_message[1].hex(), "auth_tag": encrypted_message[2].hex(), }) # Decryption decrypted_message = decrypt_with_AES_GCM(encrypted_message, secret_key) print("Decrypted Message:", decrypted_message) |
Output:
Secret Key: 0bc7ebda2f81bf9a5ff27a93e67ac048 Encrypted Message: {'ciphertext': 'ef6706a067a3c56278afa8a79899b3d4eacf7dd7010f1c82db994b2e4b68ea29c4b7e8f61a504de8023397df10', 'nonce': 'e1984b7bc33be671e2e75febf5028f04', 'auth_tag': '5bc38b07dc0488070b85ee2c3a209cb2'} Decrypted Message: b'Password to be encrypted by AES-GCM algorithm'
Note: As mentioned earlier and shown in the examples above, AES does not accept 64-bit keys (8 bytes). If you must implement 64-bit encryption, check out this link.
Conclusion
AES is a very powerful encryption method for digital data. In this article, we discussed how AES encryption works (at a high level) and then implemented three AES algorithms in Python. After going through the guide, you should be able to easily implement the other modes mentioned at the beginning of this article.