1878 films and counting...

PGP - Blog


Encrypted Email

We've supported PGP (for encrypting email) for a while, in a small way.

When you take a glance at our security.txt, you'll find a PGP Public Key, which will allow you to send us an email or file, whilst being able to encrypt and sign it so nobody but us can see the details.

That's an essential for disclosing security vulnerabilities.

However, until recently we haven't encrypted the emails sent by our server. Mostly because the system is mostly automated, and doesn't actually send anything sensitive when generating an automated email. Knowing an account name isn't enough to let you know which of our accounts it is, for example.

There is one part of the email system though, they might one day contain sensitive information. The contact form. The form sends us an email, and sets Reply-To to either the user's account, or a manually entered email address.

As of today, those emails to us will be encrypted and signed.

Just to demonstrate, here's an email we sent to confirm we had put everything together:

Date: Mon, 30 Sep 2019 19:51:27 -0700 (PDT) Content-Type: multipart/encrypted; protocol="application/pgp-encrypted"; boundary="===============3841450122723937794==" MIME-Version: 1.0 Subject: Testing Encryption From: [email protected] To: [email protected] Reply-To: [email protected]

--===============3841450122723937794== Content-Type: application/pgp-encrypted Content-Description: PGP/MIME version identification

Version: 1

--===============3841450122723937794== Content-Type: application/octet-stream; name="encrypted.asc" Content-Description: OpenPGP encrypted message Content-Disposition: inline; filename="encrypted.asc"

-----BEGIN PGP MESSAGE----- hQIMA2ZQP+t9PnXqAQ//VRenm9gJP6+u018hhKiIiPzpE/JBDU2DqkKg4TapP 889ZKL+vEMeLjyBNGmWTrKIVXz7/Q/zDWUZPKRXb6vTYGUTMS0p0sfYckJPfP FlYa5cHwPeJSeVsHEssOwn/cr5Ry+PM5L308zH9pdqxODJ+tkI93ApVo8dyWo bUZh7aQ3O31zsWHbrbr1/fHSpobS9rnx7zpsydV4ppL54hVUugy9cS5LmUZtQ dmsff4dWil92OW/IuKzPxUEsXHCKmAna24GTuVNUa1CX0D3TwNb/l3NXghbcs ZUmG2FA4X0l/7r4PcyIrx93PuOfAJBuk5HSsW5xWMxbm5A/TeE1hOrFuPmLpg /b9l7gbNFvTy3Pf7Jl4Y4CqDD8n0T3og3yoN2FsIn9aXIliaYoxcP0m17KTSI bOeKn5yq5Y7XxWSnNJ2CN3gS2tcwdiwEgbrrPjZQeO21V6nw2CPvopJrF02za 3dEH70BdhZ0REAWM4RPDQA5uAuyBgi+FeS5pWQWIQU8aGjITiMCQSMgZ6lFeu O+9S2fRdiWDGgYTFr0QPAKjgpThbAjWbz1XxGgIs0ZQaRCBbx7IVqY4Mabgc5 cmdxTsQpi6qvFg7SP6vpW5CnnZ0td9UA0N/Y+QO/oiziHapsCjozUBuEOpKFW 2i2dZw72qajuABP8WSoSe//9nSxC3pTnS6QH+fQTsBxPGW+rCUdYTDWNyGE4Z LQfk9/qRxT1oP7JGmPk0wi4j7EylklJDfniJXGXls/WhlEjN5j2KxBvM6kT5Q 1sfBCOxCnQYAuFmsU9kzyRTUXF0ECma1qs5aIlPkLx2zzyZHSrk7Hchn2lbUa 4X0XwYwQdhvrmCbBp2Ndwk2KJDZEdX822f0z0SAEve7al8DOXBMXRVSI53qNo x3/uX9wqtduA+U0n8fWhwKLI94bHgHOUFbUW0xhjMsYpLPGkKpuiY7fKDhtyW XpcboJdo0d7Vj8HyKST0qt5XKvkGaMFFQzHu1vrK7PLgFu7FEZdY1Ry/rgeDE zswxkgILafXWjaXRc5C1z0KMnXx+homQtvb2MdQPHj57srgVYxbmzaVF764Q3 0NAAaXlCmJbS63Wgf5Nm7JhiFi9owTW5mGAgiU5US78vTyBrKRTBFZvEAjNJt 3LCkL8SIrihJsY379a+fc0C135jqkdMOOPKZMYju2gQMBc34wfB/qFSsInwCi UgN3sj/nlPeM9PigbWZACIaPRY4B1c4Xn6MkMdmjwCMnteVN3CCt2NZl3QpXn 2bnGGCmPq+QKydYMsJrrHSTEr4aKeJesBYY3l7FikVbhFAjNEbnC69Qx0k2V0 G3kgwkHNiVZtSHZChQY2yM1vdar7T6f/GKU+eKQClJPz1twFOMGBAQsZaZjQL zrtqUeqqua3+ipUhF8vb5NaCgyHzkcxf3BG2/VI4pFpmW80HJyc9dku+H1tbu CfZ3uDarmgBk9J2IdIe9qphyOqTtLqAftTLYUb7oPfiFMwu0dUwEe2bhZtzOn J6Eq8dX6mtXwyxb7wqfZ3x/dQQRNvvN2EBeIJ1SDiZRiy+cHy4hh+blc5XXCA 5FXHgkkdJk95/cr3ZdVeWeBtqge4Wq+j2T6wLeH6+0hTu5ZUXji4PYoZtIH0E ZTe3/Y/ou0AeK456qyBy5cp+VFvTBl5nK4xbmDudEDqdhUdxp6669f5MQWjeh 1iPk74+jWbKZivxTup2YwC7f6R81jIG1Q6i7Eh+6VNICGCcY2w== -----END PGP MESSAGE----- --===============3841450122723937794==--

