A while ago, I made a tweet about implementing Fernet symmetric encryption algorithm, you can see the tweet here and the gist here
At the time, I was working on a web app that needed me to encode data that was to be sent via email. I didn't want to leave the data in plain text so I thought, why not encrypt it then decrypt when the user enters the data back into the app. I didn't want a situation where an attacker would be able to reverse engineer the system leading to a vulnerability hence the need for encryption.
The code in the tweet was extracted from a password manager called PVault which I wrote sometime last year, It used Fernet to encrypt stored passwords that could only be decrypted with a master key. You can find the repo here
What is Fernet Algorithm?
Image gotten from www.asecuritysite.com
Fernet is a symmetric encryption algorithm that makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet also uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random()
I know that's a lot to take in especially if you don't have any knowledge about cryptography and all so let me try and explain in simpler terms
Fernet algorithm is one that allows you to encrypt messages and decrypt them using a specific key (you can refer this to a password). Let's take a door, for example, the doors in your house can only be accessed by their key, and whatever lies behind the door is only available to whoever is in possession of the right key. So let us say the text "Hello World" is encrypted with the key "password" the resulting string could then be "Ifmmp-Xpsme" and you can only recover the original text with the key used in encrypting it. It's also like your online passwords. They can only be unlocked with the right key.
What are QR Codes?
You've probably seen one of these around, those are QR Codes. It's a way of encoding data into black and white images like dots as shown above (custom colors can be set too). They're commonly used to store URL addresses just like the one above, If you scan that it leads you to this blog post, how cool is that :)
Identity Systems hmm...
The kind of identity system being referred to here is similar to ID cards and the rest which can be used to personally identify a person. In this blog post, we are going to be assuming we are a school issuing ID cards to students that contain a QR code which when scanned brings out the student's full profile as opposed to the traditional way of writing all the info on it.
Let's start writing code
Image gotten from www.wikipedia.org
I'm sure you're already tired of all the talk and hungry for code, Well we're at that junction now. We are going to be using the Python language because It's what I'm most proficient in and It has libraries to help us complete our task easier.
Generating QR Codes
Before we proceed, you need to have pyqrcode, pypng and pyzbar all installed. Follow the links for their installation guide.
# import necessary libraries import pyqrcode # generate our QRCode qr = pyqrcode.create("Hello World, This is my first QRCode!!!")
You can also extend this with a few things
# export to png qr.png("qr.png", scale=6) # export as a custom color qr.png("qr-colored.png", scale=6, module_color="#2962ff") # export in text form print(qr.text()) # print in the terminal print(qr.terminal("blue", "white"))
Reading QR codes
# importing necessary libraries from PIL import Image from pyzbar.pyzbar import decode # reading the qr image data = decode(Image.open("qr.png")) text = data.data.decode("utf-8") print(text)
Now we have our QR generator and reader, what's next? Let's go back to our school ID card example, we are going to assume we have 5 students in the school and their data is represented below
To create our QR cards for the students, we need to encode the matric_number of each student into our cards, then when the QR is scanned to identify the student, the student database is then queried and the result is returned. Let's write semi-pseudo-code for this
# import necessary libraries from PIL import Image import pyqrcode from pyzbar.pyzbar import decode def generate_student_qr(matric_number): qr = pyqrcode.create(matric_number) qr.png(matric_number + ".png", scale=6) def get_student_data(qr_image): data = decode(Image.open(qr_image)) matric_number = data.data.decode("utf-8") # query DB for student info # SELECT * FROM students WHERE matric_number=matric_number student_data = "Student Data" if student_data == None: return "The student does not exist or the QR is invalid" return student_data # register new student QR matric_number = "test" generate_student_qr(matric_number) # get student data student_data = get_student_data(matric_number + ".png") print(student_data)
With this, we've got a fully working ID generator and reader
The need for encryption
Image gotten from www.varonis.com
As it is, the system simply encodes the matric number of the student onto the QR, If a student manages to write a custom QR reader he/she would understand the algorithm behind the ID generation and would be able to clone and duplicate any student ID card in the system and this is very dangerous. We would want to encrypt the data before encoding it on the QR so even if it is read, the user only sees gibberish and only the server understands the actual data and this is where Fernet comes in.
Fernet will encrypt the data in a form that only people who own the key are able to use the data and in this case, our server. First install cryptography module here
# import necessary libraries import base64 from PIL import Image import pyqrcode from pyzbar.pyzbar import decode from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.fernet import Fernet # generate Fernet encryption key def generate_fernet_key(master_key, salt): kdf = PBKDF2HMAC( algorithm=hashes.SHA512(), length=32, salt=salt.encode(), iterations=100000, backend=default_backend() ) key = base64.urlsafe_b64encode(kdf.derive(master_key.encode())) return key.decode("utf-8") # encrypt a given text with our key def encrypt_text(text, key): encryptor = Fernet(key) hash = encryptor.encrypt(text.encode()) return hash.decode() # decrypt a given text with our key def decrypt_text(hash, key): decryptor = Fernet(key) text = decryptor.decrypt(hash.encode()) return text.decode() # generate student_qr with encryption def generate_student_qr(matric_number, key): hashed_matric = encrypt_text(matric_number, key) qr = pyqrcode.create(hashed_matric) qr.png(matric_number + ".png", scale=6) # get student data with encryption def get_student_data(qr_image, key): data = decode(Image.open(qr_image)) hashed_matric = data.data.decode("utf-8") matric_number = decrypt_text(hashed_matric, key) # query DB for student info # SELECT * FROM students WHERE matric_number=matric_number student_data = "Student Data" if student_data == None: return "The student does not exist or the QR is invalid" return student_data # generate key for example # you need to first set a master_key and salt master_key = "server master key" server_salt = "server salt" server_fernet_key = generate_fernet_key(master_key, server_salt) # generate a new student ID with encryption matric_number = "test" generate_student_qr(matric_number, server_fernet_key) # get student data student_data = get_student_data(matric_number + ".png", server_fernet_key) print(student_data)
Let's go over what we just did, First, we implemented the Fernet encryption algorithm in Python using the cryptography module. Then we rewrote our ID generation algorithm this time using encryption
Let's take an example of how the data would have been before and after encryption
- matric_number = "my matric"
- data_encoded_in_qr = "my matric"
- matric_number = "my matric"
- master_key = "master key"
- server_salt = "server_salt"
- data_encoded_in_qr = "gAAAAABenoO8V-2Xx2ys9taCb_4u4Ao-uZ70MdwuxPbTPITzhLbdQ3bqoBlOQfOBDVoA6YWAvkylJEG6t6lpTK94MhBmwliXHQ=="
- We learned about generating and reading QR Codes in Python
- We learned about the Fernet encryption algorithm and implemented it in Python
- We made a QR Code Identity System
- And you got to read my first article on Hashnode :)
In the future, I might write a second part of this article to build a web app with Flask which will generate Fernet keys from master keys and Encrypt data into QR Codes.
What to do from here?
Go on with your regular day, remember to stay safe, and keep learning every day :)
psst... The QR code at the top of the article is the matric number we encrypted, scan it to reveal the value. And the QR code in the section about what QR codes is linked to this article when scanned.
Top comments (2)
Really cool post! I'm really interested in reviewing your GitHub code for the password manager. One edit suggestion is that the Fernet algorithm you've described is symmetric and not asymmetric.
Thank you for this