AES 256 Encryption and Decryption in Python

You might have come across “Caesar cipher“, which is one of the oldest and simple encryption techniques that helps to encrypt and decrypt a secret message. In this digital age, Have you ever wondered, what the latest technique is used to encrypt and decrypt messages?

AES 256 encryption & decryption uses a 256-bit key. Rijndael algo encrypts/decrypts data in 128-bit blocks via substitution, permutation & linear operations. The key schedule generates round keys. Ciphertext generated by last round transformation on the plaintext. Decryption is the reverse process.

AES 256 Encryption and Decryption in Python - Explained

Introduction to AES 256 Encryption and Decryption

AES (Advanced Encryption Standard) is a widely used encryption algorithm that is designed to be both secure and efficient. AES is a block cipher that works by dividing plaintext into blocks of fixed size (usually 128 bits), and then encrypting each block separately using a key. AES is available in three key sizes: 128-bit, 192-bit, and 256-bit. We are going to discuss 256-bit in-depth

Setting up the environment

To perform AES-256 encryption and decryption in Python, We need to have pycryptodome library installed, we can always install using pip as below:

pip install pycryptodome

If you are using Pycharm IDE, You can follow the below step to install pycryptodome

Pycharm -> preferences -> python interpreter -> + -> search "pycryptodome" and install
step to install pycryptodome

Once pycryptodome is installed, we can begin writing our encryption and decryption code.

Generating a 256-bit key

As an initial step to encrypt and decrypt using AES-256, we need a 256-bit key. Which, We can generate using the secrets module in Python:

import secrets key = secrets.token_bytes(32)

In the above code, We are using secrets.token_bytes(32) to generate a random 32-byte key, which is equivalent to a 256-bit key.

Encrypting plaintext using AES-256

Padding the plaintext

AES encryption works on fixed-size blocks of data, so we need to ensure that our plaintext is a multiple of the block size (which is 16 bytes for AES). We can do this using the pad function from the Crypto.Util.Padding module:

from Crypto.Util.Padding import pad 

plaintext = b'This is a secret message.' 

padded_plaintext = pad(plaintext, AES.block_size)

Initializing the AES cipher

Next, we need to initialize an AES cipher object with our key and a nonce. The nonce is a random value used to ensure the uniqueness of the ciphertext, and should be different for each encryption operation. We can generate a random nonce using secrets.token_bytes shown below

from Crypto.Cipher import AES 

cipher = AES.new(key, AES.MODE_EAX, nonce=secrets.token_bytes(16))

Encrypting the plaintext

With our cipher object initialized, we can now encrypt the padded plaintext using the encrypt method:

ciphertext = cipher.encrypt(padded_plaintext)

Generating an authentication tag

Finally, we need to generate an authentication tag, which is used to ensure the integrity of the ciphertext. The tag is generated by calling the digest method on our cipher object:

tag = cipher.digest()

Complete code for AES-256 encryption

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import secrets

# Generate a random 256-bit (32-byte) key
key = secrets.token_bytes(32)

# Generate a random 128-bit (16-byte) nonce
nonce = secrets.token_bytes(16)

# Create an AES cipher object with EAX mode and the random key and nonce
cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)

# Define the plaintext message to be encrypted
plaintext = b'Coding spell is a awesome learning platform..'

# Pad the plaintext message
padded_plaintext = pad(plaintext, AES.block_size)

# Encrypt the padded plaintext message
ciphertext = cipher.encrypt(padded_plaintext)

# Get the authentication tag
tag = cipher.digest()

# Print the results
print("Key: ", key)
print("Nonce: ", nonce)
print("Ciphertext: ", ciphertext)
print("Tag: ", tag)

Output:

