rust 42 lines · 7 steps

HMAC signing and constant-time verify in Rust

Sign a payload with HMAC-SHA256 and verify tags without leaking timing information.

Explained by highlit
1use hmac::{Hmac, Mac};
2use sha2::Sha256;
3use subtle::ConstantTimeEq;
4 
5type HmacSha256 = Hmac<Sha256>;
6 
7#[derive(Debug, thiserror::Error)]
8pub enum SignatureError {
9 #[error("invalid signing key length")]
10 InvalidKey,
11 #[error("malformed hex tag")]
12 MalformedTag,
13 #[error("signature mismatch")]
14 Mismatch,
15}
16 
17pub fn sign(key: &[u8], payload: &[u8]) -> Result<String, SignatureError> {
18 let mut mac = HmacSha256::new_from_slice(key).map_err(|_| SignatureError::InvalidKey)?;
19 mac.update(payload);
20 let tag = mac.finalize().into_bytes();
21 Ok(hex::encode(tag))
22}
23 
24pub fn verify(key: &[u8], payload: &[u8], provided_tag: &str) -> Result<(), SignatureError> {
25 let expected = {
26 let mut mac = HmacSha256::new_from_slice(key).map_err(|_| SignatureError::InvalidKey)?;
27 mac.update(payload);
28 mac.finalize().into_bytes()
29 };
30 
31 let provided = hex::decode(provided_tag).map_err(|_| SignatureError::MalformedTag)?;
32 
33 if provided.len() != expected.len() {
34 return Err(SignatureError::Mismatch);
35 }
36 
37 if provided.ct_eq(expected.as_slice()).into() {
38 Ok(())
39 } else {
40 Err(SignatureError::Mismatch)
41 }
42}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1HMAC produces a keyed tag that proves a payload came from someone holding the secret key.
  2. 2Comparing secret-derived bytes with a constant-time equality check prevents timing side-channel attacks.
  3. 3Modeling each failure mode as a distinct error variant keeps callers honest about what can go wrong.

Related explainers

Share this explainer

Here's the card — post it anywhere.

HMAC signing and constant-time verify in Rust — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code