Strong Encryption Keys for Rails

Encryption Keys

Encryption is a common way to protect sensitive data. Generating a secure key is an important part of the process.

attr_encrypted, the popular encryption library for Rails, uses AES-256-GCM by default, which takes a 256-bit key. So how can we generate a secure one?

If you’re in a hurry, feel free to skip to the answer.

Take 1

One way to generate a key is:

SecureRandom.base64(32).first(32)

This generates a 32 character string that looks pretty secure. Each character has 64 possible values (letters, numbers, / and +). However, a single byte can represent 256 possible values. We’ve eliminated 75% of possible values per byte, which compounds across all 32 bytes. Here’s the math:

Method Possible Keys Equivalent
Random 25632 2256
Take 1 6432 2192

This reduces the number of possible keys by 99.999999999999999994%. Luckily, computers have not (yet) been able to brute force 128-bit keys, which have 2128 possible values.

Why 256?

So why do we use 256-bit keys to begin with? Security researcher Graham Sutherland puts it well:

“Essentially it’s about security margin. The longer the key, the higher the effective security. If there is ever a break in AES that reduces the effective number of operations required to crack it, a bigger key gives you a better chance of staying secure.”

Also, quantum computers are expected to brute force in square root time. This means a 256-bit key could be brute forced in the same time as traditional computers can brute force a 128-bit key.

A Better Way

The right way to generate a random 32-byte key is:

SecureRandom.random_bytes(32)

However, we can’t store this directly in Rails credentials or as an environment variable. We need to encode it first. Hex is a popular encoding. Rails uses this for its master key in Rails 5.2.

SecureRandom.random_bytes(32).unpack("H*").first

Ruby provides a helper to do this:

SecureRandom.hex(32)

To decode the key, use:

[hex_key].pack("H*")

We now have a much stronger key. If you store the key as an environment variable, your model should look something like:

class User < ApplicationRecord
  attr_encrypted :email, key: [ENV["EMAIL_ENCRYPTION_KEY"]].pack("H*")
end

Libraries

Libraries should educate users on how to generate sufficiently random keys. The rbnacl gem has a neat way of enforcing this - it checks if a string is binary before allowing it as a key.

if key.encoding != Encoding::BINARY
  raise ArgumentError, "Insecure key - key must use binary encoding"
end

This prevents our initial (flawed) method from working. I’ve incorporated this approach into the blind_index gem and opened an issue with attr_encrypted to get the author’s thoughts.

Conclusion

While secure key generation provides better protection against brute force attacks, it won’t help at all if the key is compromised. Limit who has access to encryption keys as well. For more security, consider a key management service to manage your keys.

Happy encrypting!

Published October 22, 2018


You might also enjoy

Why and How to Keep Your Decryption Keys Off Web Servers

Active Storage S3 Client-Side Encryption

Lockbox: Now with Types


All code examples are public domain.
Use them however you’d like (licensed under CC0).