Ring is an open source cryptography library that implements cryptographic primitives and is designed to have an easy to use interface. It is implemented in Rust, C and assembly and most of the C and assembly language code in ring comes from BoringSSL. It supports the following features:
- Authenticated Encryption with Associated Data (AEAD)
- Key agreement
- Constant-time operations
- Hash functions and digests
- Hashed based Message Authentication Codes (HMAC)
- Key Derivation Functions (KDF)
- Cryptography pseudo-random number generation
- Public key cryptography and digital signatures
- Serialization and deserialization
You can find the documentation for the library here and the Github link here.
Getting Started
To get started using Ring, add the library as a dependancy to your Rust Cargo.toml file. Note that we are also using a library called hex for encoding binary to hexadecimal:
[dependencies]
ring = "0.16.20"
hex = "0.4.3"
Then run the cargo build command to download the library into your project:
cargo build
If everything works correctly, you should see it downloading and installing packages and your Cargo.lock file will be updated with a list of dependancies.
Calculating Hash Values using the Digest Module
The Ring library has multiple modules which each implement various cryptographic primitives. In this post we will be covering the digest
module which includes functions for calculating hash digests.
One off hash values can be calculated using the digest
function. Alternatively, the Context
struct can be used for multi-step hash calculations where we may want to progressively update the data buffer before calculating the hash.
The module supports these hash algorithms:
- SHA-1
- SHA-256
- SHA-384
- SHA-512
- SHA-512/256
Common Pitfalls & Attacks
These are the common pitfalls and attacks to be aware of when using the SHA-1 and SHA-2 algorithms supported by this library:
- SHA-1 is generally considered to be deprecated as it has been broken numerous times due to its smaller bit size (160 bit).
- SHA-1 is susceptible to the collision attack where it is possible to find two inputs that when hashed produce the same output. It should generally be avoided where possible but it can still be used in applications where collision resistance is not a requirement.
- SHA-2 is susceptible to the length extension attack in certain scenarios such as when it is used to hash a secret key with some data. You can read more about the length extension attack here.
- SHA-1 and SHA-2 should not be used for password hashing as they don’t provide any means to slow down attempts to brute force passwords. There are other special purpose hashing algorithms that should be used instead for passwords.
- When choosing the bit length of the hash remember that as a general principle, 256 bits is required for collision resistance and 128 bits for pre-image and second pre-image resistance (assuming an offline attack is possible)
Rust Imports
We will use these imports to pull in the Digest
and Context
structs, the digest
function and a few of the hash algorithms.
use ring::digest::Digest;
use ring::digest::Context;
use ring::digest::digest;
use ring::digest::SHA1_FOR_LEGACY_USE_ONLY;
use ring::digest::SHA256;
use ring::digest::SHA512;
The Digest Function
The digest function can be used to calculate a hash from a binary input. In Rust binary data is usually supplied as an u8 vector or array. Here is the function interface from the documentation:
pub fn digest(algorithm: &'static Algorithm, data: &[u8]) -> Digest
The function takes an Algorithm
struct and an array of binary data. Here are a few examples below where we calculate hash values using the SHA-1, SHA-256 and SHA-512 algorithms. The function returns a Digest
struct. The Digest::as_ref
method returns the digest as a &[u8]
. We use the hex::encode
function to convert the digest to a hexadecimal string for display.
// sha-1 (deprecated) using digest function
let sha1_digest: Digest = digest(&SHA1_FOR_LEGACY_USE_ONLY, b"hello, world");
println!("{:?}", sha1_digest.algorithm());
println!("{}", hex::encode(sha1_digest.as_ref()));
// sha-256 using digest function
let sha256_digest: Digest = digest(&SHA256, b"hello, world");
println!("{:?}", sha256_digest.algorithm());
println!("{}", hex::encode(sha256_digest.as_ref()));
// sha-512 using digest function
let sha512_digest: Digest = digest(&SHA512, b"hello, world");
println!("{:?}", sha512_digest.algorithm());
println!("{}", hex::encode(sha512_digest.as_ref()));
After running the code we get the following output:
SHA1
b7e23ec29af22b0b4e41da31e868d57226121c84
SHA256
09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
SHA512
8710339dcb6814d0d9d2290ef422285c9322b7163951f9a0ca8f883d3305286f44139aa374848e4174f5aada663027e4548637b6d19894aec4fb6c46a139fbf9
The Context Struct
The Context struct can be used to add data to a buffer in multiple steps and then calculate the hash.
// sha-256 using Context struct
let mut ctx = Context::new(&SHA256);
ctx.update(b"hello");
ctx.update(b", ");
ctx.update(b"world");
let ctx_digest = ctx.finish();
println!("{:?}", ctx_digest.algorithm());
println!("{}", hex::encode(ctx_digest.as_ref()));
After running the code we get the following output:
SHA256
09ca7e4eaa6e8ae9c7d261167129184883644d07dfba7cbfbc4c8a2e08360d5b
Full Sample Code
Here is the full code sample for reference.
use ring::digest::Digest;
use ring::digest::Context;
use ring::digest::digest;
use ring::digest::SHA1_FOR_LEGACY_USE_ONLY;
use ring::digest::SHA256;
use ring::digest::SHA512;
fn main() {
// sha-1 (deprecated) using digest function
let sha1_digest: Digest = digest(&SHA1_FOR_LEGACY_USE_ONLY, b"hello, world");
println!("{:?}", sha1_digest.algorithm());
println!("{}", hex::encode(sha1_digest.as_ref()));
// sha-256 using digest function
let sha256_digest: Digest = digest(&SHA256, b"hello, world");
println!("{:?}", sha256_digest.algorithm());
println!("{}", hex::encode(sha256_digest.as_ref()));
// sha-512 using digest function
let sha512_digest: Digest = digest(&SHA512, b"hello, world");
println!("{:?}", sha512_digest.algorithm());
println!("{}", hex::encode(sha512_digest.as_ref()));
// sha-256 using Context struct
let mut ctx = Context::new(&SHA256);
ctx.update(b"hello");
ctx.update(b", ");
ctx.update(b"world");
let ctx_digest = ctx.finish();
println!("{:?}", ctx_digest.algorithm());
println!("{}", hex::encode(ctx_digest.as_ref()));
}
Conclusion
We covered the digest
module of the popular Rust cryptography library Ring and showed you how to generate hashes using a variety of algorithms with both the digest
function and the Context
struct. Stay tuned for more blog posts on cryptography using Rust. If you have any questions, feel free to leave a comment below.