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) # try: 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 else: # 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) msgshell.attach(msg) 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) else: with open(filename, 'rb') as f: msg = email.mime.base.MIMEBase(mtype, stype) msg.set_payload(f.read()) email.encoders.encode_base64(msg) msg.add_header("Content-Disposition","attachment",filename=filename) msgshell.attach(msg) # # 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 try: ciphertext = subprocess.check_output( [ "/usr/bin/gpg", "--armor", "--homedir",PATH, "--batch", "--recipient",TO, "--local-user",FROM, "--encrypt", "--sign" ],input=bytes(msgshell.as_string(),"utf-8"),stderr=subprocess.DEVNULL ).decode() except subprocess.CalledProcessError: # something went wrong with gpg: exit, gracefully sys.exit(0) # # construct encrypted multipart message # gpgmsg = email.mime.multipart.MIMEMultipart( _subtype="encrypted", _data="This is an OpenPGP/MIME encrypted message (RFC 4880 and 3156)", protocol="application/pgp-encrypted") 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" gpgenc.set_payload(ciphertext) gpgmsg.attach(gpgcon) gpgmsg.attach(gpgenc) # # write email content to stdout # print(gpgmsg)
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.