Let a Server Send Encrypted and Signed E-Mails

Here’s a small Python program that generates code for an encrypted and signed e-mail in PGP/MIME format.

#!/usr/bin/env python
# BlackBox multipart e-mail generator
# Frank Abelbeck, Mai 2016
# Usage: genmail.py Messagetext Subject [File1 [File2 [... FileN]]]
# prints the message text to stdout
import email.encoders
import email.mime.multipart
import email.mime.audio
import email.mime.image
import email.mime.base
import email.mime.text
import mimetypes
import sys
import os.path
import subprocess

TO = "To.Address@To.Domain.TLD"
FROM = "From.Address@From.Domain.TLD"
PATH = "/path/to/a/directory/containing/a/keyring/"

# construct core message, either as text or as multipart (in case of attachments)
        messagetext,subjecttext = sys.argv[1:3]
except ValueError:
        sys.exit(0) # wrong usage, nevermind for this should not be used interactively

if len(sys.argv) == 3:
        # create simple text message
        msgshell = email.mime.text.MIMEText(messagetext)
        msgshell["Subject"] = subjecttext
        msgshell["To"]      = TO
        msgshell["From"]    = FROM
        # with attachments: create multipart message
        msgshell = email.mime.multipart.MIMEMultipart()
        msgshell["Subject"] = subjecttext
        msgshell["To"]      = TO
        msgshell["From"]    = FROM

        msg = email.mime.text.MIMEText(messagetext)

        for filename in sys.argv[3:]:
                if not os.path.isfile(filename): continue
                ctype,encoding = mimetypes.guess_type(filename)
                if ctype is None or encoding is not None:
                        ctype = "application/octet-stream"
                mtype,stype = ctype.split("/",1)

                if mtype == 'text':
                        with open(filename) as f:
                                msg = email.mime.text.MIMEText(f.read(), _subtype=stype)
                elif mtype == 'image':
                        with open(filename, 'rb') as f:
                                msg = email.mime.image.MIMEImage(f.read(), _subtype=stype)
                elif mtype == 'audio':
                        with open(filename, 'rb') as f:
                                msg = email.mime.audio.MIMEAudio(f.read(), _subtype=stype)
                        with open(filename, 'rb') as f:
                                msg = email.mime.base.MIMEBase(mtype, stype)

# create PGP/MIME message using subprocess and the gpg program:
#   msgshell.as_string() ---bytes---> /usr/bin/gpg ---string---> ciphertext
# gnupg parameters:
#   --armor               create ASCII output
#   --homedir PATH        use keyring stored in given directory
#   --batch               do it non-interactively
#   --recipient TO        use TO user's public key for encryption
#   --local-user FROM     use FROM user's secret key to sign the message
#   --encrypt --sign      encrypt and sign the message
        ciphertext = subprocess.check_output(
                        "--encrypt", "--sign"
except subprocess.CalledProcessError:
        # something went wrong with gpg: exit, gracefully

# construct encrypted multipart message
gpgmsg = email.mime.multipart.MIMEMultipart(
        _data="This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)",
gpgmsg["To"] = TO
gpgmsg["From"] = FROM
gpgmsg["Subject"] = subjecttext

gpgcon = email.mime.base.MIMEBase(_maintype="application",_subtype="pgp-encrypted")
gpgcon["Content-Description"] = "OpenPGP/MIME version identification"
gpgcon.set_payload("Version: 1\n")

gpgenc = email.mime.base.MIMEBase(_maintype="application",_subtype="octet-stream")
gpgenc["Content-Description"] = "OpenPGP encrypted and signed message"


# write email content to stdout

My storage server uses this script to notify me in a secure way, even when sending files. By using PGP/MIME, all attachments are wrapped in a multipart message which in turn is encrypted and signed as a whole.

In order to sign its messages, the server needs access to its own private key, in a non-interactive way. Thus the private key is not protected by any passphrase. This is a calculated risk; if the machine gets compromised, I’ll have to untrust the key.