# AES Encryption & Decryption In Python: Implementation, Modes & Key Management

AES has been the standard encryption method used by the US federal government for 20 years, and it still remains **a de-facto standard for securing digital data** to this day. In fact, AES is so trustworthy it’s one of the two encryption methods we use at Onboardbase to store secrets.

If you need **to build a secure web application in 2022** using encryption, look no further: this is the article you are looking for. In the following piece, you’ll learn what AES is, how its main components work, and how to correctly use AES encryption and decryption in your project.

## What is AES

There are dozens of encryption algorithms out there, but the most commonly used is AES―short for **Advanced Encryption Standard**, also known as Rijndael. AES is an encoding algorithm that transforms plain text data into a version known as ciphertext that’s not possible for humans or machines to understand without an encryption key―a password.

For example, you use AES in software development to securely store passwords in a database. Storing a password as plain text would allow anyone with access to the database to log in to user accounts, so encrypting them is the first step to adding a layer of security to your authentication system.

## Symmetric Vs Asymmetric Encryption

AES is **a symmetric-key algorithm**, meaning the same key (aka passphrase or password) is used to encrypt and decrypt the data. This characteristic presents pros and cons detailed in the following sections.

**Asymmetric methods** use a public key for encryption and a secret key for decryption. Anyone can send encrypted messages but only the receiver knows how to decrypt them. TLS certificates used for secure HTTP communication (HTTPS) leverage asymmetric encryption, for example.

## Why AES Encryption

First, **encryption is a vital part of software development**. You use it everywhere: you need API keys to interact with web services, you need TLS certificates to send data over the Internet, you need passwords to implement user accounts, etc. Understanding how AES encryption works and how you can use it reduces the risk of potential data breaches.

AES encryption is **virtually unbreakable** as it would take years to brute-force a key. It’s so secure that governments use it to encrypt top-secret files. Its mathematical properties make it less vulnerable to potential attacks than other encryption methods out there, like Blowfish or Twofish.

AES encryption is also among the fastest symmetric encryption algorithms, making it **more practical to use at scale in real-life applications** than slower counterparts like Serpent. With data privacy becoming dearer to end-users, it’s vital to take into account encryption speed in data-intensive applications like instant messaging platforms implementing E2E encryption―if it took minutes to encrypt a message, the software would become unusable.

## How To Use AES Encryption / AES Decryption

## 1. Overview Of The AES Algorithm

Without going into the mathematical details of the algorithm, you need to understand the different parameters to have a better grasp of how libraries implement AES.

The AES algorithm can be divided into 3 main parts:

**Generate a key****Generate a cipher****Encrypt/decrypt the data with the cipher**

### Generating the AES key

AES requires **a secret passphrase known as a “ key” to encrypt/decrypt data**. Anybody with the key can decrypt your data, so you need it to be strong and hidden from everyone―only the software program should be able to access it.

The key can be either 128, 192, 256, or 512 bit long. An AES cipher using a 512-bit key is abbreviated as AES 512, for example. The longer the key, the more secure it is, but the slower the encryption/decryption will be as well. 128 bits is equivalent to 24 characters in base64 encoding, 44 characters for 256 bits. Since storage space isn’t usually a problem and the difference in speed between the versions is negligible, a good rule of thumb is to **use 256-bit keys**.

### Generating the cipher

The cipher is **the algorithm used to perform encryption/decryption**. To handle data of arbitrary length and nature, several modes of operation for AES are possible. Each mode changes the general algorithm while offering pros and cons, as we will later read.

### Encrypting/decrypting the data

AES operates on data blocks of 16 bytes represented as 4x4 two-dimensional arrays. The byte content of these matrices is transformed using a mathematical function defined by the cipher (also called *block cipher*), which gives us a ciphertext―the encrypted result.

Decryption is simply the inverse operation.

Without going into the technical details, the mathematical function guarantees the strength of the encryption, which is why **AES is considered unbreakable as long as the properties of the function are respected**―strong key, correctly implemented cipher, unique nonce, unique initialization vector, etc.

