extern crate alloc;
use alloc::vec;
use alloc::vec::Vec;
use oxicrypto_core::{CryptoError, StreamingHash};
use subtle::ConstantTimeEq;
pub struct StreamingHashHmac<H, F>
where
H: StreamingHash,
F: Fn() -> H + Send,
{
padded_key: Vec<u8>,
block_size: usize,
output_len: usize,
factory: F,
}
impl<H, F> StreamingHashHmac<H, F>
where
H: StreamingHash,
F: Fn() -> H + Send,
{
pub fn new(
key: &[u8],
block_size: usize,
output_len: usize,
factory: F,
) -> Result<Self, CryptoError> {
if block_size == 0 || output_len == 0 {
return Err(CryptoError::BadInput);
}
let effective_key: Vec<u8> = if key.len() > block_size {
let mut hashed = vec![0u8; output_len];
let mut h = (factory)();
h.update(key);
h.finalize(&mut hashed)?;
hashed
} else {
key.to_vec()
};
let mut padded_key = vec![0u8; block_size];
let copy_len = effective_key.len().min(block_size);
padded_key[..copy_len].copy_from_slice(&effective_key[..copy_len]);
Ok(Self {
padded_key,
block_size,
output_len,
factory,
})
}
pub fn mac_oneshot(&self, msg: &[u8], out: &mut [u8]) -> Result<(), CryptoError> {
if out.len() < self.output_len {
return Err(CryptoError::BufferTooSmall);
}
let ipad_key: Vec<u8> = self.padded_key.iter().map(|b| b ^ 0x36u8).collect();
let opad_key: Vec<u8> = self.padded_key.iter().map(|b| b ^ 0x5cu8).collect();
let mut inner_tag = vec![0u8; self.output_len];
{
let mut h = (self.factory)();
h.update(&ipad_key);
h.update(msg);
h.finalize(&mut inner_tag)?;
}
{
let mut h = (self.factory)();
h.update(&opad_key);
h.update(&inner_tag);
h.finalize(&mut out[..self.output_len])?;
}
Ok(())
}
pub fn output_len(&self) -> usize {
self.output_len
}
pub fn block_size(&self) -> usize {
self.block_size
}
pub fn verify(&self, msg: &[u8], expected: &[u8]) -> Result<(), CryptoError> {
if expected.len() != self.output_len {
return Err(CryptoError::InvalidTag);
}
let mut tag = vec![0u8; self.output_len];
self.mac_oneshot(msg, &mut tag)?;
if tag.as_slice().ct_eq(expected).into() {
Ok(())
} else {
Err(CryptoError::InvalidTag)
}
}
pub fn streaming_session(&self) -> StreamingHashHmacSession<H, F>
where
F: Clone,
{
StreamingHashHmacSession::new(self)
}
}
pub struct StreamingHashHmacSession<H, F>
where
H: StreamingHash,
F: Fn() -> H + Send,
{
inner: H,
opad_key: Vec<u8>,
output_len: usize,
factory: F,
}
impl<H, F> StreamingHashHmacSession<H, F>
where
H: StreamingHash,
F: Fn() -> H + Send + Clone,
{
fn new(hmac: &StreamingHashHmac<H, F>) -> Self
where
F: Clone,
{
let ipad_key: Vec<u8> = hmac.padded_key.iter().map(|b| b ^ 0x36u8).collect();
let opad_key: Vec<u8> = hmac.padded_key.iter().map(|b| b ^ 0x5cu8).collect();
let mut inner = (hmac.factory)();
inner.update(&ipad_key);
Self {
inner,
opad_key,
output_len: hmac.output_len,
factory: hmac.factory.clone(),
}
}
pub fn update(&mut self, data: &[u8]) {
self.inner.update(data);
}
pub fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
if out.len() < self.output_len {
return Err(CryptoError::BufferTooSmall);
}
let mut inner_tag = vec![0u8; self.output_len];
self.inner.finalize(&mut inner_tag)?;
let mut outer = (self.factory)();
outer.update(&self.opad_key);
outer.update(&inner_tag);
outer.finalize(&mut out[..self.output_len])?;
Ok(())
}
}
pub fn hmac_with_streaming_hash<H, F>(
key: &[u8],
block_size: usize,
output_len: usize,
msg: &[u8],
make_hash: F,
) -> Result<Vec<u8>, CryptoError>
where
H: StreamingHash,
F: Fn() -> H + Send,
{
let hmac = StreamingHashHmac::new(key, block_size, output_len, make_hash)?;
let mut tag = vec![0u8; output_len];
hmac.mac_oneshot(msg, &mut tag)?;
Ok(tag)
}
#[cfg(test)]
mod tests {
use super::*;
struct SimpleSha256Hasher {
inner: sha2::Sha256,
}
impl SimpleSha256Hasher {
fn new() -> Self {
use sha2::Digest;
Self {
inner: sha2::Sha256::new(),
}
}
}
impl StreamingHash for SimpleSha256Hasher {
fn update(&mut self, data: &[u8]) {
sha2::Digest::update(&mut self.inner, data);
}
fn finalize(self, out: &mut [u8]) -> Result<(), CryptoError> {
use sha2::Digest;
if out.len() < 32 {
return Err(CryptoError::BufferTooSmall);
}
let result = self.inner.finalize();
out[..32].copy_from_slice(&result);
Ok(())
}
fn reset(&mut self) {
sha2::Digest::reset(&mut self.inner);
}
}
#[test]
fn test_hmac_sha256_rfc4231_tc1() {
let key = vec![0x0bu8; 20];
let msg = b"Hi There";
let expected =
hex_decode("b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7");
let result = hmac_with_streaming_hash(&key, 64, 32, msg, SimpleSha256Hasher::new)
.expect("hmac_with_streaming_hash failed");
assert_eq!(result, expected);
}
#[test]
fn test_hmac_sha256_rfc4231_tc2() {
let key = b"Jefe";
let msg = b"what do ya want for nothing?";
let expected =
hex_decode("5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843");
let result = hmac_with_streaming_hash(key, 64, 32, msg, SimpleSha256Hasher::new)
.expect("hmac_with_streaming_hash failed");
assert_eq!(result, expected);
}
#[test]
fn test_hmac_sha256_rfc4231_tc5_long_key() {
let key = vec![0xaau8; 131];
let msg = b"Test Using Larger Than Block-Size Key - Hash Key First";
let expected =
hex_decode("60e431591ee0b67f0d8a26aacbf5b77f8e0bc6213728c5140546040f0ee37f54");
let result = hmac_with_streaming_hash(&key, 64, 32, msg, SimpleSha256Hasher::new)
.expect("hmac with long key failed");
assert_eq!(result, expected);
}
#[test]
fn test_hmac_verify_correct_and_incorrect() {
let key = b"test-key";
let msg = b"test-message";
let hmac = StreamingHashHmac::new(key, 64, 32, SimpleSha256Hasher::new)
.expect("StreamingHashHmac::new failed");
let mut tag = vec![0u8; 32];
hmac.mac_oneshot(msg, &mut tag).expect("mac_oneshot");
assert!(hmac.verify(msg, &tag).is_ok(), "correct tag should verify");
let mut bad_tag = tag.clone();
bad_tag[0] ^= 0x01;
assert!(
hmac.verify(msg, &bad_tag).is_err(),
"flipped tag should fail"
);
}
#[test]
fn test_streaming_session_matches_oneshot() {
let key = b"streaming-test-key";
let msg = b"the quick brown fox jumps over the lazy dog";
let hmac = StreamingHashHmac::new(key, 64, 32, SimpleSha256Hasher::new)
.expect("StreamingHashHmac::new");
let mut tag_oneshot = vec![0u8; 32];
hmac.mac_oneshot(msg, &mut tag_oneshot)
.expect("mac_oneshot");
let hmac2 = StreamingHashHmac::new(key, 64, 32, SimpleSha256Hasher::new).expect("new2");
let mut session = hmac2.streaming_session();
for chunk in msg.chunks(7) {
session.update(chunk);
}
let mut tag_streaming = vec![0u8; 32];
session.finalize(&mut tag_streaming).expect("finalize");
assert_eq!(tag_oneshot, tag_streaming);
}
#[test]
fn test_different_keys_produce_different_macs() {
let msg = b"same message";
let r1 =
hmac_with_streaming_hash(b"key-alpha", 64, 32, msg, SimpleSha256Hasher::new).unwrap();
let r2 =
hmac_with_streaming_hash(b"key-beta", 64, 32, msg, SimpleSha256Hasher::new).unwrap();
assert_ne!(r1, r2);
}
#[test]
fn test_empty_message_accepted() {
let result = hmac_with_streaming_hash(b"key", 64, 32, b"", SimpleSha256Hasher::new);
assert!(result.is_ok());
assert_eq!(result.unwrap().len(), 32);
}
#[test]
fn test_buffer_too_small() {
let hmac = StreamingHashHmac::new(b"key", 64, 32, SimpleSha256Hasher::new)
.expect("StreamingHashHmac::new");
let mut out = vec![0u8; 16]; assert!(
hmac.mac_oneshot(b"msg", &mut out).is_err(),
"should fail with buffer too small"
);
}
fn hex_decode(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).expect("hex decode"))
.collect()
}
}