blog

Photo of ENIGMA Rotor Set by brewbooks

Securing Your Forms With Braintree.js

by

Think back to the web of fifteen years ago. Most of the web sites of the time consisted of a few pages of content along with a contact page (and maybe even a guestbook.) Most often that contact page was backed by a script that mailed the results to a fixed e-mail address. Is anyone willing to admit to using formmail.pl? In the least, we can admit that we didn’t move data around in the most secure way.

Braintree.js is a smallish library (<41kiB minified, <14kiB minified and gzipped) that makes it simple to encrypt your form data before submission to the server. Today I’m going to use it to secure contact form data, but there’s no reason you can’t use it with any HTML form.) Let’s start with a simple contact form. There is nothing special about this form.

<!DOCTYPE html>
<html>
    <head>
        <title>Contact Us</title>
    </head>
    <body>
        <h1>Contact Us</h1>
        <form action="contact" method="post">
            <label>
                E-mail address
                <input type="email" name="email"></input>
            </label>
            <label>
                Message
                <textarea name="message"></textarea>
            </label>
            <input type="submit"></input>
        </form>
    </body>
</html>

I’ll also write a flask method that sends the message to my inbox:

@app.route('/contact', methods=['POST'])
def contact():
   text = 'From: {email}nn{message}'.format(
    email=flask.request.form['email'],
    message=flask.request.form['message'],
   )
   msg = MIMEMultipart()
   msg['From'] = gmail_user
   msg['To'] = gmail_user
   msg['Subject'] = 'Contact Form Submission'
   msg.attach(MIMEText(text))
   smtp = smtplib.SMTP('smtp.gmail.com', 587)
   smtp.ehlo()
   smtp.starttls()
   smtp.ehlo()
   smtp.login(gmail_user, gmail_pwd)
   smtp.sendmail(gmail_user, gmail_user, msg.as_string())
   smtp.quit()
   smtp.close()
   return 'Message sent!'

Note that I’ve defined gmail_user and gmail_pwd in the global scope. Let’s add the braintree.js library and some data attributes to the form inputs.

<!DOCTYPE html>
<html>
    <head>
        <title>Contact Us</title>
        <script src="static/js/braintree-1.3.3.js"></script>
    </head>
    <body>
        <h1>Contact Us</h1>
        <form action="contact" id="contact-form" method="post">
            <label>
                E-mail address
                <input type="email" data-encrypted-name="email"></input>
            </label>
            <label>
                Message
                <textarea data-encrypted-name="message"></textarea>
            </label>
            <input type="submit"></input>
        </form>
        <script>
            var braintree = Braintree.create('PUBLIC_KEY');
            braintree.onSubmitEncryptForm('contact-form');
        </script>
    </body>
</html>

As you can see, the changes are almost trivial. Unfortunately, that won’t cut it. We need a keypair. We can use OpenSSL to generate one:

$ openssl genrsa 4096 > private.pem
Generating RSA private key, 4096 bit long modulus
..........................................................................++
...........................................................................
...........................................................................
...........................................................................
....................++
e is 65537 (0x10001)
$ openssl rsa -in private.pem -pubout > public.pem
writing RSA key

Keep the private.pem file a secret (ignoring for a moment, the fact that I have checked it in to my public git repository.) Take the Base64-encoded value from public.pem and pass it to Braintree.create:

<script>
    var publicKey="MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAv7BVeNTu1PZk6eOnU6+2rz3MoLHpx5RODcOYS+EMFANI3F/zLRPdb6tkeZTy95RIEeQsxlQ+Hnwcwcq3zf+goWjxshLLCMwziMNUsCSLvd9lm5aLfVMGJTbUbJiZMmbTmBfADmE8TFPexgAWdsl3470ni2ODsKfHB02vzOV2KahqN36F8b8CeM/m0B+wLFfvujlGxVuU3q9cIRZGvvtkuG3keBZpEAjYzWwOLu+cpvRUAAhndJqLLw0FUJ1Sa+G1SyTEr2J4yxPLCnd6Hoill60wDa1ZFmc13RnPmvVbud5TwwaQ4j4KAXYiu3p2uP29mfQnQq8+qeGgcOKM4Vudtatwd4z7H/TK/tJyfx6gUW1uWQ+bedmzp/5ntIAgyFIETMy9J/FmHpN42i2plciGHWiZDRUCDeyOFygaBTExQWszj5JAa8imPCvHYmz+bhK7CVga7Ej/bHiLtu2nhr/BS9rz79ZqZQEFZS6XyuX9Xms6+tBB7nV4l4hywHAfJrrU8Kom+ysDMgncsT4GoyRFTMYPVkZ446pHEdOoVFj2ZwqMNZUQ1pL3eVK5qpQvbMjUJ8mCg78GO4rQlZ6F7TsLCdGuK30A/Z+h3+xQlpVwbACv6I3cIqA1dgWiYHk/3wbjshGj47Aqt8MVOL3MWFJVFqct535iWxiY02ZDQzNgz+MCAwEAAQ==";
    var braintree = Braintree.create(publicKey);
    braintree.onSubmitEncryptForm('contact-form');
