Skip to content
Blog » Cryptographic Hash Functions in Rust using Ring

Cryptographic Hash Functions in Rust using Ring

    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.

    Leave a Reply

    Your email address will not be published. Required fields are marked *