Skip to content

interglobalmedia/encrypt-decrypt-files-python

Repository files navigation

Encrypting/Decrypting Files and Strings

Python vs Command Line (macOS)

In cybersecurity, we want to be as efficient and swift as possible when executing our tasks. In this scenario, I want to compare encrypting and decrypting files and strings in Python vs doing the same thing via the Command Line.

First of all, in my Python approach to encrypting and decrypting a file, I wanted to make sure that I modularized my task functions. So I created the following files:

  • create_key.py
  • get_key.py
  • encrypt_file.py
  • decrypt_file.py

As for encrypting and decrypting a string, I created the following files:

  • encrypt_string.py
  • decrypt_string.py

Now to break things down and explain what each file does. I tried to create file names that describe the individual tasks they are responsible for. I call this "task modularization".

The create_key.py file creates an encryption key using the Python Fernet symmetric encryption method. It was created by Google and uses 128-bit AES keys. AES 128-bit encryption in of itself is safe against brute-force attacks. The only "wildcard" is the safety of the key. I would say that using symmetric encryption is best when you are encrypting and decrypting for yourself and don't need to share the key with others for successful completion of the process.

The get_key.py file's task is to load the key that has been generated. So for example, I make the following import statement in the decrypt_file.py, decrypt_string.py, encrypt_file.py, encrypt_string.py:

from get_key import load_key

And I also make the following import statement for Python Fernet in all the files except for get_key.py:

from get_key import load_key

All get_key.py does is load the key.key file which contains the encryption key itself, so importing Fernet, or anything else for that matter, is not necessary.

The difference between the code for encrypting and decrypting a file was minimal and therefore easy to set up modularly. It was a bit trickier, however, with the creation of the message_encrypt and message_decrypt functions. There, I had to make sure that the string which is encrypted is the same string that is then decrypted.

In the encrypt_string.py file, I had to make sure that I created a local variable inside the message_encrypt function that I would be able to import into the decrypt_string.py file and then use inside the message_decrypt function. So:

# store string in variable
message = 'super secret message'
# inside the message_encrypt function
# encrypt data
message_encrypt.message_encrypted = f.encrypt(message.encode())

message_encrypt is the name of the function, and message_encrypted is the variable name. This made it possible to include the following import statement inside the decrypt_string.py file:

from encrypt_string import message_encrypt

And this then made it possible to add the following inside the message_decrypt function thereby linking the encrypted message created in encrypt_string.py to the encrypted message imported from the encrypt_string.py file into decrypt_string.py, which resulted in the successful decryption of the encrypted message (string):

from encrypt_string import message_encrypt
# inside the message_decrypt function
message_encrypt.message_encrypted = f.decrypt(message_encrypt.message_encrypted.decode())

The above prevents the following error from taking place in Terminal when running the string related files containing the modularized functions:

File "/Users/mariacam/Development/cyber-projects/encrypt-decrypt-files-python/venv/lib/python3.11/site-packages/cryptography/fernet.py", line 86, in decrypt
    timestamp, data = Fernet._get_unverified_token_data(token)
                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/mariacam/Development/cyber-projects/encrypt-decrypt-files-python/venv/lib/python3.11/site-packages/cryptography/fernet.py", line 119, in _get_unverified_token_data
    raise InvalidToken

Lastly, it is important to note that in the message_decrypt function, the message represented by message_encrypt.message_encrypted passed to the f.decrypt() method, is chained to the decode() method instead of the encode() method. Not only are we decrypting our message, but we also have to decode it, since it was encoded when encrypted. Now the message is just "plain text" as it was originally when first created.

Using GnuPG to encrypt and decrypt strings and files via the Command Line

This was a lot of work, right? A programmatic approach will always be more complex than executing the same task via the Command Line. And in Cybersecurity, we won't necessarily have the luxury or even need to programmatically execute such tasks.

This is where tools such as gnupg come in. gnupg, aka gpg, which stands for GNU Privacy Guard, is

a complete and free implementation of the OpenPGP standard as defined by RFC4880 (also known as PGP). GnuPG allows you to encrypt and sign your data and communications; it features a versatile key management system, along with access modules for all kinds of public key directories. GnuPG, also known as GPG, is a command line tool with features for easy integration with other applications. A wealth of frontend applications and libraries are available. GnuPG also provides support for S/MIME and Secure Shell (ssh).

