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.
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.
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.
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.