How to keep your data safe using PGP Keys with Python

Introduction

Every day, there are roughly 2.5 billion gigabytes of data created, and a large portion of that is transferred over the internet every second. Today, companies are investing millions of dollars to ensure that their (and your) data are transported safely and securely. However, some open-source packages can provide some security to your data transfers without the need for a big budget.

Enter PGP keys!

PGP Key Pairs

PGP keys are based on the popular encryption standard (Pretty Good Privacy), which is commonly used to encrypt and decrypt emails, files, text messages, and so much more. It provides a way to encrypt and decrypt data, and create and verify digital signatures.

PGP keys are generated using key pair generators, such as GnuPG.

This will generate 2 keys, a public and a private key. Both are technically called keys; however, they operate more like a key (decryption) and lock (encryption).

The lock is the public key. This is the key that is given to whoever needs to encrypt data. If this key were to be leaked, no harm will come of it because the only thing a person could do with it is put a lock on stuff — hence the name. Think about it, would a lock be useful without a key to open it?

Meanwhile, a private key is the key used to decrypt data. It is best practice for this key to be kept by the person or company that is generating the pair. So, the only key needed to be distributed is the public key. Leaking the private key could be considered a security breach.

Generating PGP Keys with Python

Now that we have a good understanding of what is going on, let’s write some code!

To generate PGP keys with Python, you will need a library that supports PGP. For this article, we will use an open-source python library named python-gnupg. This library provides a Python interface to GnuPG, allowing us to use PGP keys in our Python scripts.

First, we need to install and import the gnupg package and instantiate the gnupg class.

pip install python-gnupg
import gnupg

gpg = gnupg.GPG()

Before we can generate the keys, we must configure our settings for the keys.

To do this we need to call the ‘gen_key_input’ method. This method takes any parameters we pass in and turns them into a special string that tells our key how exactly we want it configured.

This method allows us to customize an enormous number of settings on our keys, such as what type of encryption algorithm is used, the length of our key, the type of key, etc. These settings can get very complex, but for the sake of simplicity, we will stick with some of the basic settings.

input_data = gpg.gen_key_input(name_email = '123@example.com', 
                               passphrase = 'examplepassword', 
                               expire_date = '365d')

Here, we’ve created our input data with our email and passphrase. These parameters will be important when we encrypt and decrypt our data.

We also specified our desired expiration date. Our key will expire in exactly a year. To get the same result, we could have passed in ‘52w’, ‘12m’, ‘1y’, or even a UTC date format string.

Speaking of expiration dates, there are just a couple of things to keep in mind. Expiration dates only apply to the public key, private keys do not expire! Also, not passing in a date means the public key defaults to no expiration date.

Now that we have the input data for our keys, we can finally generate them with our desired settings. We can do this using the ‘gen_key’ method and passing in the input data that we created earlier.

keys = gpg.gen_key(input_data)

public_keys = gpg.export_keys(keyids = keys.fingerprint)

private_keys = gpg.export_keys(keyids = keys.fingerprint, 
                                             secret = True, 
                                             passphrase='examplepassword')

After we generate the keys, to use them, we need to export them. To do this, we needed to call the ‘export_keys’ method.

For public keys, the only parameter we need to specify is the ‘keyids’. This is a parameter that is used to identify keys. It’s common practice to use the fingerprint of the keys, as its key id. I would advise just making sure this is unique throughout ALL of your other key pairs since it can get messy searching for keys with the same key ids later on.

Exporting private keys is a little different. To indicate that this key is our private one, we must set the ‘secret’ parameter to true and we pass in the passphrase. We also have to make sure that the passphrase matches what we passed into the input data earlier, or your private key will not be successfully exported.

Writing Keys To Files

The next step is to write our keys to files to be stored and distributed as necessary.

Think of it as we are putting our key and lock set into a secure box so that we can use them later on!

with open('public_key.asc', 'w') as f:
    f.write(public_keys)

with open('private_key.asc', 'w') as f:
    f.write(private_keys)

Writing your newly generated keys to files is a good way to distribute the public key to those who will need it, and store your private key. Remember, it’s best to have the party that is receiving encrypted data generate the keys and distribute the public key as needed. It could be done the other way around, but just make sure the private key is distributed securely. We don’t want that to get leaked!

Encrypting and Decrypting Messages

Encrypting Messages

Now that we have our keys generated and key files stored, we can use these keys to encrypt messages.

with open('public_key.asc', 'r') as f:
    public_key = f.read()

#To import our public key, we must pass in the contents of the public key
#from the file we copied it to
public_key = gpg.import_keys(public_key)

data = gpg.encrypt('hey', public_key.fingerprints)
# or data = gpg.encrypt('hey', '123@example.com')
# or data = gpg.encrypt('hey', *keyid*)

print(data)

# -----BEGIN PGP MESSAGE-----

