AES Encrypt / Decrypt - Examples

Let's illustrate the **AES encryption** and **AES decryption** concepts through working **source code** in Python.

The first example below will illustrate a simple **password-based AES encryption** (PBKDF2 + AES-CTR) without message authentication (**unauthenticated encryption**). The next example will add message authentication (using the AES-GCM mode), then will add password to key derivation (AES-256-GCM + Scrypt).

Simple AES-CTR Example

Let's start with simple AES-256-CTR non-authenticated encryption.

Install Python Libraries

`pyaes`

and `pbkdf2`

First, install the Python library **AES** symmetric key encryption algorithm:

`pyaes`

that implements the 1

pip install pyaes

Copied!

Next, install the Python library **PBKDF2** password-to-key derivation algorithm:

`pbkdf2`

that implements the 1

pip install pbkdf2

Copied!

Now, let's play with a simple AES encrypt / decrypt example.

Password to Key Derivation

First start by **key derivation**: from password to 256-bit encryption key.

1

import pyaes, pbkdf2, binascii, os, secrets

2

â€‹

3

# Derive a 256-bit AES encryption key from the password

4

password = "s3cr3t*c0d3"

5

passwordSalt = os.urandom(16)

6

key = pbkdf2.PBKDF2(password, passwordSalt).read(32)

7

print('AES encryption key:', binascii.hexlify(key))

Copied!

The above code **derives a 256-bit key** using the **PBKDF2** key derivation algorithm from the password **salt** (128-bit). This salt should be stored in the output, together with the ciphertext, because without it the decryption key cannot be derived again and the decryption will be impossible.

`s3cr3t*c0d3`

. It uses a random password derivation The output from the above code may look like this:

1

AES encryption key: b'7625e224dc0f0ec91ad28c1ee67b1eb96d1a5459533c5c950f44aae1e32f2da3'

Copied!

The derived **key** consists of **64 hex digits** (32 bytes), which represents a **256-bit** integer number. It will be different if you run the above code several times, because a random salt is used every time. If you use the same salt, the same key will be derived.

AES Encryption (CTR Block Mode)

Next, generate a **random 256-bit initial vector (IV)** for the AES CTR block mode and perform the **AES-256-CTR encryption**:

1

# Encrypt the plaintext with the given key:

2

# ciphertext = AES-256-CTR-Encrypt(plaintext, key, iv)

3

iv = secrets.randbits(256)

4

plaintext = "Text for encryption"

5

aes = pyaes.AESModeOfOperationCTR(key, pyaes.Counter(iv))

6

ciphertext = aes.encrypt(plaintext)

7

print('Encrypted:', binascii.hexlify(ciphertext))

Copied!

The output from the above code may look like this:

1

Encrypted: b'53022cf12c5959ddf3e733128930dd3d52e3ea'

Copied!

The **ciphertext** consists of **38 hex digits** (19 bytes, 152 bits). This is the size of the input data, the message

`Text for encryption`

.Note that after AES-CTR encryption the **initial vector (IV)** should be stored along with the ciphertext, because without it, the decryption will be impossible. The **IV** should be randomly generated for each AES encryption (not hard-coded) for higher security.

Note also that if you encrypt the same **plaintext** with the same encryption **key** several times, the output will be **different** every time, due to the randomness in the **IV**. This is intended behavior and it increases the security, e.g. resistance to dictionary attacks.

AES Decryption (CTR Block Mode)

Now let's see how to **decrypt a ciphertext** using the AES-CTR-256 algorithm. The input consists of **ciphertext** + encryption **key** + the **IV** for the CTR counter. The output is the original **plaintext**. The code is pretty simple:

1

# Decrypt the ciphertext with the given key:

2

# plaintext = AES-256-CTR-Decrypt(ciphertext, key, iv)

3

aes = pyaes.AESModeOfOperationCTR(key, pyaes.Counter(iv))

4

decrypted = aes.decrypt(ciphertext)

5

print('Decrypted:', decrypted)

Copied!

The output of the above should be like this:

1

Decrypted: b'Text for encryption'

Copied!

