Exploring Cryptography Fundamentals in Ruby

Share this article

Exploring Cryptography Fundamentals in Ruby

Cryptography is used all over the world to protect data from prying eyes. If you want to protect your own data you should be familiar with this topic.

To help you get started, I will show you the main concepts & algorithms that you need to know, and I will include some Ruby examples so you can see the concepts in action.

Primitive Ciphers

Cryptography was already in use way before computers existed. For example you may have heard about the Caesar cipher.

In the Caesar cipher letters are shifted X positions. The number of positions is the key needed to recover the original message.

It’s trivial to break this cipher using a computer, so what we use today are algorithms that take advantage of some mathematical problems (like prime factorization) that are hard to solve, even for the most powerful computers in the world.

Modern cryptography algorithms can be divided into three groups:

  • Symmetric ciphers
  • Asymmetric ciphers
  • Hash functions

Symmetric Ciphers

A symmetric algorithm uses one secret key to encode & decode data. This is the kind of algorithm that you would use to protect an important file on your computer.

Note that you should not use this for storing passwords because a symmetric algorithm can easily be reversed if you have the key (and the key needs to be on the system if you want to use the encrypted data without human interaction). What you want for storing passwords is called a ‘hashing function’ (covered later in this article).

Here are some popular symmetric algorithms:

  • DES
  • Triple DES
  • AES
  • Blowfish

You can use the OpenSSL library to work with many symmetric ciphers in Ruby.

Example:

require 'openssl'

cipher = OpenSSL::Cipher.new('aes256')
cipher.encrypt

key = cipher.random_key
iv  = cipher.random_iv

encrypted = cipher.update('test') + cipher.final

# You need to save both the IV (initialization vector) + the key

If you want to use your own key instead of the one generated by OpenSSL, you need to use a password derivation function like PBKDF#2 (Password-Based Key Derivation Function 2). Here is how you can do that:

require 'openssl'

cipher = OpenSSL::Cipher.new('aes256')
cipher.encrypt

iv  = cipher.random_iv

# Password derivation
salt = OpenSSL::Random.random_bytes(16)
key  = OpenSSL::PKCS5.pbkdf2_hmac_sha1('password', salt, 20_000, cipher.key_len)

cipher.key = key

encrypted = cipher.update('test') + cipher.final

For decryption you only need to change cipher.encrypt to cipher.decrypt & provide the salt and IV generated during encryption.

Asymmetric Ciphers

Asymmetric algorithms use two keys: a public key & a private key. The private key is used for decoding & signing messages. The public key is used for encoding & verification.

One practical application is the initial exchange in an SSL connection. The Internet is a hostile network and both the client & the server need to agree on a shared secret key to be able to secure the communication between them.

The problem is that this key can’t be sent in the clear because anyone could just intercept it and use it to spy on the encrypted communication.

The solution is to use an asymmetric cipher, like RSA, or a dedicated algorithm, like DH (Diffie-Hellman Key Exchange). After a secret key has been agreed upon, the secure SSL session can start using a regular symmetric cipher like AES.

You can generate a pair of public/private keys using the openssl command. Like this:

openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048
openssl rsa -pubout -in private_key.pem -out public_key.pem

You can also generate a key pair from Ruby:

key = OpenSSL::PKey::RSA.new(2048)

File.open('private_key.pem', 'w') { |f| f.write(key.to_pem) }
File.open('public_key.pem', 'w')  { |f| f.write(key.public_key.to_pem) }

Hash Functions

A hash function takes in an input and returns a fixed-size output. In a cryptographic hash function, the output is typically represented as a hexadecimal string.

It’s important to note that a hash function is not meant to be reversed, it’s a one-way function. This is what makes it great for storing passwords.

Hash functions can also be used to compare if two files are identical. This is done by hashing the contents of the file & comparing the resulting hashes.

Some common hash functions are:

  • MD5
  • SHA1
  • SHA2
  • bcrypt

The Ruby standard library includes the Digest module to help you produce the first 3.

Here is an example:

require 'digest'

Digest::MD5.hexdigest('test')
# "098f6bcd4621d373cade4e832627b4f6"

Digest::SHA1.hexdigest('test')
# "a94a8fe5ccb19ba61c4c0873d391e987982fbbd3"

The same input will always produce the same output, but it’s also possible for two or more inputs to map to the same output, this is called a ‘hash collision’. Lower bit functions, like MD5, are more susceptible to collisions, so that’s one reason why it’s preferable to use a stronger function.

Here is the documentation for Digest: http://ruby-doc.org/stdlib-2.2.0/libdoc/digest/rdoc/Digest.html

Another option is to use bcrypt (which is what Devise uses):

require 'bcrypt'

BCrypt::Password.create("test")
# "$2a$10$zccXimeuNdA083RSDFy7VeVgs538d5XRQurRbqjdEd3h0kU7Q0j2e"

The advantage of using bcrypt over something like SHA-1 is that bcrypt is slower, which is a good thing because it makes the resulting hashes stronger against brute-force attacks.

