Send GPG encrypted email from shell scripts and cron

GnuPG

GnuPG

Using GPG to encrypt outgoing email from a *nix system is important for both security and privacy. System admins commonly setup servers to automatically email them system log files and alerts. By encrypting these emails, important system details can be protected from prying eyes, while remaining viewable to their intended recipients. This article will cover asymmetrically encrypting outgoing email content on a remote system for a specific recipient/GPG public key, and sending the email from bash. For security, no passphrases or private keys will be stored on the system sending the email.

This how-to article assumes that:

  • You have a mail server or something similar configured for outgoing email on the remote server that will work with the sendmail or a similar mail-sending binary. If not, check out the article Setup Postfix with a remote SMTP relay host to quickly setup your system to relay mail thru Google or a similar email service.
  • You have a PGP/GPG keypair, and a capable local email client for decrypting incoming email. A popular client is Thunderbird with the Enigmail plugin, though I've heard GPGMail for MacOS X is good as well.
  • You have gnupg installed on your remote system. On a Redhat based distro, this can be installed via
    yum install gpgme

First, export the recipient's public key and send it to the system that will be sending the email. You can export a public key in Thunderbird/Enigmail by going to OpenPGP > Key Management , right click the appropriate key name, and choose Export keys to file, and then select Export public keys only (important: do not select the Export secret keys option, we only want to export the public key). You will be prompted to save the key as an .asc ASCII armored file, which can then be scp'd to the destination system. Alternatively, if you have an ssh session open with the destination system, you could simply select Copy Public Keys to Clipboard in OpenPGP > Key Management , then paste them into an arbitrary file using an editor on the destination system.

Let's assume the public key belonged to the email address foo@bar.com and is now located at /etc/foo.pubkey.asc on the remote system.

Typically, you would now import the public key and establish trust by running as every user on the system that needs to send encrypted email with commands such as:

gpg --import /etc/foo.pubkey.asc
gpg --edit-key foo@bar.com trust

But we're not going to do that. While this method helps keep the public key private and gives it a trust level on a per-user basis on the system, it can be tedious to setup, and requires a $HOME environment variable to be set for the encrypting/sending user, which can be troublesome for things like cron and web processes.

A shortcut can be to simply manually specify the public key and trust level when encrypting a file with the help of a few gpg options. Note this requires that the public key be in binary format. The conversion from ASCII to binary, along with the secure removal of the original ASCII file, can be done like this:

gpg --dearmor < /etc/foo.pubkey.asc > /etc/foo.pubkey.bin
shred -u /etc/foo.pubkey.asc

Also note that the public key is currently readable by all users of your system (depending on your default umask settings), so an attacker that's gained access to the system could potentially locate the key and send encrypted email to your recipient. It all depends on the file permissions you assign to the public key.

Let's test encrypting some arbitrary file, and then sending it to and decrypting it by the recipient client.

echo "some super secret text" > /tmp/message.txt
gpg --batch --armor --trust-model always --no-default-keyring --keyring /etc/foo.pubkey.bin --recipient foo@bar.com --encrypt < /tmp/message.txt > /tmp/message.txt.pgp

The above gpg command reads in the /tmp/message.txt file, and encrypts it to a new file in a non-interactive mode (--batch), ASCII armoring the output (--armor), specifies that the key is trusted (--trust-model always), which key to use (--keyring) and not to use the keyring in the $HOME/.gnupg directory (--no-default-keyring). If you cat the output file and you should see the BEGIN PGP MESSAGE message boundary and a big chunk of encrypted content. If not, it didn't work.

cat /tmp/message.txt.pgp
-----BEGIN PGP MESSAGE-----
Version: GnuPG v2.0.14 (GNU/Linux)

hQIMA5EtkWaTM1h9AQ//YFMYq6T15NwSCKIuXQL24pgDjJows6SSS40Mpl8FxKZt
GYKnFLEydPzTOVaddBGQ3LG9PRHOEb3scrCa1arX4jzLcrWRbxtb7xLCJZtsn4QL
CXdYgYHyvJ9OPG7NmkqUof9MClBdT036kpAJwx0q5KK56iyjbeBO/RxpqC5ODlDY
hKVK89vF1B6aX7FdZaFKYJmcLrlH9rjDLuPZfdBeELwabbavdrZy+e3JAaGDTYDE
E10FlAe9QlYXorS3prx65Msvka2hmXOPOK94G3jzSGNfQB0qqZoynb07u1JoSbps
UdD+Ry7X3dvL66W5sfZPK+9NfcwxkPjmTM1BlfW4PUCvVkDkm40HasWzHEUkXfmn
M1QdAshk1P5YvCNjGOc+Vu3e1k/X0pUKxr+NkWHZ9ovEmh+DZAsvfya0obPZZ+gE
Xj5qSlFugY2a4ByGDYEN9NSiuhUKbxP7FSkbYnbVo0iVisDpjfJfGTB3euzChZoQ
2Psw6vfuj9rKIpA1NFJp2pHy/MHI7wPz4WF5OXWfFINYpeeAHlaWWbHxAKKP/iC4
lB6vmiQ0fqT3fRzqwKuh8FiuxlLCp2qiyW1C8fEEetX3Sj9ac/eVuAxaHQma0KK/
8gIo80hICUce829qmD1Xwjr/NuUjc9zWzP4QFwoyqu8H+dLLWXvGRmXyW/B2fqbS
UgFX5pOsdbXJwMY7t44PZjoToxrcpK1alH7n8W/kK1sswogZgA7E0dBSk0J6Tvm4
sQ0Yw9sCGvPiVAR9RZ78nLS/j/5BjUal5cn8WFEI9xGnD3k=
=fmXI
-----END PGP MESSAGE-----

