Living secure in insecure times — encrypting in Python
By: Fernando García Varela
Tags: Python, Security, Cryptography

Photo by Luis Villasmil on Unsplash

Somedays I feel more insecure. I read the news or watch TV instead travelling inside the menus of Netfilx or Disney+.

In these rare occassions I finally fear that thieves are looking at me, awaiting for me to leave my computer to take a coffee and steal the very valuable information I have.

As far as some of the systems I’m writing rely on very sensible information, I need a good, easy, trusted and maintenable system to hide this information from the bad guys. I need a convenient way to do it. And in Python.

Additionally I don’t want to be worried if someone steals the files where the information rely as far as they are not able to decrypt it.

For this Python has a very handy solution to encrypt your information: use the the cryptography package.

As described in the Python documentation:

cryptography is a package which provides cryptographic recipes and primitives to Python developers. Our goal is for it to be your “cryptographic standard library”. It supports Python 2.7, Python 3.5+, and PyPy 5.4+. (source: https://pypi.org/project/cryptography/)

Sounds good! Very handy.

How does it work from 100.000 feet distance?: provide a password, use the password to obtain an encryption-decryption key and finally use the key to encrypt and decrypt.

Sweet! we’re now prepared to do secure stuff in insecure environments.

Well, I decided to use a password named: “password”. This password will be used to encrypt and decrypt the information.

This password can be (and must be) whatever you want. The only thing you need to bear in mind is that it’s not accessible for the villians. I decided to put this inside a environment variable that it’s called by my securization script to encrypt and decrypt the information, always in memory.

You, of course, can save and restore the key to do it (in the case you want to store as a backup or share with other members of your org). But in my opinion this is not very advisable.

So the Python script I have wroten follows this approach:

Register your password in an environment variable (Windows) or in a .bash if you’re in MAC or LINUX. The code shown works on Windows but it’s very easy to tweak to work on other systems. Generate your encryption key using the function generateEncryptionKey(). Then you can begin using encrypt or decrypt Files using the functions encryptFile() or decryptFile(). These functions use internally encrypt() and decrypt() that work on strings, no files.

Say you want to encrypt a file. Let’s write the functions. Easy!

First: Import libraries

Importing the libraries is a stantdard step in every Python script, so I assume you’re familiar to it. In the case you need any package that’s not in your system you can use pip or conda to install it.

# import librariesimport cryptography

from cryptography.fernet import Fernet

import base64

import os

from cryptography.hazmat.backends import default_backend

from cryptography.hazmat.primitives import hashes

from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC

Setting the password and generate key

Let’s see first how you write the code to obtain the password from the environment variable (Windows). You need to substitute NAME_OF_ENV_VARIABLE for your own one.

def initializeEncryptionKey():

import os

CM_Encrypt_Password = "NAME_OF_ENV_VARIABLE"

key = CM_Encrypt_Password

key = os.environ[key]

return key

Now, that we have the password, we proceed to generate the encryption key. Note: it no password is sent, it gets the one contained as an environment varialbe. You can send a password instead. In this case the password provided will be used.

def generateEncryptionKey(password = None):



if password == None: # no password means get from environment

password = initializeEncryptionKey()



password_provided = password

password = password_provided.encode()



salt = b"\xb9\x1f|}'S\xa1\x96\xeb\x154\x04\x88\xf3\xdf\x05"



kdf = PBKDF2HMAC(algorithm=hashes.SHA256(),

length=32,

salt=salt,

iterations=100000,

backend=default_backend())



key = base64.urlsafe_b64encode(kdf.derive(password))

return key

This function uses the methods inside the cryptography library to generate the effective key, based on the password, to encrypt and decrypt information.

If you want to swim a little bit in the cryptography ocean, read the subsequent paragraph. You don’t really need it and you plan only using the functions, like me, jump to the next section directly.

A little bit of swim in the functions

A couple of statements need a bit of explanation in the case you want to be a little bit close to encryption methodology: PBKDF2HMAC and kdf.derive.

PBKDF2HMAC: without entering in a deep analysis, out of scope of this article, this function is typically used for deriving a cryptographic key from a password. (Exactly what we want!)

Parameters (source: https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/):

algorithm — An instance of HashAlgorithm .

— An instance of . length (int) — The desired length of the derived key in bytes. Maximum is (232–1) * algorithm.digest_size .

(int) — The desired length of the derived key in bytes. Maximum is (232–1) * . salt (bytes) — A salt. Secure values [1] are 128-bits (16 bytes) or longer and randomly generated.

(bytes) — A salt. Secure values [1] are 128-bits (16 bytes) or longer and randomly generated. iterations (int) — The number of iterations to perform of the hash function. This can be used to control the length of time the operation takes. Higher numbers help mitigate brute force attacks against derived keys. A more detailed description can be consulted for additional information.

(int) — The number of iterations to perform of the hash function. This can be used to control the length of time the operation takes. Higher numbers help mitigate brute force attacks against derived keys. A more detailed description can be consulted for additional information. backend — An instance of PBKDF2HMACBackend .

base64.urlsafe_b64encode(kdf.derive(password)): this function helps us to manage the information generated safelly (takes 8-bit binary byte data and encodes it using only the characters A-Z , a-z , 0–9 , + , / * so it can be transmitted over channels that do not preserve all 8-bits of data, such as email.) Again: very handy and simple.

kdf.derive(password): is a Key derivation function (what the hell?) The HKDF paper provides as good a summary as any: A Key derivation function (KDF) is a basic and essential component of cryptographic systems: Its goal is to take a source of initial keying material, usually containing some good amount of randomness, but not distributed uniformly or for which an attacker has some partial knowledge, and derive from it one or more cryptographically strong secret keys.

Uff, if you want a good understanding of this you need to swim much further in the ocean. Lots of papers describe these approaches in deep. If this is your case: good luck!

Encrypt and Decrypt Files

Finally we proceed to encrypt and decrypt using these functions and the generated key.

def encryptFile(infilename, outfilename, key):



with open(infilename, 'rb') as f:

data = f.read()



fernet = Fernet(key)

encrypted = fernet.encrypt(data)



with open(outfilename, 'wb') as f:

f.write(encrypted)def decrypFile(infilename, key):



with open(infilename, 'rb') as f:

data = f.read()



fernet = Fernet(key)

decrypted = fernet.decrypt(data)



return decrypted

For clarification pourposes I didn’t include any try/except section. So this not includes any protection against files that don’t exist or are misspelled.

The functions provided let you encrypt and decrypt a file. As you can see the Fernet() function is used. Again, if you want a little explanation of the function, here you have it.

Fernet(): This function has a very clear duty: Fernet guarantees that a message encrypted using it cannot be manipulated or read without the key. Well, the department of “easy tasks” has made his homework!

Final code calling the functions

If you are here then you’re prepared to do the homework. The final code to encrypt and then decrypt a file is as follows:

# generate key

key = generateEncryptionKey()# encrypt file using key

infilename = "Documento.txt"

outfilename = "Documento.txt.encrypted"

encryptFile(infilename, outfilename, key)# decrypt file using key

infilename = "Documento.txt.encrypted"

data = decrypFile(infilename, key)

You first generate the key using the env variable or providing a password instead. You use the key to encrypt a file or decrypt a file using the functions.

This is all you need to start working with secured files using simmetric encryption.

Conclusion

Python has a lot of capabilities offered to the developer community to make really secure systems.

You can, very easily and fast, write code to secure your systems. Now, you have no excuses on designing systems that are secure and easily maintenable.

I hope you enjoyed as I did with this handy approximation to cryptography.

Happy coding!