Here is the documentation for the bcrypt gem: https://rubygems.org/gems/bcrypt/

Adding a Little Salt

One problem with hash functions is that it’s possible to pre-calculate the hash values for many common words & passwords, which makes ‘cracking’ a set of hashes much faster than pure brute-force. In fact, you can download sets of pre-computed hashes called ‘Rainbow tables’.

The solution is to make all your hashes unique using what we call a ‘salt’ value. This value is used like this:

sha1(salt + password)

And the salt is saved with the hashed password, so it can be used for login attempts. In the case of bcrypt, the salt is generated by the algorithm & included in the hash output.

Note: This is already done for you if you use something like Devise, but it’s still good to know :)

One more thing: make sure to use a unique salt for every password, otherwise you won’t get the full benefits of salting.

Conclusion

Cryptography is a complex world, but you want to be familiar with it if you deal with important data. One word of warning: don’t try to be clever & invent your own ciphers, you are probably going to shoot yourself in the foot.

Frequently Asked Questions on Cryptography Fundamentals in Ruby

What is the importance of cryptography in Ruby?

Cryptography is a crucial aspect of Ruby programming, especially when dealing with sensitive data. It provides a way to secure information by transforming it into an unreadable format, which can only be deciphered using a unique key. This ensures that the data remains confidential and safe from unauthorized access. Cryptography in Ruby is also essential in verifying the integrity of data, ensuring that it has not been tampered with during transmission. It also plays a significant role in authentication, confirming the identity of the parties involved in a communication process.

How does the OpenSSL::Cipher library work in Ruby?

The OpenSSL::Cipher library in Ruby is a wrapper for the OpenSSL library, which provides a set of cryptographic recipes. It allows developers to encrypt and decrypt data using various encryption algorithms. To use it, you first create a new Cipher instance, specify the encryption algorithm and mode, set the key and IV, then call the #update method with the data you want to encrypt or decrypt, and finally call #final to get the remaining data.

What are some common encryption algorithms used in Ruby?

Ruby supports a variety of encryption algorithms through the OpenSSL::Cipher library. Some of the most commonly used ones include AES (Advanced Encryption Standard), DES (Data Encryption Standard), and RSA (Rivest-Shamir-Adleman). AES is a symmetric encryption algorithm that is widely used due to its security and speed. DES, although older and less secure than AES, is still used in some systems. RSA is an asymmetric encryption algorithm used for secure data transmission.

How can I ensure the security of my encryption keys in Ruby?

Ensuring the security of encryption keys is paramount in cryptography. In Ruby, you can secure your keys by storing them in a secure and encrypted location. Avoid hardcoding keys directly into your code. Instead, use environment variables or secure key management systems. Regularly rotate your keys and use strong, unique keys for each encryption task.

What is the difference between symmetric and asymmetric encryption in Ruby?

Symmetric encryption in Ruby involves the use of a single key for both encryption and decryption of data. It is faster and more efficient, making it suitable for encrypting large amounts of data. On the other hand, asymmetric encryption uses two different keys – a public key for encryption and a private key for decryption. This adds an extra layer of security as the private key does not need to be shared. However, it is slower and more resource-intensive compared to symmetric encryption.

How can I use cryptography for authentication in Ruby?

In Ruby, cryptography can be used for authentication through the use of digital signatures. A digital signature is created by encrypting a piece of data (such as a message or document) with a private key. The recipient can then verify the signature by decrypting it with the corresponding public key. If the decrypted data matches the original, it confirms the authenticity of the sender.

What is a cryptographic hash function in Ruby?

A cryptographic hash function in Ruby is a function that takes an input and returns a fixed-size string of bytes. The output is unique to each unique input, making it impossible to regenerate the original input value from the hash value. This is useful for storing passwords securely, as even if the hash value is compromised, the original password cannot be determined.

How can I implement secure password storage in Ruby?

In Ruby, you can implement secure password storage using a combination of cryptographic hash functions and salt. Salt is a random value that is added to the password before hashing. This prevents attackers from using precomputed tables of hash values (rainbow tables) to crack the password. The hashed and salted password is then stored in the database. When a user logs in, the entered password is salted and hashed in the same way, and the result is compared to the stored value.

What is a cipher in Ruby?

A cipher in Ruby is a cryptographic algorithm used for encryption or decryption. The OpenSSL::Cipher library in Ruby provides a set of ciphers that can be used to secure data. Each cipher has a specific key size and block size, and operates in a specific mode (such as ECB, CBC, CFB, OFB, or CTR).

How can I generate a secure random number in Ruby?

Ruby provides the SecureRandom module for generating secure random numbers. These can be used for creating random keys, IVs, or salts. The SecureRandom module uses the OpenSSL library to generate random numbers, which are suitable for cryptographic use.

Jesus CastelloJesus Castello
View Author

Jesus is a Ruby developer who likes to help other developers improve their Ruby skills & fill-in the gaps in their education. Check out his blog here.

cryptographyGlennG
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week