Now you can send it off to the foo@bar.com recipient. Let's assume the sender is sender@bar.com and fire it off with a sendmail one-liner:

echo -e "From: sender@bar.com\nTo: foo@bar.com\nSubject: test 123" | cat - /tmp/message.txt.pgp | sendmail -it

The above basically combines the email headers (From, To, Subject) and the encrypted file contents and pipes them to the sendmail command. The -i flag tells sendmail not to treat a "." as the end-of-line character, and -t tells it to extract recipients from the headers.

Check your recipient inbox for the email and ensure the body content can be decrypted.

Encrypting and emailing files in a script

The above commands would be tedious to type out whenever a file needs to be encrypted and emailed. The basic sample bash function below can be used to encrypt text files (such as log files and alerts) and send them to an admin. Make sure to set the PUBKEY, TO, and FROM variables with appropriate values (though with a bit of tweaking, these could be easily passed as parameters).

# sends a text file as GPG encrypted email body content to the TO address below
# params:
# $1        path of text file to encrypt 
# $2        subject line 
sendEncTextFile()
{
    PUBKEY="/path/to/binary/public/key"
    TO="recipient@address.com"
    FROM="sender@address.com"
    GPG="/usr/bin/gpg"
    SENDMAIL="/usr/sbin/sendmail"

    # read in the passed $1 parameter,
    # encrypt it for RECIPIENT. redirect stderr > stdout
    # for easier inspection of gpg error message
    ENC=$("${GPG}" --batch --armor --trust-model always --no-default-keyring --keyring "$PUBKEY" --recipient "$TO" --encrypt < "$1" 2>&1)

    # failed to encrypt. prepend error message to content and send clear text content
    if [ $? -ne 0 ]; then
        ENC="Failed to encrypt contents: $ENC"
        ENC+=$'\n\n'
        ENC+=$(cat "$1")
    fi

    cat <<EOF | "${SENDMAIL}" -t
From: ${FROM}
To: ${TO}
Subject: ${2}
Content-type: text/plain
${ENC}
EOF
}

Place the above in a file, source it, and call it like this:

sendEncTextFile /path/to/some/text/file "super secret email"

Since the script defines the full paths of the GPG public key and the necessary binaries, it should work with in cron'd scripts without having to set any environment variables.

Note about lines 20-24: these handle the case of failed encryption. It prepends the email body with the encryption error, and sends the file's contents in clear text. If you're emailing very sensitive info, it'd be best remove this code and handle errors differently.

Note: the sendEncTextFile function is meant to be used with text files so they can be set and decrypted as the email body. Other content type files should be emailed as multipart attachments.

Encrypting and emailing cron job output with MAILTO

Vixie cron has the option of specifying that a user or email address be sent the STDERR output of cron jobs. The recipient is specified in the /etc/crontab file in the MAILTO variable. The output of particular cron jobs can be GPG encrypted prior to being emailed to the MAILTO address.

Edit /etc/crontab, and set the following variables at the top with appropriate values:

MAILTO=recipient@address.com
GPG_CMD="/usr/bin/gpg --batch --armor --trust-model always --no-default-keyring --keyring/path/to/binary/public/key --recipient recipient@address.com --encrypt"

Note: the recipient@address.com address must be specified in both the MAILTO and GPG_CMD. The crontab's predefined variables do not support variable expansion.

Then append 2>&1 | $GPG_CMD at the end of each cron job, for example:

25 01 * * *  root  /path/to/some/script 2>&1 | $GPG_CMD

The above captures both STDERR and STDOUT and encrypts it prior to emailing it to the MAILTO address. It's also possible to capture and encrypt only STDERR by appending 2>&1 >/dev/null | $GPG_CMD at the end of any cron job, but this suffers a drawback: an email will still be sent even STDERR contained no output. One work around may be to write STDERR to a temp file and cat its contents if it's non-empty.

Other stuff to note

  • Email headers and the subject line are not encrypted. So be careful to keep sensitive information limited to the email body content.

Leave a Comment