Playing with symmetric encryption algorithms in Ruby
In the times where data privacy is a headline every week it is good for software developers to have an understanding of encryption and programming.
For Rubyists the standard library of Ruby offers the OpenSSL::Cipher class to use symmetric encryption in Ruby code. Symmetric encryption means that both users who want to communicate are using the same key. This is different to asymmetric encryption where both users do not share the same key.
Basics of symmetric encryption algorithms
Lets get right into it and fire up our irb. We need to require the openssl library to use its functionalities.
> require 'openssl'
=> true
Lets see what kind of symmetric algorithms the Ruby OpenSSL library offers.
> puts OpenSSL::Cipher.ciphers
As we can see Ruby offers a few algorithms in different varieties. Most ciphers are given using the following format:
<name>-<key length>-<mode>
Lets take a few as examples:
- Data Encryption Standard (DES) is a widely used encryption algorithm. Today it is not considered as secure and should not be used anymore.
- Rivest Cipher/Ron’s Code (RC) is a different symmetric algorithm. It is also considered as unsecure and should not be used anymore. (see this)
- Advanced Encryption Standard (AES) is a symmetric encryption algorithm which is widely used today and counts as secure
Since AES is considered as secure we will use it as our algorithm for now.
AES comes with different key bit lengths: 128, 192, 256. The higher the key length is the harder it is to brute force it (try all possible combinations). Eventhough AES128 counts as secure today computers are getting more and more powerful. To ensure that our code will stay secure for a longer period we will use AES256 for our following examples.
The different modes of encryption algorithms is out of the scope of this blog post so we will not take a deeper look at them. Just note that encryption algorithms can use different modes to encrypt data.
Using OpenSSL::Cipher
Let’s create a new instance of a AES256 cipher using the CBC mode (as stated above: just ignore the mode since it is not relevant for these examples)
> cipher = OpenSSL::Cipher::AES256.new :CBC
After obtaining a cipher instance we have to set it to decryption or encryption mode:
> cipher.encrypt
Normally a cipher needs a key and an initialization vector to encrypt and decrypt a message. The initialization vector should be a random number. After generation it can be transmitted openly and does not need to be kept privately. The algorithm needs an initialization vector (IV) and the key for its encryption process.
OpenSSL::Cipher provides a method for generating an IV and should be used to save some hassle.
> iv = cipher.random_iv
This line does not only generate a random IV and sets it to iv but also sets the initialization vector for this cipher instance.
The key is where things get actually interesting. There are different ways to generate keys. One way of course would be to let the computer generate a random key.
But a user might want to set the key by himself. A problem is that you cannot take a normal word like SecretPassword as your key since AES256 requires your key to be 256 bit long.
> cipher.key = "SecretPassword"
OpenSSL::Cipher::CipherError: key length too short
By executing the following line
> "string".encoding
#<Encoding:UTF-8>
we can see that a string in my current irb instance is using UTF-8 as encoding which means that one character equals 8 bits.
Simple math: 256 / 8 = 32. This means that we need a 32 character string as our key for the AES256 algorithm. Ever had a 32 character long password?
Now lets take a key which is long enough and encrypt some data.
> cipher.key = "ThisPasswordIsReallyHardToGuess!"
Now we have our IV and key set. Now we can encrypt some data.
> cipher_text = cipher.update('This is a secret message') + cipher.final
cipher.update takes a message and encrypts it using the IV and key given before. cipher.final adds the final part of the cipher text to the end of the encrypted message. This is needed if encrypted data is transmitted so the decrypter knows when the end of the message is reached.
Decryption
Decrypting works like encrypting:
> decipher = OpenSSL::Cipher::AES256.new :CBC
> decipher.decrypt
> decipher.iv = iv # previously saved
> decipher.key = 'ThisPasswordIsReallyHardToGuess!'
> plain_text = decipher.update(cipher_text) + decipher.final
=> "This is a secret message"
Different Keys
What if we wanted our key just to be SecretPassword?
Here we could use a hash. A hash takes something (like a file, a string, …) and turns it into a fixed number of characters. This fixed number is specified by the hash algorithm which is being used.
In our case we want a 256 bit key so we need a hash function which creates 256 bit hashes. For this we can use the SHA-256 hash algorithm.
In Ruby we can use the SHA-256 hash algorithm using the digest library.
> key = Digest::SHA256.digest 'SecretPassword'
This line turns SecretPassword into a 256 bit hash which we can then use as a key for our AES encryption.
I hope this little post could give some insights in the world of symmetric encryption. We could not go deep into many aspects of symmetric encryption since it is out of scope for a simple blog post. But if anybody is interested feel free to leave a comment and I can write about a certain topic more specifically.