Skip to content
Blog » Signing and Verifying Messages with HMAC in Rust using Ring

Signing and Verifying Messages with HMAC in Rust using Ring

    In my previous post I introduced the Rust cryptography library Ring (see here). In this post I will show you how to use Ring to sign and verify messages using the HMAC algorithm.

    Introduction to HMAC

    First a bit of cryptography theory. There are four main properties which we would like to have under certain conditions in the context of cryptography:

    • Confidentiality – Encrypting or keeping data private or hidden.
    • Integrity – Data has not been changed or tampered with.
    • Authenticity – The data is authentic and from an authenticated source.
    • Non-repudiation – The sender cannot deny sending the message or the identity of the sender is authenticated.

    HMAC is an algorithm that can be used to provide both message integrity and message authenticity.

    HMAC stands for Hash-based Message Authentication Code and it works by taking a message and a signing key (which must be kept secret) and running an algorithm which produces a HMAC or message tag. Internally the HMAC algorithm uses a hash function that can be selected by the program.

    The interface of a HMAC signing function looks something like this (in pseudo code):

    tag = hmac_sign(hash_function, key, message)

    We pass in a hash function, a secret key and the message to be signed. The tag returned is the signature or authentication tag or HMAC (different words for the same thing). Normally the message would be sent with the tag over an insecure medium such as the internet, after which we can verify both the integrity and authenticity of the message using a verify function. We need to make sure that the signing key is kept secret otherwise we can’t be sure of the authenticity of the message. This is because in this case a malicious actor could create messages using the signing key.

    The verify function looks like this in pseudo code:

    result = hmac_verify(hash_function, key, message, tag)

    The result of the verify function is either true or false. If true the message is valid and hasn’t be modified or tampered with. We can do all this in Rust using the Ring library as follows.

    HMAC Usage Guidelines

    • Never try to create authentication tags yourself by combining a secret key with a message as this can lead to vulnerabilities such as the length extension attack.
    • The HMAC_SHA1 algorithm has a smaller tag size (160 bits) and should be avoided where possible to future proof applications.
    • Verification of authentication tags needs to be done in constant time in order to prevent timing attacks. For this reason you should always make sure that you use the verify function supplied by this library when checking a tag.
    • Message authentication codes do not protect against replay attacks so even if an attacker is unable to forge an authentication tag they may be able to resend a message in order to exploit a vulnerability in a system. In this case you may need to use a nonce (incrementing counter) that increases with each message to prevent such attacks.
    • When choosing the HMAC algorithm to use in your application remember that a minimum of 128 bits is required for collision resistance and to prevent forgery of authentication tags.

    Rust Imports

    We start by importing the required Ring modules, rand, digest and hmac. rand is for random number generation, digest includes support for hash functions, and hmac is the module we will be using to sign messages.

    use ring::rand;
    use ring::digest;
    use ring::hmac;

    Create a Secure Random Number Generator

    Next we create a cryptographically secure pseudo random number generator to be used for the signing key generation. The SystemRandom instance is a secure random number generator where random numbers come directly from the operating system. In other words it is using data from the operating system as a source of entropy. Only a single SystemRandom instance should be created per application and the instance can be shared across threads.

    let rng = rand::SystemRandom::new();

    Generate the HMAC Signing Key

    Run the Key::generate function to create a signing key. The function takes the HMAC algorithm and SystemRandom instance as parameters.

    let key = hmac::Key::generate(hmac::HMAC_SHA256, &rng).unwrap();

    The following HMAC algorithms are defined in the hmac module:

    • HMAC_SHA1_FOR_LEGACY_USE_ONLY
    • HMAC_SHA256
    • HMAC_SHA384
    • HMAC_SHA512

    Create a Message

    Next we create a message to be signed. In this case I’m simply creating a static string but we could use any data type that can be converted to an array of bytes (&[u8]). This is because the signing method below accepts a bytes array as input.

    let msg = "This is the message to be signed";

    Sign the Message

    Convert the message to a bytes array and pass into the hmac::sign function along with the Key instance to produce the authentication tag. The returned instance is a Tag type which contains a tag equal in size to the output of the hash function used internally. For example HMAC_SHA256 will produce a 256 bit tag.

    let tag = hmac::sign(&key, msg.as_bytes());

    Verify the Integrity of the Message

    Run the hmac::verify function on the message, passing in the same Key instance and the tag in order to check if the message is valid. In this example we are using the unwrap function which will throw an error if the Result instance returned contains an Err value, otherwise the call to unwrap simply returns the unit type. The call to Tag::as_ref returns the tag data as a byte array (&[u8]) which gets passed into the function.

    hmac::verify(&key, msg.as_bytes(), tag.as_ref()).unwrap();

    If the message verification fails for any reason, the program will throw an error because we are calling unwrap on the returned Result object. For example if we try to verify a message that has been altered using the same tag like this:

    hmac::verify(&key, "This is the message to be signed - but altered".as_bytes(), tag.as_ref()).unwrap();

    Then we get the following error with type Unspecified which is a type defined in the Ring library:

    thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: Unspecified', src/main.rs:29:99
    stack backtrace:
       0: rust_begin_unwind
                 at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
       1: core::panicking::panic_fmt
                 at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
       2: core::result::unwrap_failed
                 at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/result.rs:1785:5
       3: core::result::Result<T,E>::unwrap
                 at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/result.rs:1107:23

    Using Context to Sign a Multi-part Message

    Alternatively to the above flow, we can use the hmac::Context struct to concatenate and then sign a multi-part message. This is useful if we have structured data that has multiple parts because we can use the Context::update method to add each field of the message in a separate step.

    // Create a message to be signed
    let msg = ["hello", ", ", "world"];
    
    // Sign the message
    let mut context = hmac::Context::with_key(&key);
    for str in &msg {
        context.update(str.as_bytes());
    }
    let tag = context.sign();
    
    // concat the message data
    let mut msg_data = Vec::<u8>::new();
    for str in &msg {
        msg_data.extend(str.as_bytes());
    }
    
    // Verify the integrity of the message - success case
    hmac::verify(&key, &msg_data.as_ref(), tag.as_ref()).unwrap();

    Full Sample Code

    Here is the full code sample for reference.

    use ring::rand;
    use ring::digest;
    use ring::error::Unspecified;
    use ring::hmac;
    
    fn main() {
        // scenario 1 - simple sign and verify of a message
    
        // Create a secure random number generator
        let rng = rand::SystemRandom::new();
    
        // Generate the hmac signing key
        let key = hmac::Key::generate(hmac::HMAC_SHA256, &rng).unwrap();
    
        // Create a message to be signed
        let msg = "This is the message to be signed";
    
        // Sign the message
        let tag = hmac::sign(&key, msg.as_bytes());
    
        // Verify the integrity of the message - success case
        hmac::verify(&key, msg.as_bytes(), tag.as_ref()).unwrap();
    
        // Verify the integrity of the message - failure case
        //hmac::verify(&key, "This is the message to be signed - but altered".as_bytes(), tag.as_ref()).unwrap();
    
    
        // scenario 2 - sign and verify a multi-part message
    
        // Create a message to be signed
        let msg = ["hello", ", ", "world"];
        
        // Sign the message
        let mut context = hmac::Context::with_key(&key);
        for str in &msg {
            context.update(str.as_bytes());
        }
        let tag = context.sign();
        
        let mut msg_data = Vec::<u8>::new();
        for str in &msg {
            msg_data.extend(str.as_bytes());
        }
    
        // Verify the integrity of the message - success case
        hmac::verify(&key, &msg_data.as_ref(), tag.as_ref()).unwrap();
    }

    Conclusion

    In this post we introduced the HMAC algorithm, explained how it works and the cryptographic properties it can provide. Then we explained how to use the Ring hmac module to sign and verify messages using hmac::sign, hmac::verify and the hmac::Context multi-part API.

    Leave a Reply

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