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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1HMAC produces a keyed tag that proves a payload came from someone holding the secret key.
- 2Comparing secret-derived bytes with a constant-time equality check prevents timing side-channel attacks.
- 3Modeling each failure mode as a distinct error variant keeps callers honest about what can go wrong.
Related explainers
rust
use serde::Deserialize; #[derive(Debug, Deserialize)] #[serde(untagged)]
Parsing flexible JSON shapes with serde
deserialization
enums
json
Intermediate
6 steps
ruby
require "shellwords" require "open3" module Backup
Building safe shell commands in Ruby
shell-out
subprocess
command-injection
Intermediate
7 steps
rust
use std::cmp::{Ordering, Reverse}; use std::collections::BinaryHeap; use std::fs::File; use std::io::{self, BufRead, BufReader, BufWriter, Lines, Write};
K-way merge of sorted logs in Rust
binary-heap
k-way-merge
streaming-io
Intermediate
8 steps
java
@Component @Converter public class EncryptedStringConverter implements AttributeConverter<String, String> {
Transparent column encryption in Spring & JPA
encryption
aes-gcm
jpa-converter
Advanced
10 steps
python
import time import uuid from django.utils.deprecation import MiddlewareMixin
Attaching per-request context in Django
middleware
request lifecycle
multi-tenancy
Intermediate
7 steps
rust
use axum::{ extract::{Path, State}, response::sse::{Event, KeepAlive, Sse}, };
Streaming import progress with SSE in Axum
server-sent-events
streams
watch-channel
Advanced
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/hmac-signing-and-constant-time-verify-in-rust-explained-rust-f918/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.