How to actually do a cryptography in Ruby
In 2013 I wrote a short article titled You are dangerously bad at cryptography . Since then I have gone on to consult on web application security. I got a little further in the Matasano Crypto Challenges and I can remember at least five distinct occasions where I was able to find and exploit the broken cryptography of real-life codebases.
When developers misuse cryptography, it’s typically because they are using pieces of crypto code in ways unintended by the people that wrote the library or the people that came up with the cryptographic constructs within. Here are some examples:
- “Double” a.k.a. “Multiple layers” of encryption
- Generating a “nonce” using a combination of timestamp, date of birth, and security question answers
- Generating random keys using PBKDF algorithms that take random data as input (in that case… why not just use the random data as they key?)
- Using ciphers that have been deprecated by the original creators in production code
I’m not an expert in cryptography, so perhaps my lack of expertise means that in some cases, I’m simply looking at a construct I haven’t seen before. But the bet I’m going to make is that in most cases, the developer using the crypto code didn’t know what they were doing.
This kind of broken cryptography is prevalent in Rails application codebases and popular cryptography ruby gems. Worst still, there are many, many articles on the internet with incoherent advice about using cryptography from Ruby
Either no one read my original article or people read it and ignored the central message. There’s plenty of bad crypto code running in production. In this article I’m going to attempt to tell you how to do it correctly. In the long run this article may do more harm than good, but my goal is for it to be the least bad article about how to use cryptography from Ruby.
Before we get to the code, we need to figure out exactly what it is you’re trying to do. Cryptography can help you get a message from person A to B with the following non-functional requirements:
- Confidentiality - The message stays secret
- Integrity - The message hasn’t been tampered with
- Authentication - Person B can be assured that the message came from Person A
- Non-repudiation - Person B can prove that Person A sent the message
When you’re encrypting data at rest, you can think of person A as being present you and person B as being future you, and the same constraints can apply.
In the vast majority of Ruby/Rails codebases we’ve seen that use cryptography, the developer is trying to encrypt some data at rest using symmetric encryption. This is the only use case we’re going to tackle in this article.
You want to encrypt a thing with a shared secret key. This has two parts:
- Generating the key
- Encrypting and decrypting using the key
To make this happen using cryptography, you’re going to use the Ruby binding to the Networking and Cryptography (NaCl) library ).
libsodium. Using Homebrew that’s:
$ brew install libsodium
Then install the
rbnacl gem (or the equivalent using Bundler):
$ gem install rbnacl
Here’s how you generate a key:
require 'rbnacl' key_bytes = RbNaCl::SecretBox.key_bytes key = RbNaCl::Random.random_bytes(key_bytes)
Take that key and put it somewhere outside version control. Use a different key in all environments (so one for each developer, another for staging, another for production, and any other environments your application runs in).
Here’s how you encrypt a message using that key:
plaintext = "The Secret Message" box = RbNaCl::SimpleBox.from_secret_key(key) ciphertext = box.encrypt(plaintext)
The ciphertext is the encrypted plaintext. You can store that in a file, a database table, or anywhere else you want so long as it’s separate from they key used to encrypt it.
Here’s how to get your plaintext back from your ciphertext using the same key:
box = RbNaCl::SimpleBox.from_secret_key(key) plaintext = box.decrypt(ciphertext)
That’s all. That’s it. That’s the whole thing. You have the power. Don’t add a thing. You don’t need a nonce. You don’t need an initialisation vector. You don’t need to worry about encrypt then mac or mac then encrypt. Don’t double encrypt it. Don’t use the HMAC of the key instead of the key. Don’t randomly add a PBKDF somewhere in the process. Don’t sign the message. Just do it exactly like I’ve done it above, or defer to the documentation for Simplebox if this post goes stale. All of those decisions have been made for you and handled by NaCl.
The one thing you’re OK to do is Base64 encode/decode the key you get from
#random_bytes. It may be difficult for you to pass the key around in environment variables as the raw binary you get from that method, so you’re allowed this one little addition to the code above. Anything else is at your own peril.
require 'rbnacl' require 'base64' key_bytes = RbNaCl::SecretBox.key_bytes key = RbNaCl::Random.random_bytes(key_bytes) encoded_key = Base64.strict_encode64(key) # Now you can store `encoded_key` in a file, copy paste it, etc.
# Once you're ready to encrypt/decrypt with it, decode the key and use it with `SimpleBox` decoded_key = Base64.strict_decode64(encoded_key) box = RbNaCl::SimpleBox.from_secret_key(decoded_key) ciphertext = box.encrypt(plaintext)
Congratulations, you’re done! Put the ciphertext in a database table, put it in a file, do what you want with it, just don’t do any more cryptography on it. All of the problems I’ve discovered in real-world cryptography stem from developers applying creativity to their usage of cryptography libraries. This is one case where less work will result in better software. Just keep it simple, use NaCl as documented and your code is far more likely to be secure.
- Najaf Ali
Find this article useful? Sign up to our mailing list to get updates like this in your inbox every week. No spam, and you can unsubscribe at any time.
Give me the goodies