#![forbid(unsafe_code)]
extern crate alloc;
use alloc::vec::Vec;
use oxicrypto_core::{CryptoError, SecretVec};
const MAX_OUTPUT_BYTES: usize = 64 * 1024;
#[must_use = "derived key should be used"]
pub fn kbkdf_counter_hmac_sha256(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<Vec<u8>, CryptoError> {
kbkdf_counter_sha256(key_in, label, context, output_len)
}
#[must_use = "derived key should be used"]
pub fn kbkdf_counter_hmac_sha384(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<Vec<u8>, CryptoError> {
kbkdf_counter_sha384(key_in, label, context, output_len)
}
#[must_use = "derived key should be used"]
pub fn kbkdf_counter_hmac_sha512(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<Vec<u8>, CryptoError> {
kbkdf_counter_sha512(key_in, label, context, output_len)
}
#[must_use = "derived key should be used"]
pub fn kbkdf_counter_hmac_sha256_secret(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<SecretVec, CryptoError> {
let bytes = kbkdf_counter_hmac_sha256(key_in, label, context, output_len)?;
Ok(SecretVec::new(bytes))
}
fn validate_params(output_len: usize) -> Result<(), CryptoError> {
if output_len == 0 || output_len > MAX_OUTPUT_BYTES {
return Err(CryptoError::BadInput);
}
Ok(())
}
fn kbkdf_counter_sha256(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<Vec<u8>, CryptoError> {
use digest::KeyInit;
use hmac::Mac as HmacMac;
validate_params(output_len)?;
const HASH_LEN: usize = 32;
let l_bits = (output_len as u32).saturating_mul(8);
let n_blocks = output_len.div_ceil(HASH_LEN);
let mut out = Vec::with_capacity(n_blocks * HASH_LEN);
for i in 1u32..=n_blocks as u32 {
let mut mac = hmac::Hmac::<sha2::Sha256>::new_from_slice(key_in)
.map_err(|_| CryptoError::BadInput)?;
mac.update(&i.to_be_bytes());
mac.update(label);
mac.update(&[0x00u8]);
mac.update(context);
mac.update(&l_bits.to_be_bytes());
let block = mac.finalize().into_bytes();
out.extend_from_slice(&block);
}
out.truncate(output_len);
Ok(out)
}
fn kbkdf_counter_sha384(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<Vec<u8>, CryptoError> {
use digest::KeyInit;
use hmac::Mac as HmacMac;
validate_params(output_len)?;
const HASH_LEN: usize = 48;
let l_bits = (output_len as u32).saturating_mul(8);
let n_blocks = output_len.div_ceil(HASH_LEN);
let mut out = Vec::with_capacity(n_blocks * HASH_LEN);
for i in 1u32..=n_blocks as u32 {
let mut mac = hmac::Hmac::<sha2::Sha384>::new_from_slice(key_in)
.map_err(|_| CryptoError::BadInput)?;
mac.update(&i.to_be_bytes());
mac.update(label);
mac.update(&[0x00u8]);
mac.update(context);
mac.update(&l_bits.to_be_bytes());
let block = mac.finalize().into_bytes();
out.extend_from_slice(&block);
}
out.truncate(output_len);
Ok(out)
}
fn kbkdf_counter_sha512(
key_in: &[u8],
label: &[u8],
context: &[u8],
output_len: usize,
) -> Result<Vec<u8>, CryptoError> {
use digest::KeyInit;
use hmac::Mac as HmacMac;
validate_params(output_len)?;
const HASH_LEN: usize = 64;
let l_bits = (output_len as u32).saturating_mul(8);
let n_blocks = output_len.div_ceil(HASH_LEN);
let mut out = Vec::with_capacity(n_blocks * HASH_LEN);
for i in 1u32..=n_blocks as u32 {
let mut mac = hmac::Hmac::<sha2::Sha512>::new_from_slice(key_in)
.map_err(|_| CryptoError::BadInput)?;
mac.update(&i.to_be_bytes());
mac.update(label);
mac.update(&[0x00u8]);
mac.update(context);
mac.update(&l_bits.to_be_bytes());
let block = mac.finalize().into_bytes();
out.extend_from_slice(&block);
}
out.truncate(output_len);
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
const KEY: &[u8] = b"0123456789abcdef0123456789abcdef"; const LABEL: &[u8] = b"encryption key";
const CONTEXT: &[u8] = b"session-id:abc";
#[test]
fn sha256_deterministic() {
let a = kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, 32).unwrap();
let b = kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, 32).unwrap();
assert_eq!(a, b, "KBKDF must be deterministic");
assert_ne!(a, vec![0u8; 32], "KBKDF output must not be all-zero");
}
#[test]
fn sha384_deterministic() {
let a = kbkdf_counter_hmac_sha384(KEY, LABEL, CONTEXT, 48).unwrap();
let b = kbkdf_counter_hmac_sha384(KEY, LABEL, CONTEXT, 48).unwrap();
assert_eq!(a, b);
assert_ne!(a, vec![0u8; 48]);
}
#[test]
fn sha512_deterministic() {
let a = kbkdf_counter_hmac_sha512(KEY, LABEL, CONTEXT, 64).unwrap();
let b = kbkdf_counter_hmac_sha512(KEY, LABEL, CONTEXT, 64).unwrap();
assert_eq!(a, b);
assert_ne!(a, vec![0u8; 64]);
}
#[test]
fn sha256_output_length_exact() {
for len in [1usize, 16, 32, 64, 100, 128] {
let out = kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, len)
.unwrap_or_else(|e| panic!("len={len} failed: {e}"));
assert_eq!(out.len(), len, "output length mismatch at len={len}");
}
}
#[test]
fn sha384_output_length_exact() {
for len in [1usize, 48, 96, 200] {
let out = kbkdf_counter_hmac_sha384(KEY, LABEL, CONTEXT, len).unwrap();
assert_eq!(out.len(), len);
}
}
#[test]
fn sha512_output_length_exact() {
for len in [1usize, 64, 128, 300] {
let out = kbkdf_counter_hmac_sha512(KEY, LABEL, CONTEXT, len).unwrap();
assert_eq!(out.len(), len);
}
}
#[test]
fn different_labels_differ() {
let a = kbkdf_counter_hmac_sha256(KEY, b"label-a", CONTEXT, 32).unwrap();
let b = kbkdf_counter_hmac_sha256(KEY, b"label-b", CONTEXT, 32).unwrap();
assert_ne!(a, b, "different labels must produce different output");
}
#[test]
fn different_contexts_differ() {
let a = kbkdf_counter_hmac_sha256(KEY, LABEL, b"ctx-a", 32).unwrap();
let b = kbkdf_counter_hmac_sha256(KEY, LABEL, b"ctx-b", 32).unwrap();
assert_ne!(a, b, "different contexts must produce different output");
}
#[test]
fn different_keys_differ() {
let key2 = b"ffffffffffffffffffffffffffffffff";
let a = kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, 32).unwrap();
let b = kbkdf_counter_hmac_sha256(key2, LABEL, CONTEXT, 32).unwrap();
assert_ne!(a, b);
}
#[test]
fn sha256_sha384_sha512_differ() {
let a = kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, 32).unwrap();
let b = kbkdf_counter_hmac_sha384(KEY, LABEL, CONTEXT, 32).unwrap();
let c = kbkdf_counter_hmac_sha512(KEY, LABEL, CONTEXT, 32).unwrap();
assert_ne!(a, b);
assert_ne!(a, c);
assert_ne!(b, c);
}
#[test]
fn zero_output_len_rejected() {
assert_eq!(
kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, 0),
Err(CryptoError::BadInput)
);
assert_eq!(
kbkdf_counter_hmac_sha384(KEY, LABEL, CONTEXT, 0),
Err(CryptoError::BadInput)
);
assert_eq!(
kbkdf_counter_hmac_sha512(KEY, LABEL, CONTEXT, 0),
Err(CryptoError::BadInput)
);
}
#[test]
fn output_too_large_rejected() {
assert_eq!(
kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, MAX_OUTPUT_BYTES + 1),
Err(CryptoError::BadInput)
);
}
#[test]
fn empty_key_rejected() {
let _ = kbkdf_counter_hmac_sha256(b"", LABEL, CONTEXT, 32);
}
#[test]
fn secret_wrapper_matches_plain() {
let plain = kbkdf_counter_hmac_sha256(KEY, LABEL, CONTEXT, 32).unwrap();
let secret = kbkdf_counter_hmac_sha256_secret(KEY, LABEL, CONTEXT, 32).unwrap();
assert_eq!(plain.as_slice(), secret.as_bytes());
}
#[test]
fn secret_wrapper_zero_output_len_rejected() {
assert!(
kbkdf_counter_hmac_sha256_secret(KEY, LABEL, CONTEXT, 0).is_err(),
"zero output_len must return an error"
);
}
}