Simplifying File Encryption on Linux & macOS with GnuPG and Bash
Protect your data from unauthorized access with this robust command-line workflow for macOS and Linux.
Suppose you need to transfer a file or folder to someone without the risk of interception—whether over an unsecured network or via corporate email. In that case, encrypting your documents is essential.
While several utilities exist for macOS and Linux, notably GnuPG and OpenSSL, I recommend GnuPG. While attempting to use OpenSSL, I encountered recurring errors with specific files (typically those containing NUL bytes). A discussion on this topic highlights why GnuPG is generally the superior choice for this use case.
In this post, I present two GnuPG-based Bash functions compatible with Linux and macOS. These wrappers provide a streamlined command-line solution for encrypting and decrypting both files and folders.
Installation
First, we need to ensure gnupg is installed.
On macOS
We will use Homebrew, the popular package manager for macOS. If brew is not installed yet, run the following command:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
Once Homebrew is ready, install gpg:
brew install gnupg
On Linux
gpg is usually pre-installed. If not, use your distribution’s package manager. For Debian-based systems (Ubuntu, Mint, etc.):
sudo apt install gnupg
Testing the installation
Verify the installation by checking the version:
gpg --version
You should see output similar to this:
gpg (GnuPG) 2.4.5
libgcrypt 1.10.3
Copyright (C) 2024 g10 Code GmbH
...
Supported algorithms:
Pubkey: RSA, ELG, DSA, ECDH, ECDSA, EDDSA
Cipher: IDEA, 3DES, CAST5, BLOWFISH, AES, AES192, AES256...
Encryption
Using the raw GPG command
To encrypt a single file named a_file using the AES256 algorithm:
gpg --symmetric --cipher-algo AES256 a_file
GPG will prompt you to enter a passphrase.
Note: Choose a robust passphrase (at least 8 characters, mixing digits and special characters). If the password is too weak, GPG may ask for confirmation or reject it depending on your configuration.
A wrapper function for easier usage
The raw command doesn’t handle folders natively (you must archive them first). The function below handles both files and folders automatically. Add this to your .bashrc or .bash_profile:
function encrypt(){
to_encrypt=""
original_to_encrypt=""
output_file=""
other_input=""
is_folder=0
for param in "$@" ; do
key=$(echo "${param}" | cut -d"=" -f1)
value=$(echo "${param}" | cut -d"=" -f2)
if [ "${key}" = "--input" ] ; then
to_encrypt=${value}
original_to_encrypt=${value}
elif [ "${key}" = "--output" ] ; then
output_file=${value}
else
other_input=${param}
fi
done
if [ ${#to_encrypt} -eq 0 ] ; then
if [ ${#other_input} -gt 0 ] ; then
to_encrypt=${other_input}
original_to_encrypt=${other_input}
else
printf "Path to the file to encrypt: "
read -r to_encrypt;
original_to_encrypt=${to_encrypt}
fi
fi
if ! command -v gpg &> /dev/null ; then
printf "Required command 'gpg' not installed.\n"
return 1
fi
if [ -d "${to_encrypt}" ] ; then
if ! command -v zip &> /dev/null ; then
printf "Required command 'zip' not installed.\n"
return 1
fi
zip -r "${to_encrypt}.zip" "${to_encrypt}"
to_encrypt="${to_encrypt}.zip"
is_folder=1
elif [ ! -f "${to_encrypt}" ] ; then
echo "Encryption impossible: there is no file named ${to_encrypt}"
return 1
fi
if [ ${#output_file} -eq 0 ] ; then
output_file="${original_to_encrypt}.gpg"
fi
if gpg --output "${output_file}" --symmetric --cipher-algo AES256 "${to_encrypt}" ; then
echo "${original_to_encrypt} successfully encrypted!"
else
echo "An error occurred during the encryption of ${original_to_encrypt}"
fi
if [ ${is_folder} -eq 1 ] ; then
rm "$to_encrypt"
fi
}
Usage
Simply run:
encrypt
# Or with arguments:
encrypt file_to_encrypt
encrypt --input=my_folder --output=secure_archive.gpg
If the input is a folder, the script automatically zips it before encryption.
Decryption
Using the raw GPG command
To decrypt a file:
gpg --output output_file --decrypt a_file.gpg
A wrapper function for easier usage
This function detects if the decrypted file was a zip archive (from our previous step) and extracts it automatically.
function decrypt(){
to_decrypt=""
output_file=""
other_input=""
for param in "$@" ; do
key=$(echo "${param}" | cut -d"=" -f1)
value=$(echo "${param}" | cut -d"=" -f2)
if [ "${key}" = "--input" ] ; then
to_decrypt=${value}
elif [ "${key}" = "--output" ] ; then
output_file=${value}
else
other_input=${param}
fi
done
if [ ${#to_decrypt} -eq 0 ] ; then
if [ ${#other_input} -gt 0 ] ; then
to_decrypt=${other_input}
else
printf "Path to the file to decrypt: "
read -r to_decrypt;
fi
fi
if [ ${#output_file} -eq 0 ] ; then
if [[ "${to_decrypt}" == *".gpg" ]] ; then
output_file="${to_decrypt%.gpg}"
else
printf "Path for the decrypted file: "
read -r output_file;
fi
fi
if ! command -v gpg &> /dev/null ; then
printf "Required command 'gpg' not installed.\n"
return 1
fi
gpg --output "${output_file}" --decrypt "${to_decrypt}"
if [ $? -ne 0 ] ; then
printf "Error during decryption. Are the key and file valid?\n"
return 1
fi
# Auto-unzip logic
if unzip -t "${output_file}" &> /dev/null; then
if [[ "${output_file}" != *".zip" ]] ; then
mv "${output_file}" "${output_file}.zip"
output_file="${output_file}.zip"
fi
unzip "${output_file}"
rm "${output_file}"
fi
}
Usage
decrypt encrypted_file.gpg
# Or
decrypt --input=encrypted_file.gpg --output=my_secret_file