Note that we haven't hidden the Subject header. We could, but "Contact Form Response" isn't a particularly revealing header to begin with.

The Code?

The code is mostly just using primitives that comes with Python. It can be a bit hairy as the MIME and SMTP primitives are mostly just documented by type signature, and not by what they actually do. If you're looking for example code you tend to be on your own.

That being said, it wasn't especially onerous, and as long as you bear in mind that email isn't a fancy protocol, but rather a very simple text-on-the-wire protocol, you can go a long way.

You set up the container using the most generic MIME container Python has available, MIMEBase:

from email.mime.base import MIMEBase

msg = MIMEBase(_maintype="multipart", _subtype="encrypted", protocol="application/pgp-encrypted") msg['Subject'] = subject msg['From'] = from_addr msg['To'] = to_addr if reply_to != False: msg['Reply-To'] = reply_to # type: ignore

We have to use some underscore methods here, but mostly because Python doesn't have a MIMEPGP primitive, and we're basically building one from scratch. The methods aren't that scary - they'll basically do exactly what you say, so make sure you're saying the right thing.

Then, we need to add some information letting the email client know it'll be looking at PGP:

from email.message import Message

pgp_msg_part1 = Message() pgp_msg_part1.add_header(_name="Content-Type", _value="application/pgp-encrypted") pgp_msg_part1.add_header(_name="Content-Description", _value="PGP/MIME version identification") pgp_msg_part1.set_payload("Version: 1" + "\n")

And then, of course, we need to bundle in our payload.

pgp_msg_part2 = Message() pgp_msg_part2.add_header(_name="Content-Type", _value="application/octet-stream", name="encrypted.asc") pgp_msg_part2.add_header(_name="Content-Description", _value="OpenPGP encrypted message") pgp_msg_part2.add_header(_name="Content-Disposition", _value="inline", filename="encrypted.asc")

import base64

payload = "-----BEGIN PGP MESSAGE-----\r\n{payload}\r\n-----END PGP MESSAGE-----".format(payload=base64.b64encode(data).decode('utf-8'))


Basically here, we've got some binary encrypted data in a variable we call data, which we stick inside a very simple container that has the correct PGP block start and end. Because it's binary data in the end, we call it an octet-stream.

We create the payload this way so we can get the PGP block, if we didn't, then Python would helpfully generate a compliant Base64-encoded string of text... Which the email client wouldn't know what to do with.

Note: We use the Windows-style line endings because it used to be the standard on the web. Sometimes you can get away with a simpler line ending, but email is not one of the simpler parts of the web.

And finally, we bundle it all up and send it on the way:

msg.attach(pgp_msg_part1) msg.attach(pgp_msg_part2)

import smtplib

server = smtplib.SMTP_SSL(email_host, 465) server.login(from_addr, email_secret) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()

Continue reading...