Fernet is a Python module under the Cryptography package which uses a unique key to encrypt and decrypt the data. In this article, we will learn what fernet is and how to use the fernet module with Python code examples.
If you are unfamiliar with cryptography, I would recommend you check out the article I have written on Cryptography using Python modules to get a basic idea about cryptography modules and their structures.
What Is The Cryptography Package?
Before diving into Fernet, we need to understand the cryptography package in Python. It is a collection of modules that can be implemented at various parts of your program. It is of two levels – a low level and a high level.
- Low level – It is a collection of building blocks called Cryptographic primitives, which need to be ordered in a precise manner to attain the desired security level.
- High level – This level does not require many implementations and can be applied directly, which leads to a much safer process.
In this article, we will go through the components and working of the fernet module in detail. Let’s dive right into the fernet tutorial.
Components of Fernet Module
Fernet implements symmetric key cryptography, as the data cannot be read/modified without the key. The various elements used in the Fernet module are listed below:
- Inputs
- 128-bit AES with CBC mode
- Padding
- HMAC with SHA256
- Fernet token
Let’s look at each of these in detail..
1. Inputs
It takes a set of inputs and produces a fernet token. The three inputs along with the initialization vector are namely:
- Plain_text (the data that needs to be encrypted)
- A key (the unique key to decrypt the data)
- Timestamp.
2. 128-bit AES with CBC mode
AES (Advanced Encryption Standards) are fast symmetric key encryption used to encrypt the data. Blocks of size 128-bits are used to handle the encryption and decryption of data. Blocks are sections of data that are divided into fixed-length strings.
The AES algorithm can only encrypt one block at a time. Hence various modes like CBC (Cipher Block Chaining) are used to encrypt multiple blocks of data at the same iteration. In this mode, a randomly chosen initialization vector(IV) is used to initialize the first block of every token.
3. Padding
Padding is the process of adding extra data to ensure that the input fits the required size. In AES cipher blocks of size 128-bits, padding is applied for smaller and larger sizes of data. Padding schemes prove to be essential in improving security levels by hiding the length of the encrypted data. Fernet implements PKCS #7 padding.
4. HMAC with SHA256
HMAC (Hash-based Message Authentication Code) is a message authentication code that uses a cryptographic key in conjunction with a hash function. It provides the server and client with a unique private key. Fernet achieves authentication through HMAC that uses the SHA256 hashing algorithm.
5. Fernet Token
A fernet token is the encrypted data of the input text, along with the key and timestamp. This token provides the necessary integrity and authentication to the inputted text.
Fields of Fernet Token
All the tokens in fernet are encoded according to the base64url specification, which is a binary to a text encoding scheme, safe for naming files.
A fernet token is a compilation of all the following:
Fernet Token Field | Description |
---|---|
Version | It specifies the version of the fernet that will be used. Currently, there is only one defined version with a value of 128 decimals or 0x80 in hexadecimal. |
Timestamp | The number of seconds passed from January 1, 1970, UTC, till the date of the token’s creation. It is a 65-bit unsigned big-endian integer. |
Initialization Vector (IV) | A unique 128-bit initialization vector is added to the first block of each token in AES during the encryption and decryption of data. The numbers are randomly generated using the os.urandom() function. |
Ciphertext | The ciphertext of varying lengths will be padded to 128-bits size in the block. |
HMAC | All the above fields will be authenticated with HMAC and run through a SHA256 hashing algorithm. Hence, it provides integrity and authenticity to the data. |
Encryption and Authentication of the Token
The following are the steps involved in encrypting a text and creating a fernet token:
- Record the current timestamp.
- Generate a unique IV using the os.urandom() function.
- Construct the ciphertext: Pad the plain text to 128-bits using PKCS #7 technique. Encrypt the padded data with 128 AES in CBC mode, using a user-defined key and the generated IV.
- Compute the HMAC for version, timestamp, IV, and plain text and concatenate them as a whole.
- Encode the entire token with base64url specification.
Verifying and Decryption of the Token
The following are the steps to verify and decrypt the token if they possess the secret key:
- Decode the token using base64url specification.
- Ensure that the first byte of the token is 0x80 and that the token is not outdated if the maximum age is specified.
- Recompute the HMAC for version, timestamp, IV, and plain text and verify if it matches with the HMAC that is already stored in the token.
- Decrypt the AES-CBC ciphertext using the IV and key.
- Unpad and display the plain text.
Passwords in Fernet
Passwords can also be used in fernet for security purposes. The password needs to be run through key derivations like bcrypt, Scrypt, or PBKDF2HMAC and should be salted. Salting is the process of adding random characters of specified length to the password to improve the security of the hashed password.
Implementing Cryptography Using Fernet: An Example
Let’s consider an example of encrypting and decrypting a user inputted plain text using a password.
First, install the cryptography package if you haven’t already.
pip install cryptography
Import all the necessary modules.
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
Let’s read a password as user input. Once the user enters the password, random salt with 16 characters is generated to concatenate with the password.
password = input("Enter the password: ").encode()
salt = os.urandom(16)
PBKDF2 (Password-Based Key Derivation Function 2) is used for deriving a key from a password and also as used for storing keys. Here, kdf containing the key is hashed using SHA256, which handles 32 bytes of data. The number of iterations is set to the maximum limit that the system/server can handle.
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
Now, the key is encoded using base64url and placed in the variable f.
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
The plain text is entered, encrypted as a ciphertext token, and displayed.
Plain_text = input("Enter the text to be encrypted: ").encode()
token = f.encrypt(Plain_text)
print("Encrypted data: ",token)
Let’s decrypt the ciphertext and print it.
print("Decrypted data: ",f.decrypt(token).decode())
Here is the complete Python code for implementing cryptography using Fernet module:
import base64
import os
from cryptography.fernet import Fernet
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
password = input("Enter the password: ").encode()
salt = os.urandom(16)
kdf = PBKDF2HMAC(
algorithm=hashes.SHA256(),
length=32,
salt=salt,
iterations=390000,
)
key = base64.urlsafe_b64encode(kdf.derive(password))
f = Fernet(key)
Plain_text = input("Enter the text to be encrypted: ").encode()
token = f.encrypt(Plain_text)
print("Encrypted data: ",token)
print("Decrypted data: ",f.decrypt(token).decode())
Output:
Final Thoughts
Fernet module is one of the safest and ideal ways to encrypt and decrypt data for beginners. It is best suited to secure smaller data as larger data cannot be encrypted if it does not fit in the memory.
I hope this fernet tutorial has helped you understand the working of the fernet module in Python. Adios and best wishes for your future ventures in cryptography!
I think there’s a design flaw in your example code. The salt needs to be transmitted in plain (or base64 encoded) format, not as an integral part of the encrypted string. Your code works because the salt is generated before the encryption and still known when the encrypted token is decrypted. Encryption and decryption are two separate processes though. Usually, different people (or instances) are encrypting and decrypting the token, respectively. How is the person decrypting the token supposed to know the salt?
Your point is well established. But salt acts like padding to the text to make it even more complex to crack. It is done before a text is encoded and unsalted after it is decoded, so it will be known. To conclude, salting is widely used in hashing messages and passwords, so it is not an integral part here, but I have included it for reference.
Thank you for sharing valuable content.