</script>

With this in place, submitting the form results in strings like these on the server:

$bt4|javascript_1_3_3$nRaLNbS35mpDaK33mMLaePwIXmk2J6I3CnNvSucjqGwzD5I2akEbp
vWPAftp8/sIgpxxTgQzHRnje/SslcBA9w2WevYirURZMTNjMbkoQ66M6XQTR3LAvTMgfr1z1394
PZRr6MXn+jjou8luydaweBw7O/0ledkOk375M4FhHeCW46LLFx+490K+VJuRGt94PICTb9r/Zje
7FsPsAuzy/squ444N+a4WDrB/fmCWWMY2oQnWyHk+VTklLyenisqfJTW0gW7fFiq/AhVFJ4/29p
CDAOQ6CPGtKNv8cjABCpdrom7FIscB7qoEbPb5yCSfNjwITj4os2bMJN8wDlhDLGlnSvrH5pfxX
VRi7l2jE4+ogQMr/5WeH9EL/c4b6MpPcwuL07oCQscv6+FMvtJ7uSgKHo66+dMAFKRs03S4N3M5
eBSOiQhu/PWywd/CwTqZGfouIwLbKbBcwC7LaiA65jCS5jGHuaSLl2UceJXZURdyQTDYwRYjhAw
Cf3NDk3LFgn+LG0ZRWWHZuXSmoPoVNzHTKq8elE+WeDQPuNWlRBcLPynNSCtTLawDGpNykv0mW9
mk9N77vHPxp0fObwL5hjgu6dQL6zCV55Lg4iJeKrmAJqxbuvF+3dPpi01D7bHbu8UAzx1TdLazq
ODazZkOuJcDmVT0lhoBMj0pjWHm7rs=$LAxoEa0qlBCMjJObpi4XYd0kA/nmXCERqfJHKvrSdZ0
=$GFtjx84PXmyHZsM2Xxbqtluu/RCzdX1F1J3NvTaIwa8=

This string contains several values, separated by dollar signs. (You can follow along in the source code.) The first part, bt4|javascript_1_3_3 suggests that this is the fourth revision of Braintree’s encryption format.

Next is the Base64-encoded, RSA-encrypted, Base64-encoded (again) concatenation of the AES and HMAC(SHA-256) keys. After that, you’ll find the Base64-encoded, AES-encrypted value of the input, followed by the HMAC.

Great. So we have encrypted form values. Now what? It seems we should figure out how to decrypt them. Braintree doesn’t provide that part of the code (unless I missed it,) so I’ll have to figure it out. Since we need to keep the private key private, I’ll write this as a Python script that works from the command line. Here’s the meat of the script:

def Decrypt(rsaCipher, encryptedValue):
    """
    Decrypt Braintree.js-encrypted values
    rsaCipher - a PKCS#1 v1.5 RSA cipher object from PyCrypto (which contains
                the necessary private key information)
    encryptedValue - the encrypted string from braintree.js
    """
    # Get the version, keys, ciphertext and hmac from the user's input.
    parts = filter(None, encryptedValue.split('$'))
    version, keys, ciphertext, hmac = parts
    # The goal is to decrypt the ciphertext.  In order to do that, we need to
    # verify the HMAC, because it is best practice to authenticate ciphertext
    # before decrypting it.  To verify the HMAC, we need the HMAC key, which
    # is encrypted with our public key in the `keys` variable (along with the
    # AES key.)  So we have these steps:
    #
    # 1) Decrypt `keys` with our private key, and extract the AES and HMAC
    #    keys from it.
    # 2) Verify the HMAC(ciphertext, HMAC key) matches.  If not, report the
    #    error and discontinue the process.
    # 3) Decrypt the ciphertext and return it.
    # Step one: decrypt the keys with our private key.
    keysBytes = base64.b64decode(keys)
    keysDecrypted = rsaCipher.decrypt(keysBytes, None)
    if keysDecrypted is None:
        raise RsaDecryptionFailedError()
    keysDecoded = base64.b64decode(keysDecrypted)
    aesKey, hmacKey = keysDecoded[:32], keysDecoded[32:]
    # Step two: verify the HMAC.
    hmacCruncher = HMAC(hmacKey, base64.b64decode(ciphertext), SHA256)
    decodedHmac = base64.b64decode(hmac)
    if hmacCruncher.digest() != decodedHmac:
        raise HmacIsInvalidError()
    # Step three: decrypt the ciphertext.
    decodedCiphertext = base64.b64decode(ciphertext)
    initializationVector = decodedCiphertext[:16]
    ciphertextWithoutIV = decodedCiphertext[16:]
    aesCipher = AES.new(key=aesKey, mode=AES.MODE_CBC, IV=initializationVector)
    plaintext = aesCipher.decrypt(ciphertextWithoutIV)
    return plaintext