Note that the **initialized again**, because the CTR cipher block mode algorithm keeps an internal **state** that changes over the time.

`aes`

object should be Note also that the above code **cannot detect wrong key**, wrong **ciphertext** or wrong **IV**. If you use an incorrect key to decrypt the ciphertext, you will get a wrong unreadable text. This is clearly visible by the code below:

1

key = os.urandom(32) # random decryption key

2

aes = pyaes.AESModeOfOperationCTR(key, pyaes.Counter(iv))

3

print('Wrongly decrypted:', aes.decrypt(ciphertext))

Copied!

The output of the above incorrect decryption attempt might be like this:

1

Wrongly decrypted: b'\xe6!\n\x9a\xa9\x15\x12\xd9\xcb\x9cS\x86\xcc\xe1\x1d\x1a\x8blw'

Copied!

Now it is your time to **play with the above code example**. Try to to encrypt and decrypt different messages, to change the input message, the key size, to hard-code the IV, the key and other parameters, switch to CBC mode, and see how the results change. Enjoy learning by playing.

AES-256-GCM Example

Now, let's give a full example how to use the **AES-256-GCM** symmetric encryption construction. We shall use a different Python library for AES, called

`pycryptodome`

, which supports the the AES-256-GCM construction:1

pip install pycryptodome

Copied!

Next, let's play with the below **AES-GCM example in Python**, which generates a random encryption key (secret key) and uses it to **encrypt** a text message, then **decrypts** it back to the original plaintext message:

1

from Crypto.Cipher import AES

2

import binascii, os

3

â€‹

4

def encrypt_AES_GCM(msg, secretKey):

5

aesCipher = AES.new(secretKey, AES.MODE_GCM)

6

ciphertext, authTag = aesCipher.encrypt_and_digest(msg)

7

return (ciphertext, aesCipher.nonce, authTag)

8

â€‹

9

def decrypt_AES_GCM(encryptedMsg, secretKey):

10

(ciphertext, nonce, authTag) = encryptedMsg

11

aesCipher = AES.new(secretKey, AES.MODE_GCM, nonce)

12

plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag)

13

return plaintext

14

â€‹

15

secretKey = os.urandom(32) # 256-bit random encryption key

16

print("Encryption key:", binascii.hexlify(secretKey))

17

â€‹

18

msg = b'Message for AES-256-GCM + Scrypt encryption'

19

encryptedMsg = encrypt_AES_GCM(msg, secretKey)

20

print("encryptedMsg", {

21

'ciphertext': binascii.hexlify(encryptedMsg[0]),

22

'aesIV': binascii.hexlify(encryptedMsg[1]),

23

'authTag': binascii.hexlify(encryptedMsg[2])

24

})

25

â€‹

26

decryptedMsg = decrypt_AES_GCM(encryptedMsg, secretKey)

27

print("decryptedMsg", decryptedMsg)

Copied!

The AES-GCM encryption takes as input a **message** + **encryption key** and produces as output a set of values: { **ciphertext** + **nonce** + **authTag** }.

- The
**ciphertext**is the encrypted message. - The
**nonce**is the randomly generated initial vector (IV) for the GCM construction. - The
**authTag**is the message authentication code (MAC) calculated during the encryption.

The encryption **key size** generated in the above code is 256 bits (32 bytes) and it configures the AES-GCM cipher as AES-256-GCM. If we change the key size to 128 bits or 192 bits, we shall use AES-128-GCM or AES-192-GCM respectively.

The output from the above code looks like this:

1

Encryption key: b'233f8ce4ac6aa125927ccd98af5750d08c9c61d98a3f5d43cbf096b4caaebe80'

2

encryptedMsg {'ciphertext': b'1334cd5d487f7f47924187c94424a2079656838e063e5521e7779e441aa513de268550a89917fbfb0492fc', 'aesIV': b'2f3849399c60cb04b923bd33265b81c7', 'authTag': b'af453a410d142bc6f926c0f3bc776390'}

3

decryptedMsg b'Message for AES-256-GCM + Scrypt encryption'

Copied!

