DEV Community

Cover image for A brief, beginner's guide to cryptography and AES!
Phantz
Phantz

Posted on • Updated on

A brief, beginner's guide to cryptography and AES!

In this guide I'll be giving a brief introduction to cryptography, explain the basic concepts of it, and show some practical code (with python) to use it all!

I've recently started diving deep into the cryptography world and I was inspired enough to share my insights about it, which is where this guide comes from.

I'm, however, by no means, an expert of this topic. But I'll try my best to convey what I've learned :)

This guide is pretty loooong, so if you are short on time, you can put it on your Reading list to continue later! If you're going for the one-go, Buckle up!

So what exactly is cryptography?

As wikipedia explains it:

Cryptography is the practice and study of techniques for secure communication in the presence of third parties called adversaries

To put it simply, Cryptography is the branch of CS which focuses on securing a piece of data through the use of specific algorithms.

Why specific algorithms? Because if we secure a piece of data by the same method everytime, so that the output for the same data is always identical - well, that's not very secure is it?

This is why we need algorithms, that are random. So that encrypting the same data doesn't always produce the same output. But you might already know that computers can never be truly random. So we need to rely on highly sophisticated encryption algorithms that can generate ciphers that are never truly random but as random as possible. So let's discuss about some of these algorithms!

Algorithms

The algorithms can be divided into 3 sub categories - Symmetric, Asymmetric and Transform.

However there's another sub category - Hashing. You might be wondering, but hashing is one-way whereas encryption is two-way. That is, what has been encrypted can also be decrypted but what is hashed can not be dehashed. This Hashing is actually different however, I'm referring to a Cryptographic Hash function which can both me mapped and demapped.

We'll be focusing on Symmetric algorithms in this guide as these are one of the most common encryption techniques.

Symmetric Encryption

From Wikipedia

Symmetric encryption is a type of encryption where only one key (a secret key) is used to both encrypt and decrypt electronic information

Symmetric encryption can be primarily divided into 3DES, AES and BLOWFISH. AES which stands for Advanced Encryption Standard, is one of the most popular encryption method. The python module pycryptodome allows us to easily implement this method of encryption. So let's dive into some examples!

First make sure you have pycryptodome installed with pip install pycryptodome

To use the AES encryption we import these modules;

from Crypto.Cipher import AES
from Crypto import Random
Enter fullscreen mode Exit fullscreen mode

Note: You only need the AES module for the main encryption, but to generate secure keys I recommend the Random module as it generated Cryptographically Secure random output.

