We’ve all heard the admonition, “Don’t roll your own encryption!”. That’s because it’s hard to get encryption right. Let’s take a moment to examine what that means exactly. Probably very few of us would set out to code a replacement for AES or ECC, except maybe for fun. We leave that to the professionals. But as developers, we often use complex algorithms that someone else wrote. We’re comfortable using them without knowing exactly how they work. Why shouldn’t this be the case with encryption? Even if you already understand encryption core concepts.
This article is part of our Security Guide series – Encryption for Developers. Read more in that series of in-depth technical articles on getting encryption right in your application.
In theory, you should be able to pick up a cryptographic library, let’s say javax.crypto, and build a layer of impressively strong security into your application. Indeed, many programmers before you have attempted to do exactly that. But like all areas of computer science and software development, cryptography is implemented in terms of a deep stack, with non-trivial steps between each conceptual layer. There are a lot of things you need to know and you need to get right in order to even pick up and use a library for AES.
In a later article, we will discuss the fundamentals of cryptography in a bit more detail, but for the purposes of “how to get it wrong,” let’s examine this conceptual stack in brief. Between the math and the application level, there are a lot of things to get right.
The Stack
In the following diagram, we depict the algorithm library like javax.crypto as only step three out of six. After selecting your algorithm and library, you need to develop or use a protocol that matches your use case, a protocol library that implements it in the language of your application, and then you need to integrate that protocol library into your application. And you need to implement each step along the way without making any mistakes that undermine the confidentiality and integrity of your application.
We don’t believe there is any fundamental reason that there are so few libraries beyond step three, but that’s just the way it is in 2019.
How Developers Picture Cryptography
As developers, we have a mental model of how to get encryption right that we start with when adding security and privacy to our application. That mental model, roughly speaking, is:
Encryption feels like a function that inputs plaintext and outputs ciphertext. In Java, such a function to encrypt strings might look like this:
- String encrypt (String plaintext) {…} // a nice, stateless function
Although if we’re being realistic, we quickly realize that Strings aren’t always practical, or even what we want to encrypt. It might be photos or files, for instance:
- byte [] encrypt (byte[] plaintext) {…} // byte arrays are easy too
Now as we think through things, we remember of course that the whole point of cryptography is to use a key or a password to lock up some data, so our encrypt function should input a key and the plaintext, then output ciphertext:
- byte [] encrypt (byte[] plaintext,
- byte [] key) {…} // You need a key
And this is probably about where most developers will leave off unless they’ve gotten some training in cryptography. This is a very reasonable mental model for what cryptography is. While reasonable, it happens to be wrong!
In fact, this is exactly how a function like AES operates when it only has a single block to encrypt. The core operational mode of AES (called ECB) inputs plaintext and a key and outputs ciphertext. It does so in a deterministic way. It’s a nice, stateless function, but it is not a secure function without doing a lot of extra work.
The Reality of Cryptographic Libraries
In reality, a function prototype in Java might look a bit more like this, asking for a number of parameters that are just not a part of our simple mental model. They are nevertheless completely necessary for the secure operation of a cipher like AES:
- byte [] encrypt (byte[] plaintext,
- byte [] key,
- byte [] iv,
- String algorithm,
- Strong mode) {…}
In addition to being relatively mysterious, these Initialization Vector (IV) and mode parameters are subtle. For instance, you have to understand how big the IV is, how it should be generated, whether it can be known to the attacker, what happens if it’s chosen by the attacker, and how to store it. Different modes treat the IV differently so the answer is not a one-time thing.
As for keys? We get into that in a different post. It needs its own discussion.
The function prototype above is actually nicer than what we do get in Java. The javax.crypto functions are instead: getInstance(), init(), and doFinal(), with relatively unstructured parameters. For instance, here’s the JavaDoc for getInstance(). As a developer going to the API docs, you’re going to feel pretty disappointed:
Decisions: Cryptography is Not a Stateless Function
In summary, Cryptography is not a stateless function. As a functional programmer, I feel qualified to say this. The key and IV are both non-trivial stateful values. How you generate, store, communicate, and retrieve them is vitally important to the security of your system.
Beyond managing sensitive state, you need to make a lot of important choices, which often interact in complex ways. All of these choices together represent the cryptographic protocol you are writing for your application. Make them carefully, document them, and have them reviewed by your peers and by a qualified cryptographer if you can:
Core encryption questions:
- Selecting the type of cipher you’re looking for: Symmetric and asymmetric have performance and key management trade offs.
- Choose an Algorithm: AES, RSA, ECC (with various curves), CHACHA20, etc.
- Selecting the mode: ECB, GCM, CBC, SIV, etc.
- Planning for Integrity: Hashing / tagging / MAC: MD5, SHA1, SHA2, Poly1305, GCM, etc.
- Where to implement: Is HTTPS enough? Or should your application implement end-to-end encryption? Are you only focused on data at rest in mobile?
Managing keys, because you are not done with your cryptography until you do:
- Generation and storage: Random / password-based, storage of generated keys away from the ciphertext. Using key managers, HSMs, enclaves, etc. Key size. Not all enclaves support all types of cryptography.
- Registering and communicating keys: Either a shared key or public key needs to be communicated; there’s a bootstrapping problem for that.
- User identity: Passwords, biometrics, 2FA; often have a big impact on the cryptography.
Miscellaneous practical considerations to get encryption right:
- Handling Random Elements: IV, Salt, Nonces.
- Misc.: Padding, encoding, serializing, signing.
- FIPS Compliance: In some industries this is very important, and will impact your decisions.
- Communication Decisions: All of these choices need to be known to the sending and receiving parties.
Unfortunately, while some choices are definitely bad (i.e. using ECB mode in AES), without a relatively deep understanding of your application needs, protocol and attack model, no one can tell you what’s “good.” It’s hard to get encryption right.
Have questions? Drop us a note at info@tozny.com or Request a Consultation with one of our security experts and we’ll be happy to discuss your concerns and how we can help.