GNUPG's "claim to fame" is that

GnuPG is one of the tools that Snowden used to uncover the secrets of the NSA.

Encrypting a file with GnuPG

Next, I installed gpg using Homebrew so I could use it to encrypt and decrypt a file. If you have Homebrew already installed on your macOS, but don't have gnupg installed, run the following command in Terminal:

brew install gnupg

If you think that you might have it installed, run the following command in Terminal:

gpg --version

If you get back something like the following:

gpg (GnuPG) 2.4.3

Then you do have it installed. But if you get back:

zsh: command not found: gpg

You do not have it installed.

If you are on Linux, you would use the following command to install gpg using the package manager of your Linux distribution:

sudo apt install gnupg

After gnupg was installed, I ran the following command in Terminal:

echo Hello Maria! > greetings.txt

Note: On macOS, when we run the echo command, we should not use quotes around the string of words. It throws an error.

Then I ran the gpg command to encrypt my new greetings.txt file, and I included the use of a passphrase. I even use them when communicating with Github, even though I am connected via SSH. So:

gpg --batch --output greetings.txt.gpg --passphrase mypassword --symmetric greetings.txt

mypassword should be replaced with your actual passphrase value. This command creates the encrypted file greetings.txt.gpg in the same location as greetings.txt using its default AES256 algorithm. As for the --batch option, with GPG2, which is what GPG 2.4.3 is (Homebrew links GPG to GPG2), you have to use it with the --passphrase option.

After I executed this command, the following was returned in Terminal:

gpg: WARNING: unsafe permissions on homedir '/Users/mariacam/.gnupg'

First, to make sure that the .gnupg directory and its contents is accessible to me, I run the following command:

chown -R $(whoami) ~/.gnupg

The chown command changes the file ownership of a specific file or folder. Here, it pertains to the .gnupg folder in my home directory. whoami refers to the current user, which in my case, is me, and refers to the current user's username on the system. -R indicates that ownership of a folder is being changed. In my case, this is simply a confirmation of ownership, since I am the only user on my computer.

But this was not enough. I like to make sure, even though there are no other users at this time on my computer, that only I have access to the .gnupg folder. So first I ran the following command:

chmod 700 ~/.gnupg

chmod is used to change the permissions of files and directories. The above changes the permissions on the .gnupg folder to 700. And 700 means that only the creator and owner of the folder can Read, Write, and Execute on the directory. And group and others cannot perform any operation on the files in the directory.

Then I ran the following:

chmod 600 ~/.gnupg/*

Here, the permissions for the files inside the .gnupg folder are set to 600. This means the owner has Read and Write permissions on the files but not execution permissions.

Lastly, I ran the following:

chmod 700 ~/.gnupg/*.d

This is an extra and special command which is what made it possible for me to change the permissions on the .gnupg folder and contents to the proper settings. Inside the folder, there is a directory which by default does not have execution permissions when one tries to send keys without a keyserver specified:

drw-------  2 rik rik  4096 Jun  9 03:28 crls.d    <---- Notice this here

So *.d ensures that all .d directories inside have execution permissions. To view the source where I got this information from on Github, please visit This fixes the " gpg: WARNING: unsafe permissions on homedir '/home/path/to/user/.gnupg' " error while using Gnupg., and scroll down to kirvedx's comment from Jun 13, 2022.

Decrypting files with GnuPG

Next, I ran the following command to decrypt the greetings.txt.gpg file I just had encrypted:

gpg --batch --output greetings1.txt --passphrase mypassword --decrypt greetings.txt.gpg

And the following was returned in Terminal:

gpg: AES256 encrypted data
gpg: encrypted with 1 passphrase

Then, to clear the passphrase which was stored in the session, I ran the following command:

echo RELOADAGENT | gpg-connect-agent

Next, I wanted to verify that my decrypted file matched my original greetings.txt file, so I ran the following command:

diff -s greetings.txt greetings1.txt

And the following was returned in Terminal:

Files greetings.txt and greetings1.txt are identical

Success!

Note: the -s option is short for --sign, which stands for "sign a message". So basically, I am checking to make sure that the integrity of the file when decrypted was intact.

Related Resources