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.