## 2. AES Implementation in Python Using PyCryptodome

Each programming language offers its own implementation of the AES algorithm. While implementing AES from scratch is a possibility, it is highly recommended to **use known libraries** instead if you are not a cybersecurity expert: the slightest error in your code would result in data leaks!

In Python, you have the *pycryptodome* library. Note that *pycrypto* is no longer maintained but its fork *pycryptodome* is.

Encrypting a password takes 3 lines of code:

```
from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes
data = b'secret data'
key = get_random_bytes(16)
cipher = AES.new(key, AES.MODE_EAX)
ciphertext, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
```

Decrypting a password is just as simple:

```
cipher = AES.new(key, AES.MODE_EAX, nonce)
data = cipher.decrypt_and_verify(ciphertext, tag)
```

Depending on the cipher you use, you’ll need to store different elements―tags, nonce, initialization vectors, MAC, etc.

The simplest way to store a key is to use an environment variable in your program, using a .env file for example:

`AES_KEY=MbQeThWmZq4t6w9z`

Other elements can simply be stored using JSON or along with the encrypted data by concatenation, as their length is fixed:

```
ciphertext, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
stored_text = nonce + tag + ciphertext
```

But how does each mode differ, you may ask? That’s what we are going to see in the next section.

## 3. AES Modes & Use Cases

AES implementations come with tens of different ciphers, so you may wonder which one is best to use. Using the right mode of operation is important to make your program more secure, as some modes have vulnerabilities that can be leveraged by attackers.

**TL;DR: use GCM mode for maximum security by default.**

### ECB (Electronic Code Book) mode (AES-ECB)

Each 16-byte block of plaintext is encrypted independently. This mode is not advised as it’s the least secure.

**Pros**:

- Simplest

**Cons**:

- Weakest cipher
- Padding needed to fit the data into 16-byte blocks

**Implementation in Python**:

`cipher = AES.new(key, AES.MODE_ECB)`

### CBC (Cipher Block Chaining) mode (AES-CBC)

Each plaintext block gets XOR-ed with the previous ciphertext block prior to encryption. An initialization vector is used to make sure each encryption has a different ciphertext result.

**Pros**:

- Each ciphertext is different, so it’s not possible to know if two hashed data strings originate from the same plain text

**Cons**:

- Padding needed to fit the data into 16-byte blocks
- An error in one plain text block will affect all the following blocks
- Vulnerable to attacks like padding oracle attacks, chosen-plaintext attacks, and chosen-ciphertext attacks if the initialization vector isn’t changed for every encryption
- Since you need to know the previous block to encrypt the next one, you can’t parallelize the operations, which results in a slower algorithm

**Implementation in Python**:

```
cipher = AES.new(key, AES.MODE_CBC)
cipher_text = cipher.encrypt(pad(data, AES.block_size))
iv = cipher.iv
decrypt_cipher = AES.new(key, AES.MODE_CBC, iv)
plain_text = decrypt_cipher.decrypt(cipher_text)
```

### CFB (Cipher FeedBack) mode (AES-CFB)

Turns the block cipher into a stream cipher. Each content byte is XOR-ed with a byte taken from a *keystream*. The keystream is obtained by encrypting the last ciphertext produced with the block cipher.

**Pros**:

- A stream cipher accepts data of any length (i.e. padding is not needed)

**Cons**:

- Corrupted data cannot be recovered as each ciphertext relies on the other to be decrypted
- Since you need to know the previous block to encrypt the next one, you can’t parallelize the operations, which results in a slower algorithm

**Implementation in Python**:

```
cipher = AES.new(key, AES.MODE_CFB)
cipher_text = cipher.encrypt(data)
iv = cipher.iv
decrypt_cipher = AES.new(key, AES.MODE_CFB, iv=iv)
plain_text = decrypt_cipher.decrypt(cipher_text)
```

### OFB (Output FeedBack) mode (AES-OFB)

Turns the block cipher into a stream cipher. Each content byte is XOR-ed with a byte taken from a keystream. The keystream is obtained by recursively encrypting the Initialization Vector.

**Pros**:

