HMAC Generator — Rust source
Generate a keyed-hash HMAC (SHA-1/256/384/512) for a message and secret. Runs entirely in your browser via Web Crypto, with a shareable link to your exact input.
This is the Rust implementation — the same logic the interactive tool runs, in a shareable, citable form.
// hmac-generator — Rust polyglot showcase port
//
// Language: Rust
// Ported from: src/lib/hmac.ts
// Display source — part of CosmoDev's polyglot tool pages.
//
// Keyed-hash HMAC using the RustCrypto crates `hmac`, `sha2`, and `sha1`.
// Rust's standard library deliberately ships no cryptographic primitives, so
// the idiomatic and secure path is the audited RustCrypto family — these are
// the de-facto standard hashing crates in the Rust ecosystem. Add to
// Cargo.toml:
//
// sha1 = "0.10"
// sha2 = "0.10"
// hmac = "0.12"
//
// Behavior mirrors the TypeScript reference: UTF-8 inputs, lowercase hex
// output, SHA-256 by default, and rejection of empty secrets and unknown
// algorithms.
use hmac::{Hmac, Mac};
use sha1::Sha1;
use sha2::{Sha256, Sha384, Sha512};
// Fixed digest type aliases per algorithm. Pinning the digest type at compile
// time means the HMAC tag length is statically correct for each arm below —
// there is no runtime-sized hash state to mismanage.
type HmacSha1 = Hmac<Sha1>;
type HmacSha256 = Hmac<Sha256>;
type HmacSha384 = Hmac<Sha384>;
type HmacSha512 = Hmac<Sha512>;
/// Canonical algorithm names. The spellings match the TypeScript union so the
/// same string works across every port.
pub const SHA1: &str = "SHA-1";
pub const SHA256: &str = "SHA-256"; // default algorithm
pub const SHA384: &str = "SHA-384";
pub const SHA512: &str = "SHA-512";
/// Errors returned by [`hmac_hex`]. Kept as a concrete enum (not a string) so
/// callers can `match` on the kind — the idiomatic Rust error shape.
#[derive(Debug, PartialEq, Eq)]
pub enum HmacError {
/// The algorithm string was not one of the supported names.
UnknownAlgorithm(String),
/// An empty secret was supplied; the TypeScript reference rejects this too.
EmptySecret,
}
impl std::fmt::Display for HmacError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HmacError::UnknownAlgorithm(a) => write!(f, "unsupported HMAC algorithm: {a}"),
HmacError::EmptySecret => write!(f, "HMAC secret must not be empty"),
}
}
}
impl std::error::Error for HmacError {}
/// Lowercase hexadecimal encoding of a byte sequence.
///
/// Generic over `AsRef<[u8]>` so it accepts slices, arrays, and the
/// `GenericArray` tag returned by `finalize()` without an intermediate
/// allocation. Lookup-table based to avoid the per-byte `format!` allocation
/// that clippy flags.
fn to_hex<T: AsRef<[u8]>>(bytes: T) -> String {
const HEX_DIGITS: &[u8; 16] = b"0123456789abcdef";
let bytes = bytes.as_ref();
let mut out = String::with_capacity(bytes.len() * 2);
for &b in bytes {
out.push(HEX_DIGITS[(b >> 4) as usize] as char);
out.push(HEX_DIGITS[(b & 0x0f) as usize] as char);
}
out
}
/// Compute HMAC(`message`, `secret`) under the named algorithm and return it
/// as lowercase hex.
///
/// Both inputs are treated as UTF-8 bytes. An empty `algorithm` selects
/// SHA-256 (Rust has no default-parameter syntax, so the empty string is the
/// default signal); an empty `secret` is rejected to match SubtleCrypto's
/// refusal of a zero-length key.
///
/// The `Mac` trait is the idiomatic entry point: build a keyed instance from
/// the secret, `update` with the message, then `finalize` to the tag.
pub fn hmac_hex(message: &str, secret: &str, algorithm: &str) -> Result<String, HmacError> {
let algorithm = if algorithm.is_empty() { SHA256 } else { algorithm };
if secret.is_empty() {
return Err(HmacError::EmptySecret);
}
// Dispatch on the algorithm name. Each arm fixes the digest type at
// compile time, so the tag length is statically determined. `new_from_slice`
// returns a Result that can only error on an invalid key length — but HMAC
// accepts keys of any length per RFC 2104, so the expect is sound.
let tag = match algorithm {
SHA1 => {
let mut mac = HmacSha1::new_from_slice(secret.as_bytes())
.expect("HMAC accepts keys of any length per RFC 2104");
mac.update(message.as_bytes());
mac.finalize().into_bytes()
}
SHA256 => {
let mut mac = HmacSha256::new_from_slice(secret.as_bytes())
.expect("HMAC accepts keys of any length per RFC 2104");
mac.update(message.as_bytes());
mac.finalize().into_bytes()
}
SHA384 => {
let mut mac = HmacSha384::new_from_slice(secret.as_bytes())
.expect("HMAC accepts keys of any length per RFC 2104");
mac.update(message.as_bytes());
mac.finalize().into_bytes()
}
SHA512 => {
let mut mac = HmacSha512::new_from_slice(secret.as_bytes())
.expect("HMAC accepts keys of any length per RFC 2104");
mac.update(message.as_bytes());
mac.finalize().into_bytes()
}
other => return Err(HmacError::UnknownAlgorithm(other.to_string())),
};
Ok(to_hex(tag))
}
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →