Skip to content

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 →