Last year’s revelations show that crypto is broken on all levels.1 We cannot trust hardware nor commercial software providers anymore to securely encrypt our data. My first instinct as a developer is to turn to open source libraries which have been tested and validated by the community. Unfortunately, unless you are a crypto expert, it is tremendously difficult to get it right as my experiment shows.
In this article I outline why it is virtually impossible to use regular open source crypto libraries like OpenSSL and how we can start to fix crypto using the NaCl library. I complement this article with a GitHub project demonstrating how to encrypt messages with state of the art crypto in a secure way.
So why is it so hard to implement crypto functionality? Let me take you with me on a little experiment in which I encrypt a message securely.
The state of the art in securely exchanging messages is called hybrid cryptography. It works in three steps. First, you take a message and encrypt it with a random symmetric key. Second, the symmetric key is encrypted with the asymmetric public key(s) of your recipient(s). This way, you have the security of asymmetric key exchange and the performance of symmetric encryption.2 At last, you send the encrypted symmetric key and a signature in a so called envelope together with the symmetrically encrypted data.
It’s Bad — The Assembler For Crypto Code
I started my experiment with the standard open source crypto library: OpenSSL. It is the crypto foundation of a plethora of software products and has a native C API with bindings for almost all programming languages.
But this library — as others of its kind — only offers the most primitive crypto routines. The number of steps to implement hybrid encryption with OpenSSL is mind-boggling. And each step consists of multiple function calls, memory allocations, error handling, and format transformations:
- Choose algorithms and parameters, e.g. AES 256 bit, RSA 4096 bit etc.
- Generate RSA key pair
- Generate random AES key and nonce3
- Use AES key to encrypt data
- Hash encrypted data
- Read RSA private key from wire format
- Use key to sign hash
- Read recipient’s public key from wire format
- Use public key to encrypt AES key and signature
This approach is like using assembler to write your application: cumbersome and error-prone. There are so many possible mistakes.
For example, the developers of Bitcrypt, a malware that encrypts your hard drive and blackmails you to pay for the decryption key in BitCoins, used a 128 byte (1024 bits) RSA encryption theme. At least that’s what they thought. As reverse engineers found out, they only used a 128 digits long number. This mistake rendered the crypto to 426 bit RSA which was broken in under 48 hours.
It Gets Worse — The Black Magic
But it still gets worse. Even after you got the code right, the journey to secure encryption does not get any easier. You may select wrong parameters, e. g. random values for nonces and key generation. Or did you know that the standard glib random function is based on LFSR and thus easily predictable and inherently insecure?
What about generating a asymmetric key pair? Here you go. Let’s create an RSA key pair with OpenSSL:
int RSA_generate_key_ex(RSA *rsa, int bits, BIGNUM *e, BN_GENCB *cb);
You need to pass a buffer to write the key to (
*rsa), the key length to generate in bits (
bits), an exponent (
*e), and a call back (
*cb) in case you want to generate the key asynchronously. Wait a minute. An exponent? What’s that and how can I pick a value for it?
The documentation “The exponent is an odd number, typically 3, 17 or 65537”. Alright, typically. So can I choose 42? Of course not! It turns out that everything but 65537 is considered insecure by today’s standards. This is like black magic!
Selecting random values and generating an RSA key are just the first two steps of the above list. Seven more to screw up.
Abstraction to the Rescue — NaCl
So we should agree that OpenSSL is only useful for crypto experts. The rest of us need a simpler abstraction. Fortunately, Daniel Bernstein (djb)4 and his colleagues did not only point out the problem, they also offer a solution. They have created a high performance, open source crypto library called NaCl (pronounced salt) that gives us exactly what we need: A simple to use and secure API for crypto.
NaCl is an opinionated library. It gives us access to the secure application of cryptography, because it has predefined settings and therefore saves us from learning black magic. The authors chose three algorithms and the corresponding parameters: curve25519 for elliptic curve asymmetric encryption, Salsa20 for symmetric encryption, and poly1305 for message authentication.
Even though these algorithms are not as commonly known as RSA and AES, they are widely adopted for crypto tasks and have been crypto-analyzed during open contests like eSTREAM. Curve25519 uses smaller keys and is faster than RSA. Salsa is way faster than AES.
NaCl currently only offers C and C++ APIs. Remember the nine steps plus function calls above? In order to achieve the hybrid encryption described above, all we need with NaCl are three function calls.
Let me show you how to encrypt and decrypt with NaCl:
// Create asymmetric key pair
// Pick a symmetric key and a nonce, then symmetrically encrypt the message
crypto_secretbox(ciphertext, message, message_len, nonce, key);
// Pick another nonce and then asymmetrically encrypt key with nonce2,
// public key of recipient, and secret key of sender
crypto_box(enc_key, key, key_len, nonce2, reciepient_public_key, sender_secret_key);
Creating a key and a nonce requires random data. On Unix based systems you can read either from /dev/random (truly random, but may block if not enough randomness is available) or /dev/urandom. When in doubt, always use /dev/random — see below.
Now you can send the envelope (
nonce) as well as the encrypted message (
ciphertext) to the recipient. NaCl automatically takes care of computing the message authentication for both symmetric and asymmetric encryption. Therefore, the
ciphertext buffer always has to be
crypto_box_MACBYTES bytes larger than the original message to fit the MAC.
To decrypt the message, there are two more functions:
// Decrypt symmetric key
crypto_box_open(key, enc_key, enc_key_len, nonce2, sender_public_key, recipient_secret_key);
// Use symmetric key to decrypt message
crypto_secretbox_open(message, ciphertext,ciphertext_len, nonce, key);
NaCl is highly optimized and uses your processor’s capabilities to their maximum. For this, you need to compile NaCl for your specific hardware. This makes NaCl difficult to distribute as a binary. Fortunately, the authors of DNSCrypt created a wrapper library called libsodium that facilitates the use of NaCl on various platforms, even for iOS and Android. There are also bindings for various programming languages like Java, Python, Erlang etc.
Moreover libsodium adds the function
randombytes_buf that fills a buffer with random data. You can configure libsodium during build time to use /dev/random or /dev/urandom with the configure parameter –enable-blocking-random.
Shick Crypto Library
NaCl already uses the hybrid approach in
crypto_box, but only allows for one recipient. In order to demonstrate how easy it is to use NaCl, I implemented a tiny C wrapper around libsodium. This wrapper allows you to specify multiple recipients for a message. It is open source and available at GitHub. It consists of six high level functions for easily creating keys, encrypting, and decrypting messages. It also contains unit and acceptance tests as well as an example.
Please feel free to use and adapt it to your needs. If you encounter a bug or even a code smell, please send me a pull request, I’ll happily apply it.
This article shows how difficult it is to get crypto right, even if you have the best intentions. It is insufficient to just use the right libraries. OpenSSL is one example. Most crypto libraries suffer from the same problem. You must truly understand how crypto works in all its details to implement it correctly and securely with these libraries.
NaCl and libsodium offer a simple way to avoid knowing all the nasty details while providing strong crypto for the rest of us. So give it a shot and try my library or take libsodium and NaCl directly for a spin. It’s easy and really secure.
Feel free to contact me if you have questions and comments.
1. At CCC 30, Daniel J. Bernstein and his colleagues explained how much this is true by showing that crypto standards like NIST FIPS 186-2 have possible backdoors and that even chip vendors like Intel cannot guarantee backdoor free crypto circuits.↵
2. Due to the usually larger key size of asymmetric encryption, it is computationally more expensive than symmetric encryption. Hybrid encryption is usually used when asymmetric encryption is applied, e.g. PGP/GPG, SSL/TLS.↵
3. A nonce is a additional random bit string added to the key before encryption or decryption. Using a nonce guarantees that no two messages lead to the same cipher text using the same key. This makes crypto analysis more difficult.↵
4. djb invented TCP Syn Cookies and qmail.↵