It is visible that the **encryption key** above is 256 bits (64 hex digits), the **ciphertext** has the same length as the input message (43 bytes), the **IV** is 128 bits (32 hex digits) and the **authentication tag** is 128 bits (32 hex digits). If we change something before the decryption (e.g. the **ciphertext** of the **IV**), we will get and **exception**, because the message integrity will be broken:

1

encryptedMsg = (b'wrong chiphertext', encryptedMsg[1], encryptedMsg[2])

2

decryptedMsg = decrypt_AES_GCM(encryptedMsg, secretKey) # ValueError: MAC check failed

Copied!

AES-256-GCM + Scrypt Example

Now let's give a more complex example: **AES encryption of text by text password**. We shall use the authenticated encryption construction **AES-256-GCM**, combined with **Scrypt** key derivation:

1

from Crypto.Cipher import AES

2

import scrypt, os, binascii

3

â€‹

4

def encrypt_AES_GCM(msg, password):

5

kdfSalt = os.urandom(16)

6

secretKey = scrypt.hash(password, kdfSalt, N=16384, r=8, p=1, buflen=32)

7

aesCipher = AES.new(secretKey, AES.MODE_GCM)

8

ciphertext, authTag = aesCipher.encrypt_and_digest(msg)

9

return (kdfSalt, ciphertext, aesCipher.nonce, authTag)

10

â€‹

11

def decrypt_AES_GCM(encryptedMsg, password):

12

(kdfSalt, ciphertext, nonce, authTag) = encryptedMsg

13

secretKey = scrypt.hash(password, kdfSalt, N=16384, r=8, p=1, buflen=32)

14

aesCipher = AES.new(secretKey, AES.MODE_GCM, nonce)

15

plaintext = aesCipher.decrypt_and_verify(ciphertext, authTag)

16

return plaintext

17

â€‹

18

msg = b'Message for AES-256-GCM + Scrypt encryption'

19

password = b's3kr3tp4ssw0rd'

20

encryptedMsg = encrypt_AES_GCM(msg, password)

21

print("encryptedMsg", {

22

'kdfSalt': binascii.hexlify(encryptedMsg[0]),

23

'ciphertext': binascii.hexlify(encryptedMsg[1]),

24

'aesIV': binascii.hexlify(encryptedMsg[2]),

25

'authTag': binascii.hexlify(encryptedMsg[3])

26

})

27

â€‹

28

decryptedMsg = decrypt_AES_GCM(encryptedMsg, password)

29

print("decryptedMsg", decryptedMsg)

Copied!

The above code encrypts using **AES-256-GCM** given text **message** by given text **password**.

- During the
**encryption**, the Scrypt KDF function is used (with some fixed parameters) to**derive a secret key**from the password. The randomly generated**KDF salt**for the key derivation is stored together with the encrypted message and will be used during the decryption. Then the input message is**AES-encrypted**using the secret key and the output consists of**ciphertext**+**IV**(random nonce) +**authTag**. The final output holds these 3 values + the**KDF salt**. - During the
**decryption**, the Scrypt key derivation (with the same parameters) is used to derive the same**secret key**from the encryption**password**, together with the**KDF salt**(which was generated randomly during the encryption). Then the ciphertext is**AES-decrypted**using the secret key, the IV (nonce) and the authTag. In case of success, the result is the decrypted original**plaintext**. In case of error, the authentication tag will fail to authenticate the decryption process and an**exception**will be thrown.

The **output** from the above code looks like this:

1

encryptedMsg {'kdfSalt': b'2dd0b783290747ba62a63fc53591170d', 'ciphertext': b'223ed888dcd216dcd40c47ff7cdaa7fd7eab65f4f0405350a43c5cad5b6b47b527c709edec29d7d6967518', 'aesIV': b'7f114d946c77508ed2e6afe652c78f21', 'authTag': b'e84a14b9542320a0b1473141c989c48f'}

2

decryptedMsg b'Message for AES-256-GCM + Scrypt encryption'

Copied!

If you run the same code, the output will be different, due to randomness (random KDF salt + random AES nonce).

Last modified 3mo ago