Key Takeaways
- The article introduces the fundamentals of cryptography in Ruby, discussing the three main types of modern cryptography algorithms: symmetric ciphers, asymmetric ciphers, and hash functions. Symmetric ciphers use a single secret key for encoding and decoding data, asymmetric ciphers utilize a public and private key, and hash functions convert an input into a fixed-size output.
- The article provides examples of how to use the OpenSSL library in Ruby to work with symmetric ciphers, generate a pair of public/private keys for asymmetric ciphers, and produce hash functions using the Digest module. It also discusses the use of bcrypt for password hashing and the importance of using ‘salt’ to make hashes unique.
- The article emphasizes the importance of not trying to invent personal ciphers due to the complexity of cryptography. It also highlights the role of cryptography in securing data, verifying data integrity, and confirming identities in communication processes.
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 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.