Making the world a better place, one line of code at a time

Common Cryptographic Pitfalls

Posted on Wednesday, November 18, 2015

When writing code that deals with security or cryptography, there are a number of mistakes that many people make; some obvious and some quite subtle. This post describes the most common mistakes I’ve seen and why they’re wrong.

Don’t re-invent the car

Correctly using cryptographic primitives is hard. If at all possible, you should not use raw cryptographic primitives (even well-accepted ones like AES, RSA, or SHA2) directly; instead, you should use professionally-built and reviewed protocols that use these systems, such as TLS, NaCl, Keyczar, and others.

There are a variety of subtle issues that professional cryptographers know about and you don’t (such padding and timing vulnerabilities), and these higher-level wrappers address these issues for you.

There have been many security issues resulting from projects that attempt to build their own protocols, and get these wrong.

Don’t re-invent the wheel

Even if you do need to use primitives directly, stick to known, well-researched primitives, and use the best ones available.

In particular, please don’t try to build your own encryption or hash algorithms. Cryptography is exceptionally complicated; unless you actually have substantial experience in the mathematical side of cryptanalysis, you are very unlikely to create a secure algorithm. Standard algorithms like AES have had years of research, and yet still do not have known vulnerabilities (when used correctly).

On a similar note, try not to write your own implementations of standard algorithms; instead, stick to existing, hardened, well-tested codebases. These implementations have been hardened to avoid leaking data through timing issues with early termination or CPU caching (eg, not using secret information in CPU branching or loop boundaries).

Learn as much as possible

Especially if you do use primitives directly, learn about things like padding attacks, replay vulnerabilities, and the like. If you have time, take a course about cryptography (eg, Stanford’s online course). If you don’t have time for a full course, at least read the Wikipedia articles about algorithms you use.

Every bit of cryptographic lore you learn may help you catch a mistake in your design or implementation and stop a real-world attacker.

If you use cryptography in production, you should keep up-to-date with the latest developments; read cryptographic blogs or follow cryptographers on Twitter. Some good people to follow include Bruce Schneier and Matthew Green.

Don’t reuse keys

Don’t use the same key for two different purposes. In worst-case scenarios, you can accidentally serve an endpoint that decrypts input with the same key used elsewhere to encrypt things (yes, this has happened). Even if you use keys only with unrelated cryptosystems, you still risk leaking information that a cryptanalyst can use to derive information about the key.

Ideally, each cryptosystem you use, and each scenario you use it in, should have its own unique key, each generated by a secure random number generator. If, for whatever reason, you can’t afford that much randomness, you can accept a single random blob (at least as large as the largest key you need), and use HMAC hashes with different (hard-coded) HMAC keys to derive a separate key from that blob for each use.

Create a threat model

It is important to understand what exactly you’re trying to prevent, and who you’re trying to block. Are you just trying to prevent attackers from impersonating users (eg, authentication for a public forum)? Are you trying to prevent attackers from reading messages at all (eg, personal chat)? Are you trying to prevent attackers from knowing who your users are and who they’re communicating with (eg, off-the-record chat)? Depending on what you’re trying to do, you will need different kinds of cryptosystems, and you will probably impose different requirements on what your product can actually do.

You should document which bits of data must be kept secret from, or authenticated to, which other parties, and how you intend to do that. You can then make sure that other features do not accidentally leak data that should be secret (eg, suggesting contacts to new users based on social networks of existing users may leak who people tend to contact). This also gives you a central place to check that ACLs are properly enforced; that any endpoint that serves data will properly check that the user is allowed to access every part of that data.

Use IVs

If you need to use block ciphers (such as AES) directly, never use ECB (Electronic Code Book) mode; it encrypts identical plaintext blocks to identical ciphertext and can leak information about common plaintext.

Instead, use CBC or CTR mode, which let you provide an additional input that will differentiate identical blocks.

Don’t reuse IVs

Following up from the previous point, in order to properly benefit from this protection, your IVs / nonces must be unique every time you encrypt.

A common beginner’s mistake is to either use a fixed IV, or generate an IV by hashing the key or the plaintext (or some other fixed value). This defeats (most of) the purpose of CBC; since two identical plaintexts use the same IV, they will encrypt to identical ciphertexts, leaking information again (using a fixed IV will scramble identical blocks in different plaintexts, but that alone is not enough).

Instead, you should generate a (from a cryptographically-secure RNG) a random IV for each message, and transmit it alongside the ciphertext.

Don’t expose error messages or other internal information

When dealing with crypto code, never show raw error messages to the user. Detailed error messages from crypto libraries should go in (access-controlled) logs. Your user-facing error messages should just say things like “Invalid input” and not give any details about why. If you expose actual error messages like “Invalid block size” or “Bad padding at byte X”, cryptanalysts may be able to slowly deduce bits of plaintexts or even keys, using techniques like padding oracle attacks.

This also applies to error messages resulting from decrypted data. If you parse a decrypted plaintext as XML, showing a detailed XML parsing error can help the attacker see exactly how many bytes he got right, and in some cases can even leak actual plaintext (from more-useful error messages that include the invalid line). If you expose these raw error messages, attackers may even be able to fully decrypt arbitrary non-XML ciphertexts by reading the XML parse errors.

Similarly, if you decrypt a ciphertext and validate the data using some business rules, detailed error messages from those rules can also leak information. Even if you only show these errors to authenticated, trusted users, you may have subtler risks, such as allowing users to decrypt parts of other users’ data, or attackers who manage to exfiltrate your error messages from those trusted users (eg, by posing as tech support).

Validate everything

Don’t be liberal with what you accept. When creating public APIs, it can be helpful to accept all requests, even if they don’t exactly match what you’re expecting. However, when dealing with cryptography or with sensitive information, this is exactly the attitude you don’t want.

Security-critical code should validate all attacker-controllable inputs as strictly as possible. Any invalid or unexpected data should be rejected immediately with no further processing, and with completely generic error messages. When possible, you should sign (with a MAC) all ciphertexts, so that you can reject any data that an attacker has modified before it has a chance to interact with anything else (and potentially expose weaknesses or side-channels in your decryption code).

Like detailed error messages, accepting invalid data can allow attackers to slowly learn parts of your encryption keys, using padding oracle attacks or timing vulnerabilities to see minute differences in how your system handles different inputs. Rejecting modified inputs immediately prevents these attacks.

Note: Although I have some understanding of the use of cryptographic primtiives, I am not a professional cryptographer.
Please take any cryptographic advice with a grain of salt (pun intended). If you are building highly sensitive systems, you should verify with your company’s cryptographer (you do have one, right?)

Categories: cryptography, security, mistakes Tweet this post

comments powered by Disqus