AES Encryption with Python

DO NOT USE THIS POST TO LEARN ABOUT AES ENCRYPTION IN PYTHON. I DID NOT HAVE SUFFICIENT EXPERIENCE WITH BYTES, STRINGS, AND ENCRYPTION WHEN I WROTE THIS. I WILL HAVE A NEW POST WITH PYTHON3 (AND HOPEFULLY IT WILL HAVE BETTER INFORMATION).

To get AES encryption working in your Python script, you need to install PyCrypto.

Fedora: sudo yum install python-crypto
Debian: sudo aptitude install python-crypto
openSUSE: sudo zypper install python-crypto

Now the script, which has been created and tested on Python 2.7.


from Crypto.Cipher import AES
from base64 import b64encode, b64decode
import os
from datetime import datetime
from re import sub

# AES is a block cipher so you need to define size of block.
# Valid options are 16, 24, and 32
BLOCK_SIZE = 32

# Your input has to fit into a block of BLOCK_SIZE.
# To make sure the last block to encrypt fits
# in the block, you may need to pad the input.
# This padding must later be removed after decryption so a standard padding would help.
# Based on advice from Using Padding in Encryption,
# the idea is to separate the padding into two concerns: interrupt and then pad
# First you insert an interrupt character and then a padding character
# On decryption, first you remove the padding character until 
# you reach the interrupt character
# and then you remove the interrupt character
INTERRUPT = u'\u0001'
PAD = u'\u0000'

# Since you need to pad your data before encryption, 
# create a padding function as well
# Similarly, create a function to strip off the padding after decryption
def AddPadding(data, interrupt, pad, block_size):
    new_data = ''.join([data, interrupt])
    new_data_len = len(new_data)
    remaining_len = block_size - new_data_len
    to_pad_len = remaining_len % block_size
    pad_string = pad * to_pad_len
    return ''.join([new_data, pad_string])
def StripPadding(data, interrupt, pad):
    return data.rstrip(pad).rstrip(interrupt)

# AES requires a shared key, which is used to encrypt and decrypt data
# It MUST be of length 16, 24, or 32
# Make sure it is as random as possible 
# (although the example below is certainly not random)
# Based on comments from lighthill,
# you should use os.urandom() or Crypto.Random to generate random secret key
# I also use the GRC Ultra High Security Password Generator to generate a secret key
SECRET_KEY = u'a1b2c3d4e5f6g7h8a1b2c3d4e5f6g7h8'

# Initialization Vector (IV) should also always be provided
# With the same key but different IV, the same data is encrypted differently
# IV is similar to a 'salt' used in hashing
# It MUST be of length 16
# Based on comments from lighthill,
# you should NEVER use the same IV if you use MODE_OFB
# In any case, especially if you are encrypting, say data to be store in a database,
# you should try to use a different IV for different data sets,
# even if you use the same secret key
IV = u'12345678abcdefgh'

# Now you must choose a 'mode'. Options are available from Module AES.
# Although the default is MODE_ECB, it's highly recommended not to use it.
# For more information on different modes, read Block cipher modes of operation.
# In this example, I had used MODE_OFB
# But based on comments from lighthill,
# I switched over to MODE_CBC, which seems quite popular

# Let's create our cipher objects
cipher_for_encryption = AES.new(SECRET_KEY, AES.MODE_OFB, IV)
cipher_for_decryption = AES.new(SECRET_KEY, AES.MODE_OFB, IV)
cipher_for_encryption = AES.new(SECRET_KEY, AES.MODE_CBC, IV)
cipher_for_decryption = AES.new(SECRET_KEY, AES.MODE_CBC, IV)

# So you now have cipher objects
# Each operation that you perform on these objects alters its state
# So mostly you would want to perform a single operation on it each time
# For encrypting something, create a cipher object and encrypt the data
# For decrypting, create another cipher object and pass it the data to be decrypted
# This is the reason I called the cipher objects 
# 'cipher_for_encryption' and 'cipher_for_decryption'
#
#
#
# You will want to create encryption and decryption functions 
# so that it's easier to encrypt and decrypt data
def EncryptWithAES(encrypt_cipher, plaintext_data):
    plaintext_padded = AddPadding(plaintext_data, INTERRUPT, PAD, BLOCK_SIZE)
    encrypted = encrypt_cipher.encrypt(plaintext_padded)
    return b64encode(encrypted)
def DecryptWithAES(decrypt_cipher, encrypted_data):
    decoded_encrypted_data = b64decode(encrypted_data)
    decrypted_data = decrypt_cipher.decrypt(decoded_encrypted_data)
    return StripPadding(decrypted_data, INTERRUPT, PAD)

# We are now ready to encrypt and decrypt our data
our_data_to_encrypt = u'123456789012345678901234567890abc'
encrypted_data = EncryptWithAES(cipher_for_encryption, our_data_to_encrypt)
print ('Encrypted string:', encrypted_data)

# And let's decrypt our data
decrypted_data = DecryptWithAES(cipher_for_decryption, encrypted_data)
print ('Decrypted string:', decrypted_data)

Hat Tips

This post would not have been possible without help from: AES Encryption in Python Using PyCrypto; Block cipher modes of operation; Symmetric Encryption with PyCrypto; AES encryption of files in Python with PyCrypto; Using Padding in Encryption; Strings (Dive into Python 3);

10 Responses to AES Encryption with Python

  1. Pingback: Encrypting with Python | Notes & code snippets.

  2. Pingback: Links 2/9/2011: 11.10 ‘Oneiric Ocelot’ Beta, Cablegate is Out in Full | Techrights

  3. ludovic says:

    Thanks, it was useful. I just have one naive question: why did you make a call to b64encode()?

  4. hs says:

    It’s easier for humans (me) to read, copy/paste, etc. the encrypted data if it’s presented/stored in base64. That’s how I felt when I read other tutorials and used their examples to build this code.

  5. Anonymous says:

    In python 3 I had a few problems with the AddPadding function when I tried to prepare a string containing special characters like *µ!:;,/?, etc.. The padded string didn’t match the block size anymore. I had to call b64encode on the string before padding it:
    def AddPadding(self,data, block_size):
    interrupt = b’1′
    pad = b’0′
    new_data = b64encode(bytes(data, encoding=”utf-8″))
    new_data = new_data + interrupt
    new_data_len = len(new_data)
    remaining_len = block_size – new_data_len
    to_pad_len = remaining_len % block_size
    pad_string = pad * to_pad_len
    textToEncode = new_data + pad_string
    return textToEncode

    def StripPadding(self,data):
    interrupt = ‘1’
    pad = ‘0’
    word = str(data, encoding = “utf-8”)
    word = word.rstrip(pad)
    word = word.rstrip(interrupt)
    decoded = b64decode(bytes(word,encoding = “utf-8”))
    return str(decoded, encoding = “utf-8”)

  6. ludovic says:

    One last thing:
    You should use string.rsplit(interrupt,1)[0] instead of .rstrip(pad).rstrip(interrupt).
    data.rstrip(pad).rstrip(interrupt) would strip ‘abc11100000’ to ‘abc’ while what you expect it to return is ‘abc11’.

  7. Try http://encripta.org, this service use Gibberish AES, a JavaScript Implementation. It’s easy and fast.

  8. die_spinne says:

    I ported the code to Python 3 and implemented unicode support. Feel free to use :)
    Im removed some redundancy and unused imports and made a handy Class to integrate in projects…
    http://nopaste.me/paste/19654574104fbd872a342f8

  9. Anonymous says:

    How about using MODE_CFB. That doesn’t require the need of padding.

  10. Mayank says:

    What if my shared key is more than 32 in lenght ?