There are 2 keys that you need to generate for AES:-

  • AES_KEY: This is the primary key that is needed to both encrypt and decrypt a piece of data. This is a private protected key and should never be kept alongside the encrypted data. This key can be either 16 bytes, or 24 bytes, or 32 bytes. These are the values AES.key_size holds.
    Alt Text

  • Initiallization Vector (IV)/NONCE: Depending on what AES mode you are using (we'll be diving into the modes explanation real soon), you'll also need either an IV or NONCE. This is a public key that can be stored alongside the encrypted_data. This key is used to prevent a piece of data to be always encrypted into the same output. IV is 16 bytes in size, reflected by AES.block_size, whereas the size of a NONCE can range from 1 byte to 15 bytes inclusive. Hence the max limit is reflected by AES.block_size-1.
    Alt Text

  • Message Authentication Code (MAC): This is a byte string returned by Authenticated Encryption methods to check if the encrypted data was modified before decryption. More on this later! This is a public key that can be stored alongside the encrypted data. This key is 16 bytes in size.

Now AES has many modes, you should never just take a guess on which mode to use, most of these modes have a specific use case. Modes are denoted by AES.MODE_(mode name here). Let's focus on the most commonly used modes!

  • AES.MODE_CBC: This is a method that requires data padding. That is, if the size of the data is NOT in multiples of the AES_KEY size, you'll have to pad it with fill values (usually zeroes) to encrypt. This requires an IV. The output from this mode is completely malleable
  • AES.MODE_OFB: This is a stream cipher method. This DOES NOT require data padding and it is independent of the source data. Neither encryption nor decryption can be parallelized. That is, Neither encryption nor decryption is done in parts. If one part of the output corrupts, so does the whole data. This requires either an IV or a NONCE. The output from this mode is completely malleable
  • AES.MODE_CFB: This is a stream cipher method. This DOES NOT require data padding and it is dependent on the source data. Encryption cannot be parallelized but decryption can be. That is, Encryption is not is done in parts but decryption is. This requires either an IV or a NONCE. The output from this mode is completely malleable
  • AES.MODE_OCB: This is an authenticated encryption method. This method overcomes the drawbacks of the previous methods and uses a MAC (Message Encryption Code) to make sure the encrypted_data has not been modified before decryption. This requires a NONCE. The output from this mode is not malleable

I highly recommend reading this stack overflow post to know more about these modes.

So once you've decided on a mode to use, we can move on to the coding part! I'll be using AES.MODE_OCB for this example.

First we generate the required keys:-

AES_key = Random.get_random_bytes(AES.key_size[0])       # A key of size 16 bytes
NONCE = Random.get_random_bytes(AES.block_size-1)        # A key of 15 bytes
Enter fullscreen mode Exit fullscreen mode

Now we have to convert our source_data to a byte string. Remember AES modes take byte strings as input and put out byte strings as output. After that we can just encrypt it.

data = 'Hello World'
byte_dat = data.encode()        # You can also just do 'Hello World'.encode()
cipher = AES.new(AES_key, AES.MODE_OCB, NONCE)
ciphertext, MAC = cipher.encrypt_and_digest(byte_dat)
Enter fullscreen mode Exit fullscreen mode

Alt Text
Notice, the encryption returns 2 values, a ciphertext and a MAC. They are both byte strings and we need both.

Afterwards, when we want to decrypt this ciphertext, we'll be needing the AES_KEY, the NONCE, the MAC and ofcourse the ciphertext itself.

cipher = AES.new(AES_KEY, AES.MODE_OCB, NONCE)
decrypted_string = cipher.decrypt_and_verify(ciphertxt, MAC)
og_string = decrypted_string.decode()
Enter fullscreen mode Exit fullscreen mode

Alt Text

Now a few words on storing the keys and the encrypted data, if you remember from above, we can store the ciphertext, the MAC and the NONCE in the same place. The AES_KEY, however, should be stored as securely as possible, we DO NOT want unauthorized access to this key. There are many methods of storing byte strings. You can either store them exactly as they seem to be, as byte strings or you can conver them into a list of integers by using list(). For instance, using list() on a MAC will return a list of 16 integers;
Alt Text

You can also decode these byte strings into strings as well for storage. The codec you want to use for the AES output byte strings is latin-1. However you can also use b64encode() on the byte string and then use decode() on them. I prefer base64encoding, salting etc on top of the ciphertext since the ciphertext is actually the same length as the original string, we don't want that.

For encryption storage-

from base64 import b64encode
encoded_string = b64encode(ciphertxt).decode()
NONCE_string = NONCE.decode('latin-1')
MAC_string = MAC.decode('latin-1')
Enter fullscreen mode Exit fullscreen mode

For decryption retrieval-

from base64 import b64decode
ciphertxt = b64decode(encoded_string)
NONCE = NONCE_string.encode('latin-1')
MAC = MAC.encode('latin-1')
Enter fullscreen mode Exit fullscreen mode

And that's it! That's how you encrypt data with AES using pycryptodome!

I hope this guide helped you learn a thing or two about cryptography, it's a really cool topic and if your thirst for knowledge has not quenched yet checkout this really cool github repo which has a LOT of resources about Cryptography! If you made it this far, thanks for reading and I sincerely hope my guide helps you in your future endeavors!

Top comments (0)