Complete code execution for AES-256 encryption
Key:  b'\xa7\xc3\xa5\xc7y\xbf\xb4$\xbb\x96\x8c}\xfc\x84\xb7}\xc94\xa9\xb0hF(\xea\xdaj<\xd7\xfb\xefk\xe2'
Nonce:  b'\xbf\xe3\x84\xe1\x91\x1a\x8d:S\x83\xfe\x02\xb9\xdb\xc5\xc9'
Ciphertext:  b'\x7fs\xb2\r\xfa\x7f\xeeM\x81\xb3\xc4\xe2\xf6\x7f\xc7\x03k\xd2\xe9\x85\x9e=]c\x96v\xd9\x1f\xc7\x1d\n\x940\x887\xa9z\xed\xf2V\xee\x7f\x99\x08\x0b\x14P\xee'
Tag:  b'\x101\x8a\xe4\xee\x18\xdcD\xcaX\x03A[]d1'

Process finished with exit code 0

Decrypting ciphertext using AES-256

To decrypt ciphertext that has been encrypted using AES-256, Which is a reverse process of Encryption Below are the steps that need to be performed.

Initializing the AES cipher with the same key and nonce

First, we need to initialize an AES cipher object with the same key and nonce that were used to encrypt the plaintext:

from Crypto.Cipher import AES

cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)

Verifying the authentication tag

Next, we need to verify the authentication tag to ensure that the ciphertext has not been tampered with:

from Crypto.Util.Padding import unpad

try:

    cipher.verify(tag)

except ValueError:

    print("Incorrect key or corrupt ciphertext")

If the authentication tag is valid, the verify method will not raise a ValueError. If the tag is invalid, the verify method will raise a ValueError with the message “MAC check failed”.

Example:

If we supplied an incorrect tag, we will see the below error message, Which shows that it is used to verify the authenticity of the message

    raise ValueError("MAC check failed")
ValueError: MAC check failed

Decrypting the ciphertext and unpadded the plaintext

Assuming the authentication tag is valid, we can now decrypt the ciphertext and unpad the plaintext:

decrypted_padded_plaintext = cipher.decrypt(ciphertext) 

plaintext = unpad(decrypted, AES.block_size)

padded_plaintext, AES.block_size)

Complete code for AES-256 decryption

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# Define the key, nonce, and ciphertext generated in the encryption example
key = b'\xa7\xc3\xa5\xc7y\xbf\xb4$\xbb\x96\x8c}\xfc\x84\xb7}\xc94\xa9\xb0hF(\xea\xdaj<\xd7\xfb\xefk\xe2'
nonce = b'\xbf\xe3\x84\xe1\x91\x1a\x8d:S\x83\xfe\x02\xb9\xdb\xc5\xc9'
ciphertext = b'\x7fs\xb2\r\xfa\x7f\xeeM\x81\xb3\xc4\xe2\xf6\x7f\xc7\x03k\xd2\xe9\x85\x9e=]c\x96v\xd9\x1f\xc7\x1d\n\x940\x887\xa9z\xed\xf2V\xee\x7f\x99\x08\x0b\x14P\xee'
tag = b'\x101\x8a\xe4\xee\x18\xdcD\xcaX\x03A[]d1'

# Create an AES cipher object with EAX mode and the key and nonce
cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)

# Decrypt the ciphertext and verify the tag
try:
    plaintext = cipher.decrypt_and_verify(ciphertext, tag)
    print("Message is authentic.")
    print("Plaintext: ", plaintext.decode())
except ValueError:
    print("Message is not authentic!")

Output:

Complete code execution for AES-256 decryption
Message is authentic.
Plaintext:  Coding spell is a awesome learning platform.

Process finished with exit code 0

Conclusion

In this tutorial, we’ve seen how to perform AES-256 encryption and decryption in Python using the pycryptodome library. We generated a random 256-bit key, padded the plaintext, initialized an AES cipher object with a nonce, encrypted the plaintext, generated an authentication tag, and then decrypted the ciphertext and unpadded the plaintext.

If you want to learn more about AES encryption in Python, here are some useful resources:

Good Luck with your Learning !!

Related Topics:

Mastering Python List of Dictionaries: Your Step-By-Step Guide

List of Lists in Python

Python was not found; run without arguments

Python Parallel For Loop

Jerry Richard R
Follow me