# hQEMAyf3sTn0/RtuAQf/US+yg9wVJrCw/hKVTufv75Yt8BJe9iWVlnac8DyxoCFD
# vAHNtWFuxAMG2zkO8z39NzuM/TPsmysEhLUVdyfi9z2F+GfI2r31aaWa5ZgbxbHn
# d2/c7HzXQQeONR34rjcHSIAfSIO4An0dua25pS+srIM0mncZ0Ds5vZdz6gqgR5un
# sX32LHvc3lnqN3Q39PdcMXsyzx8BtOQ+3CJVtre3pQBX27obmHral7Y4JKQamFEX
# 0x/tNLRri1Yw1OQBtFHXbodOBit0L1SerXteEuVgVAy0jN89F9uXaiz47EJ/LW8R
# Utd5/Xnm7IAXk1VxbDiCKMGOZxxtYk8LH4MFvHQzT9I+AdCcYhPAMqV+b/BIw/bW
# rVKWDd0Golx/xQL5iKM9MNO0/oPX0erOpDajr+Xceq+MVtJSB6LRR/hZgyyE750=
# =glj4
# -----END PGP MESSAGE-----

In this example, we used the contents from our public key file to import our actual key. After we imported our public key, we used the ‘encrypt’ method to encrypt our desired message — ‘hey’.

Note that the second parameter in the encrypt method can be either a public key’s fingerprints, name_email (or whatever parameter that can be used to identify a recipient), or keyid parameters. This parameter is used to identify which key should be used for encrypting.

This is important because we want to make sure our keys match, or we won’t be able to decrypt them later on.

Then lastly, we printed out our newly encrypted message. Safe to say it’s encrypted now!

Decrypting Messages

The code to decrypt messages is similar to encrypting them.

with open('private_key.asc', 'r') as f:
    private_key = f.read()

private_key = gpg.import_keys(private_key)

decrypted_data = gpg.decrypt(data.data, passphrase ='examplepassword')

print(decrypted_data)
# hey

After we’ve imported our private key, we need to call the ‘decrypt’ method and pass in the ‘data’ attribute of the encrypted message. This will pass in the string version of our encrypted message. We also need to pass in the passphrase of our private key to use our key to unlock our locked message.

After we’ve passed in the necessary parameters, we can print our original message!

Encrypting and Decrypting Files

Encrypting Files

The process to encrypt files is very similar to encrypting data.

with open('public_key.asc', 'r') as f:
    public_key = f.read()

public_key = gpg.import_keys(public_key)

with open('test_file.txt', 'rb') as f:
    status = gpg.encrypt_file(
        f,
        recipients = public_key.fingerprints,
        output = 'test_file.encrypted'
    )

# -----BEGIN PGP MESSAGE-----

# hQEMA5SYFrbmRipCAQf/dj3EkEESjWw/IPY/6jWZ1BIA29bzMS4oRokYUEjHtY8V
# JWv02EBeZ5pPLn3MWzD20K4u1m7xqFLVBjCs5pL3SpSQy9ltqB10vuS/B+yZeO3p
# sTGJct/0QWkx/0A4CF7lz+oYNc1nJiTYGMbDzCnrib3CAS/StoxEi083dxmISmST
# 67+5VGTPZ/xhThXINN4y/HdmBfQ2pLgRG3nTD6ZTEGnqW0ATGbayGS7m7VcPPpsW
# zzO5XU0CBUFwAqAKH4DfEdTjd6EcEGmAkuTabYQbcSCNjQOmErzzW0NFmX97woT4
# ndRS2Bye+guqhXR1jXzO2zlL3vbiqjgZo8LwfCo949JLAWMyiwriQbu94Nii719x
# PG5ellA3SFMc7ZrZPx7MPRbp/wQWJsiT3tTX2QdOlSYDAa/vaAFsVI+ZPXgYZeyq
# jidYIbnuIrejkhwT
# =DMGT
# -----END PGP MESSAGE-----

Like before, we can only encrypt after we’ve imported our public key.

After we have selected our desired key to use, we must open our selected file with our message inside and call the ‘encrypt_file’ method. Here, we must pass in our file, recipients, and the desired name of our output file.

Decrypting Files

Finally, let’s decrypt our file!

with open('private_key.asc', 'r') as f:
    private_key = f.read()

private_key = gpg.import_keys(private_key)

with open('test_file.encrypted', 'rb') as f:
    status = gpg.decrypt_file(
        f,
        output = 'test_file.decrypted',
        passphrase = 'examplepassword'
    )

# This is a test lol

We must import our private key, as per usual. Then we have to open and pass in our encrypted file as a parameter to the ‘decrypt_file’ method, as well as the name of our future decrypted file, and the password to our private key.

Voila! We see the message from our original file!

Conclusion

With the enormous amount of data traveling across the internet every second, the importance of encryption is paramount for every company and even your applications. Using PGP keys with Python is a powerful way to secure your emails, server messages, files, and other types of data. The python-gnupg library provides a convenient and open-sourced way to use PGP keys in your Python scripts or web applications. Thus, providing a cost-efficient way to secure your data!