- A stream cipher accepts data of any length (i.e. padding is not needed)

**Cons**:

- Corrupted data cannot be recovered as you need the correct initialization vector to decrypt the whole thing.
- Cannot be parallelized (slower)

**Implementation in Python**:

```
cipher = AES.new(key, AES.MODE_OFB)
cipher_text = cipher.encrypt(data)
iv = cipher.iv
decrypt_cipher = AES.new(key, AES.MODE_OFB, iv=iv)
plain_text = decrypt_cipher.decrypt(cipher_text)
```

### CTR (Counter) mode (AES-CTR)

Turns the block cipher into a stream cipher. Each content byte is XOR-ed with a byte taken from a keystream. The keystream is generated by encrypting a sequence of *counter blocks* with ECB.

**Pros**:

- Accepts data of any length (i.e. padding is not needed)
- Each counter block can be encrypted separately. Parallelization makes the algorithm faster.

**Cons**:

- Corrupted data cannot be recovered
- Need a different IV for each message to be truly secure

**Implementation in Python**:

```
cipher = AES.new(key, AES.MODE_CTR
cipher_text = cipher.encrypt(data)
nonce = cipher.nonce
decrypt_cipher = AES.new(key, AES.MODE_CTR, nonce=nonce)
plain_text = decrypt_cipher.decrypt(cipher_text)
```

### GCM (Galois Counter Mode) mode (AES-GCM)

A combination of Counter mode (CTR) and Authentication. Uses a Message Authentication Code (MAC) for authentication.

**Pros**:

- Guarantees integrity (to establish if the ciphertext was modified in transit or if it really originates from a certain source)
- Accepts pipelined and parallelized implementations and have a minimal computational footprint

**Cons**:

- Complex implementation, hard to code yourself from scratch

**Implementation in Python**:

```
header = b"header"
#Encryption
cipher = AES.new(key, AES.MODE_GCM)
cipher.update(header)
cipher_text, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
#Decryption
decrypt_cipher = AES.new(key, AES.MODE_GCM, nonce=nonce)
decrypt_cipher.update(header)
plain_text = decrypt_cipher.decrypt_and_verify(cipher_text, tag)
```

### EAX (Encrypt-then-authenticate-then-translate) mode (EAX)

Another method for Authenticated Encryption with AES.

**Pros**:

- Detects any unauthorized modification
- Simpler than GCM to implement

**Cons**:

- Slower than GCM

**Implementation in Python**:

```
header = b"header"
#Encryption
cipher = AES.new(key, AES.MODE_EAX)
cipher.update(header)
cipher_text, tag = cipher.encrypt_and_digest(data)
nonce = cipher.nonce
#Decryption
decrypt_cipher = AES.new(key, AES.MODE_EAX, nonce=nonce)
decrypt_cipher.update(header)
plain_text = decrypt_cipher.decrypt_and_verify(cipher_text, tag)
```

## 4. AES Key Management & Storage

The AES key is the weakest link in the chain: you need to protect it at all costs!

While having your AES key in a .env file is a simple way to go about it, with a secret manager like Onboardbase you can inject AES passphrases in software programs as environment variables in your CI/CD process in just 3 commands.

After defining your AES key in your account dashboard, you can use Onboardbase CLI’s setup command to get started in a few seconds:

`onboardbase setup`

The command generates an onboardbase.yml file used to access the online vault:

```
setup:
project: projectname
environment: development
```

All you have left to do is to run the build command to inject AES keys from Onboardbase at runtime, removing the need to directly share AES keys or hardcode them:

`onboardbase build --command="your project's build command"`

## Subscribe to Onboardbase to keep your AES keys safe

Onboardbase makes developers’ lives easier by making AES key storage simple and easy. It’s the only tool you’ll need to store AES keys throughout the whole software development lifecycle. A dashboard centralizes app configurations for the whole team, and a command-line interface is available to integrate AES keys in any workflow. The best part? You can get started for free in a minute.

## Subscribe to our newsletter

The latest news, articles, features and resources of Onboardbase, sent to your inbox weekly