You can find the full source code in <a href="https://bitbucket.org/troyjfarrell/using_braintreejs/src">this git repository</a>. Here’s what it looks like to use the script:

$ python decrypt.py ../keys/private.pem
Encrypted value: $bt4|javascript_1_3_3$Swq3PPQTG1WyU+slkniGQNJMvt9z53t1JfeW
j3FacUl1ZdiDVtj/DCWqglbvhxHF5/wLDp0M+N824I2vt4CZPA04DFyYVeep3yOObviubA/FOHw
fUKhK8Kcy0axHEyIKwcN9eAglM1xO2kFGWTtZdJf1PFQZOarOgK4gLu60PwDzwt8aSmqu08Pv5z
QJkOin7Lyz6LsilxGQCKt2j6TtLv2jidI5NatZ+sJFSGPk1LVSgq8z9JloEZCCERxoD8KcIDYD+
t/9Xcyq1m/lellI1+/HmTWol9m96rHdG4GvfuG9edn4p41Qqfq239ShaemVyqRemA44u9g8UpMD
z4SDtzswSteL+mPSytxFNtnf/1WPVySC0fpBiriSJLAisX2Oev0NOe6doQbOrTbvHepssJoCrEX
894ZRzlR3tOanobPRbBQryemHgh/b5c+tFjFh3pAzu0v3tdEsLP3bAami32OInJI9znR+p+bH31
bi130b+sZYisaYrnyqb8exCyEgtCh5HjNCQfiKsRBw3qQACc10zRyXwiNV4iryOjOXkjJP6O4p9
3OTcnm8blvC9CJ4sUWg4WserbZKZuNL/14lqRd0P2gya/eH56QjnuCuXXvYuNDtQPQdMhMY58oA
oahLubYoUkhdylyOcOTW1c3cE5x3GuUy92Gj8PWQfoEM7mc=$4b20RSdQhrMjot6aC/X4j2dOQt
JnCJDz5yTX8poEmGH6G5lZg/LhHEtuBLfCX5Bb$Mw4UN5kfxuHRqU/6LTmflEny0fRmg5oqeQMn
c8DxbUc=
tfarrell@artandlogic.com
Encrypted value: $bt4|javascript_1_3_3$hpIVTaJlFQ0DQ7NcazBCDrZQ5oq+/tbHuOhd
bq8bYQrWO9AfUXjfNlx87qMBfvDqIXSZG1HCFWE6qIWcb/SILj51rdiwb2DnlXvGZxO1r0bfKgv
YHLNddWF+Kd7jAWu98Ht7NHhHSE3o8kYnDxN9x6On1seiAcAs75u5KZPe2Nz8pY0y3gPe3JHq/d
vZ8Q2OLVXE9ezfWGLjUJSnO7HtuU3M95pRZzQUEdAIh8ylBfmreh+uUKG+yg4f1vot5wB3yfovE
CQnZwgvMnEliDe//Aa6+DiwKHtp5DUjftwQL4UgORVvKMyZZ8aI+nmXr5xfQCMHPr4Xwpth3p+I
jh7OjtoneqBnpkHkjaY7pVVwrsZWvHMujtqHXyRc7YvfBUV1MInlOy4MZUK8XACnZ8xhyuDFUBh
ap4eD0us+0LkX8HAjDabrorvjZNkq0fRXtTcQYR1vMbLIxupJCBiA8/+J4V6csbo7UGgsN7XhT2
Gu1GM1b1wyzt6lrg/ibiVRlDA2z0A0Pkw7cbn+sL6OUFO8xCAfKM5UFUAvZInY5J/tWVV+ASheK
eIYw0AhpqgVizPIbVxPyY9nf2EORsx0PE5qvUHz65IN7v+2EvAL1Cp4ehTm286zq1dW+0QnUwSy
BdWnEJlG6qLpRKiPTNBdWdUn7Zknsy0FF2TlH125xjk0Evs=$dnHrp6HGOIFyxu4nysLF0eJfNb
pYSECXbEvhhX7S87XwTfSh6lkS+vDen3WcEkdvtzzvxgb7t8zfiTpZRJaMfQ==$7Hwsh8VYeVIa
Hoi1haNm2MV7CdUoMdTXNhUOw3a6InI=
Art & Logic is a great place to work!

It would be an easy task to write a parser that extracts and decrypts these strings for presentation.

At this point, you can see how it is trivial to encrypt form values. While we’ve done nothing to authenticate the user submitting the form, we have kept the information private and tamper-resistant. Next time you build a form which submits private information, whether for mailing or storing in a database, consider using braintree.js to keep that information private.

+ more

Accurate Timing

Accurate Timing

In many tasks we need to do something at given intervals of time. The most obvious ways may not give you the best results. Time? Meh. The most basic tasks that don't have what you might call CPU-scale time requirements can be handled with the usual language and...

read more