use crate::errors::{CrabError, CrabResult};
use crate::secrets::SecretVec;
use hkdf::Hkdf;
use sha2::Sha256;
pub fn hkdf_extract_expand(
salt: &[u8],
input_key_material: &[u8],
info: &[u8],
output_len: usize,
) -> CrabResult<SecretVec> {
if output_len == 0 || output_len > 255 * 32 {
return Err(CrabError::invalid_input(
"HKDF output length must be between 1 and 8160 bytes for SHA-256",
));
}
let hkdf = Hkdf::<Sha256>::new(Some(salt), input_key_material);
let mut output = vec![0u8; output_len];
hkdf.expand(info, &mut output)
.map_err(|e| CrabError::key_error(format!("HKDF expand failed: {}", e)))?;
Ok(SecretVec::new(output))
}
pub fn hkdf_sha256(input_key_material: &[u8], output_len: usize) -> CrabResult<SecretVec> {
hkdf_extract_expand(&[], input_key_material, &[], output_len)
}
#[cfg(test)]
mod tests {
use super::*;
use hex_literal::hex;
#[test]
fn test_hkdf_basic() {
let ikm = b"input_key_material";
let salt = b"salt";
let info = b"info";
let key = hkdf_extract_expand(salt, ikm, info, 32).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_hkdf_deterministic() {
let ikm = b"input_key_material";
let salt = b"salt";
let info = b"info";
let key1 = hkdf_extract_expand(salt, ikm, info, 32).unwrap();
let key2 = hkdf_extract_expand(salt, ikm, info, 32).unwrap();
assert_eq!(key1.as_slice(), key2.as_slice());
}
#[test]
fn test_hkdf_different_ikm() {
let salt = b"salt";
let info = b"info";
let key1 = hkdf_extract_expand(salt, b"ikm1", info, 32).unwrap();
let key2 = hkdf_extract_expand(salt, b"ikm2", info, 32).unwrap();
assert_ne!(key1.as_slice(), key2.as_slice());
}
#[test]
fn test_hkdf_different_info() {
let ikm = b"input_key_material";
let salt = b"salt";
let key1 = hkdf_extract_expand(salt, ikm, b"info1", 32).unwrap();
let key2 = hkdf_extract_expand(salt, ikm, b"info2", 32).unwrap();
assert_ne!(key1.as_slice(), key2.as_slice());
}
#[test]
fn test_hkdf_no_salt() {
let ikm = b"input_key_material";
let info = b"info";
let key = hkdf_extract_expand(&[], ikm, info, 32).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_hkdf_no_info() {
let ikm = b"input_key_material";
let salt = b"salt";
let key = hkdf_extract_expand(salt, ikm, &[], 32).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_hkdf_sha256_convenience() {
let ikm = b"input_key_material";
let key = hkdf_sha256(ikm, 32).unwrap();
assert_eq!(key.len(), 32);
}
#[test]
fn test_hkdf_variable_length() {
let ikm = b"input_key_material";
let key16 = hkdf_sha256(ikm, 16).unwrap();
let key32 = hkdf_sha256(ikm, 32).unwrap();
let key64 = hkdf_sha256(ikm, 64).unwrap();
assert_eq!(key16.len(), 16);
assert_eq!(key32.len(), 32);
assert_eq!(key64.len(), 64);
}
#[test]
fn test_hkdf_rfc_vector() {
let ikm = hex!("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b");
let salt = hex!("000102030405060708090a0b0c");
let info = hex!("f0f1f2f3f4f5f6f7f8f9");
let expected = hex!("3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf");
let key = hkdf_extract_expand(&salt, &ikm, &info, 32).unwrap();
assert_eq!(key.as_slice(), &expected[..]);
}
#[test]
fn test_hkdf_max_output_length() {
let ikm = b"input_key_material";
let key = hkdf_sha256(ikm, 255 * 32).unwrap();
assert_eq!(key.len(), 255 * 32);
}
#[test]
fn test_hkdf_invalid_output_length() {
let ikm = b"input_key_material";
let result = hkdf_sha256(ikm, 255 * 32 + 1);
assert!(result.is_err());
let result = hkdf_sha256(ikm, 0);
assert!(result.is_err());
}
}