use hmac::{Hmac, Mac};
use sha2::{Digest, Sha256};
#[allow(dead_code)]
#[must_use]
pub(crate) fn hex_encode(bytes: &[u8]) -> String {
crate::hex::hex_encode(bytes)
}
#[allow(dead_code)] #[must_use]
pub(crate) fn sha256_hex(bytes: &[u8]) -> String {
let mut h = Sha256::new();
h.update(bytes);
hex_encode(&h.finalize())
}
#[must_use]
pub(crate) fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
let mut mac = <Hmac<Sha256> as Mac>::new_from_slice(key).expect("HMAC key");
mac.update(data);
mac.finalize().into_bytes().to_vec()
}
#[must_use]
pub fn constant_time_compare(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff: u8 = 0;
for (x, y) in a.iter().zip(b.iter()) {
diff |= x ^ y;
}
diff == 0
}
#[must_use]
pub fn salted_hmac(key_salt: &[u8], value: &[u8], secret: &[u8]) -> Vec<u8> {
let mut hasher = Sha256::new();
hasher.update(secret);
hasher.update(key_salt);
let derived = hasher.finalize();
hmac_sha256(&derived, value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hex_encode_empty_yields_empty() {
assert_eq!(hex_encode(&[]), "");
}
#[test]
fn hex_encode_known_vector() {
assert_eq!(hex_encode(&[0xde, 0xad, 0xbe, 0xef]), "deadbeef");
}
#[test]
fn hex_encode_handles_full_byte_range() {
let all: Vec<u8> = (0..=255u8).collect();
let s = hex_encode(&all);
assert_eq!(s.len(), 512);
assert!(s.starts_with("000102030405"));
assert!(s.ends_with("fafbfcfdfeff"));
}
#[test]
fn hex_encode_uses_lowercase() {
assert_eq!(hex_encode(&[0xAB, 0xCD, 0xEF]), "abcdef");
}
#[test]
fn sha256_hex_empty_input_matches_nist_vector() {
assert_eq!(
sha256_hex(b""),
"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
);
}
#[test]
fn sha256_hex_abc_matches_nist_vector() {
assert_eq!(
sha256_hex(b"abc"),
"ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad"
);
}
#[test]
fn sha256_hex_is_deterministic() {
let a = sha256_hex(b"hello world");
let b = sha256_hex(b"hello world");
assert_eq!(a, b);
assert_eq!(a.len(), 64); }
#[test]
fn hmac_sha256_rfc4231_test_case_1() {
let key = [0x0b_u8; 20];
let data = b"Hi There";
let mac = hmac_sha256(&key, data);
assert_eq!(
hex_encode(&mac),
"b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
);
}
#[test]
fn hmac_sha256_rfc4231_test_case_2() {
let key = b"Jefe";
let data = b"what do ya want for nothing?";
let mac = hmac_sha256(key, data);
assert_eq!(
hex_encode(&mac),
"5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843"
);
}
#[test]
fn hmac_sha256_rfc4231_test_case_3() {
let key = [0xaa_u8; 20];
let data = [0xdd_u8; 50];
let mac = hmac_sha256(&key, &data);
assert_eq!(
hex_encode(&mac),
"773ea91e36800e46854db8ebd09181a72959098b3ef8c122d9635514ced565fe"
);
}
#[test]
fn hmac_sha256_accepts_zero_length_key() {
let mac = hmac_sha256(&[], b"data");
assert_eq!(mac.len(), 32);
}
#[test]
fn hmac_sha256_distinguishes_keys() {
let a = hmac_sha256(b"key-a", b"data");
let b = hmac_sha256(b"key-b", b"data");
assert_ne!(a, b);
}
#[test]
fn ct_compare_equal_inputs_match() {
assert!(constant_time_compare(b"hello", b"hello"));
assert!(constant_time_compare(b"", b""));
}
#[test]
fn ct_compare_different_content_fails() {
assert!(!constant_time_compare(b"hello", b"hellx"));
assert!(!constant_time_compare(b"abcdef", b"abcdez"));
}
#[test]
fn ct_compare_length_mismatch_fails() {
assert!(!constant_time_compare(b"abc", b"abcd"));
assert!(!constant_time_compare(b"abcd", b"abc"));
assert!(!constant_time_compare(b"x", b""));
assert!(!constant_time_compare(b"", b"x"));
}
#[test]
fn ct_compare_handles_full_byte_range() {
let a: Vec<u8> = (0..=255).collect();
let b: Vec<u8> = (0..=255).collect();
assert!(constant_time_compare(&a, &b));
let mut c = b.clone();
c[200] ^= 0x01;
assert!(!constant_time_compare(&a, &c));
}
#[test]
fn ct_compare_first_byte_difference_returns_false() {
assert!(!constant_time_compare(b"Xbcdef", b"abcdef"));
}
#[test]
fn ct_compare_last_byte_difference_returns_false() {
assert!(!constant_time_compare(b"abcdeX", b"abcdef"));
}
#[test]
fn ct_compare_real_hmac_outputs() {
let a = hmac_sha256(b"key", b"msg-1");
let b = hmac_sha256(b"key", b"msg-2");
assert!(!constant_time_compare(&a, &b));
let c = hmac_sha256(b"key", b"msg-1");
assert!(constant_time_compare(&a, &c));
}
#[test]
fn salted_hmac_returns_32_byte_tag() {
let tag = salted_hmac(b"purpose-A", b"value", b"secret");
assert_eq!(tag.len(), 32);
}
#[test]
fn salted_hmac_is_deterministic() {
let a = salted_hmac(b"purpose", b"value", b"secret");
let b = salted_hmac(b"purpose", b"value", b"secret");
assert_eq!(a, b);
}
#[test]
fn salted_hmac_distinguishes_purposes() {
let a = salted_hmac(b"purpose-A", b"value", b"secret");
let b = salted_hmac(b"purpose-B", b"value", b"secret");
assert_ne!(a, b);
}
#[test]
fn salted_hmac_distinguishes_secrets() {
let a = salted_hmac(b"purpose", b"value", b"secret-1");
let b = salted_hmac(b"purpose", b"value", b"secret-2");
assert_ne!(a, b);
}
#[test]
fn salted_hmac_distinguishes_values() {
let a = salted_hmac(b"purpose", b"value-1", b"secret");
let b = salted_hmac(b"purpose", b"value-2", b"secret");
assert_ne!(a, b);
}
#[test]
fn salted_hmac_empty_inputs_dont_panic() {
let _ = salted_hmac(b"", b"", b"");
let _ = salted_hmac(b"salt", b"", b"secret");
let _ = salted_hmac(b"", b"value", b"secret");
}
#[test]
fn salted_hmac_handles_binary_inputs() {
let salt: Vec<u8> = (0u8..=255).collect();
let tag = salted_hmac(&salt, &salt, &salt);
assert_eq!(tag.len(), 32);
}
}