Encrypting Your Messages With OpenPGP.js

terribleimage

Terrible mashup of OpenPGP.js logo and source code by the author (because
nothing says “Where’s the source?” like a bad logo.)

Last time I wrote, I showed you how to use Braintree.js to encrypt form values. I even built a contact form to do it. It occurred to me that there might be a better technology for encrypting contact form data. (There is.) Of course, I’m not the first person to have this idea.

This is going to be a short demonstration of how you can use OpenPGP.js to encrypt a message submitted to your contact form. Most of the code is unchanged from my previous post. You can find all the source code in this git repository.

Let’s add OpenPGP.js to our form. Since OpenPGP.js is not specifically designed to encrypt forms, we’ll have to write that code ourselves. Note that I’ve included my public keys in a script block. I’ll explain how to generate it below.

<!DOCTYPE html>
<html>
    <head>
        <title>Contact Us</title>
        <script src="static/js/openpgp.js"></script>
        <script>
var publicKeyString = "-----BEGIN PGP PUBLIC KEY BLOCK-----n" +
"Version: GnuPG/MacGPG2 v2.0.18 (Darwin)n" +
"Comment: GPGTools - http://gpgtools.orgn" +
"n" +
"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" +
"Wprv9T/Tn" +
"=ZD8Kn" +
"-----END PGP PUBLIC KEY BLOCK-----";
        </script>
    </head>
    <body>
        <h1>Contact Us</h1>
        <form action="contact" id="contact-form" method="post">
            <label>
                E-mail address
                <input type="email" id="email"></input>
            </label>
            <label>
                Message
                <textarea id="message"></textarea>
            </label>
            <input type="submit"></input>
        </form>
        <script>
            function OnSubmit(ev)
            {
                openpgp.init();
                var publicKeys = openpgp.read_publicKey(publicKeyString);

                var email = document.getElementById("email");
                var message = document.getElementById("message");
                var plaintext = "From: " + email.value + "nn" + message.value;
                var ciphertext = openpgp.write_encrypted_message(publicKeys,
                 plaintext);

                var hiddenInput = document.createElement("input");
                hiddenInput.type = "hidden";
                hiddenInput.name = "message";
                hiddenInput.value = ciphertext;

                contactForm.appendChild(hiddenInput);
            }

            var contactForm = document.getElementById("contact-form");
            if (contactForm.addEventListener)
            {
                contactForm.addEventListener("submit", OnSubmit, false);
            }
            else if (contactForm.attachEvent)
            {
                contactForm.attachEvent("onsubmit", OnSubmit);
            }
        </script>
    </body>
</html>

Since we’re still using asymmetric encryption, we need a keypair. I’ll create
one with GNU Privacy Guard (GPG):

$ gpg --gen-key
gpg (GnuPG/MacGPG2) 2.0.18; Copyright (C) 2011 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Please select what kind of key you want:
   (1) RSA and RSA (default)
   (2) DSA and Elgamal
   (3) DSA (sign only)
   (4) RSA (sign only)

Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048) 4096
Requested keysize is 4096 bits
Please specify how long the key should be valid.
         0 = key does not expire
      <n>  = key expires in n days
      <n>w = key expires in n weeks
      <n>m = key expires in n months
      <n>y = key expires in n years
Key is valid for? (0) 0
Key does not expire at all
Is this correct? (y/N) y

GnuPG needs to construct a user ID to identify your key.

Real name: Example Key
Email address: tfarrell@artandlogic.com
Comment:
You selected this USER-ID:
    "Example Key <tfarrell@artandlogic.com>"

Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? O
You need a Passphrase to protect your secret key.

We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
gpg: key 42CDC257 marked as ultimately trusted
public and secret key created and signed.

gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0  valid:   7  signed:   1  trust: 0-, 0q, 0n, 0m, 0f, 7u
gpg: depth: 1  valid:   1  signed:   0  trust: 0-, 0q, 0n, 1m, 0f, 0u
gpg: next trustdb check due at 2014-02-23
pub   4096R/42CDC257 2013-06-11
      Key fingerprint = 67C2 7250 4E45 658F 9DB5  4F7C 15F8 7920 42CD C257
uid                  Example Key <tfarrell@artandlogic.com>
sub   4096R/8CDD7483 2013-06-11

