Lazy Ruby Cryptography

What is Cryptography?

Cryptography is the art of making clear text illegible by anyone but the original recipients. It is an art that is at least as old as the Roman Repulic, when Julius Caesar used the Caesar cipher to encrypt orders to his centurions in the Gallic wars.

Since then, cryptography has evolved into a science unto itself, which helped kickstart computer science (and thus programming) during World War 2.

I encourage you to discover the history of cryptography. It makes for a good techno-thriller with lots of cloak-and-dagger.

The Two Areas of Cryptography

Cryptogrpahic algorithms are used in two main areas: Validation and encryption. Validation algorithms, or message/hash digests, provide a means to verify that data has not been modified somehow. A rather mundane use of this verification is testing the hash finger print of a file after a download to make sure that the file wasn’t corrupted during the transfer.

Encryption and decryption are used to prevent a third party from reading the message. Online banking or e-commerce websites use this to secure the communication between your browser and their servers when you send payment details.

Get It

You don’t have to install anything, since Ruby includes OpenSSL bindings.

Using Hashes

Important Upfront Note

Unless you have to deal with already existing data, do not use MD5. It is considered broken.

Hashing Data

Consider the following snippet:

require 'digest/sha2'  sha256 = Digest::SHA2.new(256)  sha256.digest("data to be hashed")

After requiring the SHA2 hashing algorithm, we first have to initialize an SHA2 oject with the desired key-length (Ruby supports both 256 and 512 bit keys), before we can digest data.

The key length is important:

sha2_256 = Digest::SHA2.new(256)  sha2_512 = Digest::SHA2.new(512)  sha512_hashed = sha2_512.digest("super secret password")  sha256_hashed = sha2_256.digest("super secret password")  puts sha256_hashed == sha512_hashed

If you run this, you will see “false”. This is obvious enough, since the keylength is different, but it is a gotcha. Make sure you know what algorithm was used to hash the data, otherwise you’ll get false negatives.

The MD5 and SHA1 digests work similarly, but you don’t have to instantiate a new object:

require 'digest/md5' require 'digest/sha1'  Digest::MD5.digest("super secret password") Digest::SHA1.digest("super secret password")

Symmetric Cryptography with AES

Symmetric cryptography, or symmetric-key cryptography means that data is encrypted and decrypted with the same key. That has the benefit that it is much easier to work with, but also means that both the encrypted data and the key used to encrypt this data has to be exchanged somehow. Pulic/Private key (or asymmetrical) cryptography avoids this problem: One key is used to encrypt data, another is used to decrypt data (however, it is theoretically possible to derive one key from another, but there are no known, working attacks of this sort).

Public/Private key encryption deserves its own tutorial, so we will only deal with symmetric cryptography this time.

Let us look at some source code, where we encrypt, and then immediately decrypt clear text:

require 'openssl'  aes = OpenSSL::Cipher.new("AES-256-ECB")  key = sha2_256.digest("super secret password")  aes.encrypt aes.key = key  payload = aes.update("A very secret message.") + aes.final  puts payload  aes.reset  aes.decrypt aes.key = key  puts aes.update(payload) + aes.final

This looks more complex than it really is.

First, we load the OpenSSL bidings, which Ruby uses to do encryption.

Then, we instantiate a new AES cipher object. If you want to know which algorithms Ruy supports, OpenSSL::Cipher.ciphers will tell you.

Now that we have an AES object, we need a key to encrypt data. AES-256 expects an encryption key that is at least 256 bits long (the key size is determined by which AES variant you use). To achieve that, and to make the key harder to guess, we hash a passphrase with SHA2-256.

The next step is to call the encrypt method. That initializes the cryptographical systems needed (like acquiring a source of entropy). Both the decrypt and the encrypt method must be called before any other method!

Finally, we encrypt our data by calling

payload = aes.update("A very secret message.") + aes.final

The important part of this is the aes.final call. This adds the last block of encrypted data to our payload variable plus any necessary padding for AES, which is determined by the algorithm’s block size—the block size are 128 bit chunks of data that each get encrypted, more or less.

To decrypt the data, we reset our AES object, and call decrypt on it. The decryption process works analogous to the encryption. It is symmetric, if you allow me such a lazy pun.

Enhanced Security

If you use the same key to encrypt data several times, you are suverting the security that encryption provides. Sooner or later, parts of the message will repeat themselves and look the same even when encrypted (this is one of the vectors that were used in Bletchley Park by Alan Turing et al to break the Kriegsmarine Enigma system).

Thus, almost all modern ciphers have an Initialization Vector or IV (link is in the Resources section). Ruby cipher ojects have the iv method, which allows you to feed an IV into your cryptography. Make sure that the IV source is random (like Ruby’s rand method), and does not repeat itself.

Do Not Use DES

The DES algorithm has been broken (link below).

Resources

Footnotes

1 comment:

Unknown said...

Very informative blog. This is an excellent resource for all the people who wanted to learn all about the process of cryptography. Thank you for this great share.
what is a digital signature