Now, the keypair are on your GNU Privacy Guard keyring. You can export the key with its identifier:

$ gpg --armor --export 42CDC257
-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: GnuPG/MacGPG2 v2.0.18 (Darwin)
Comment: GPGTools - http://gpgtools.org

mQINBFG3STIBEADFAIWyvicTt6JOoBYVRQ3UfNV7KsX6m8Na+BrRIGNpYiYv6Jsu
...
Wprv9T/T
=ZD8K
-----END PGP PUBLIC KEY BLOCK-----

Now, we have a form that takes the provided e-mail address and message and
encrypts them with your OpenPGP public key. I’ve already written about tools
you can use to read OpenPGP messages. Here’s what I see when I read the
message using Mailvelope:

mailvelopeinaction

And here’s what it looks like on the command line:

$ gpg
gpg: Go ahead and type your message ...
-----BEGIN PGP MESSAGE-----
Version: OpenPGP.js v.1.20130420
Comment: http://openpgpjs.org

wcFMAyr4lPaM3XSDARAAuqOhlLCZWZNVjMFu68bPDLgXNOEhrz++0ZCUe5Cu
Y3hKSgCeA0STWyPGHqd77w/jUFSazVv0UUd5+xAD8JnYlxr+7bZl0sKa0lvx
4TjHrgtRd3NbY0Ve1UdoAJbekho5fay2+LlZ7IK3uDs2EidWLT+51SW8s08f
L0MPUEassL/C70KE+XT6b2yhUIcD/QGHUQUdgTYH+PN07hhwt1JxduH06uNP
JZKQZ5Z7UGlQnZaioCmmslO8LY5QrwR14U7GW4AqWtNdilRWvqXo2RC8L6L/
OP8vYkM4GyinTaneCmBqhOqxkc8jHZ3r1/l1KO8uqbj0dI5TPQu/LOvYsP4I
keoiz04EllRzCJ4YNc1SD4Kv+4qrNDLTuNjfMediuoLljKmoMarcR1H/jNlM
ebjp5KRlXQcSDo5w9nJBX0eSWOro4TkND89/+02fbKjdba7Am/Uf0S4u13pq
mYJ8pl7YDdSM2KlALtunmYKtp2wynJjVGLQ42JCSq+6FvX/MZDmU1p4/Vz9S
aVZuBo7f3aTai9/eo4uDsfzLQKG8Ex/EAkuusJRfMzoY9ktDVNjIPvUCIhVq
56XPabplvvalkHKo38AB4U+yr0tLeG5EcLMTCJ8L2AsPhlWlTSBIcraFIVdl
9Y7ZsKJqSdLcEvhzi6SlzyZdZm0fsBmYoSB0ZE1UvN/ScwGTWFcQfj7bHcwX
XxSgHiInmPVk8S0Riym5G3fFbLZQB75GQZP6W6ZqGNxVCSL0T7/zMl9nZSZL
tO0YQpsgNy3WIXrzAraHHM1MXeJ1HzTHLAvChkNk2/jUpjf2tKul02c3ktm3
57FYK+TS3z/5mpD2350=
=JhKY
-----END PGP MESSAGE-----
You need a passphrase to unlock the secret key for
user: "Example Key <tfarrell@artandlogic.com>"
4096-bit RSA key, ID 8CDD7483, created 2013-06-11 (main key ID 42CDC257)

gpg: encrypted with 4096-bit RSA key, ID 8CDD7483, created 2013-06-11
      "Example Key <tfarrell@artandlogic.com>"
From: annie@wgnradio.com

Be sure to drink your Ovaltine.

I’ve now provided two examples of how you can use JavaScript-based encryption to secure form data. If you use one of these with HTTPS, submitted information is private in transit and at rest. What’s stopping you?

Troy Farrell

Troy Farrell

Troy Farrell

Latest posts by Troy Farrell (see all)

Tags:

Creative Commons License

This work is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International License.

1 Comment

  1. AC

    Nice. This is a good article for people that want to code it themselves. So far the easiest way I have found to make a PGP contact form is https://fncontact.com, which is pretty awesome. It does PGP in the browser using kbpgp.js and handles creating the keys as well. There’s also a management console where you can read the messages, customize the form, set up notifications, embed it, etc…