#[derive(Debug, Clone)]
pub enum KdfError {
HkdfExpandFailed,
Argon2Failed(String),
}
impl core::fmt::Display for KdfError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::HkdfExpandFailed => write!(
f,
"HKDF expand failed: requested output length exceeds maximum (8160 bytes for SHA-256)"
),
Self::Argon2Failed(msg) => write!(f, "Argon2id derivation failed: {msg}"),
}
}
}
#[cfg(feature = "kdf-hkdf")]
pub fn hkdf_sha256<const N: usize>(
ikm: &[u8],
salt: Option<&[u8]>,
info: &[u8],
) -> Result<[u8; N], KdfError> {
use hkdf::Hkdf;
use sha2::Sha256;
let hk = Hkdf::<Sha256>::new(salt, ikm);
let mut okm = [0u8; N];
hk.expand(info, &mut okm)
.map_err(|_| KdfError::HkdfExpandFailed)?;
Ok(okm)
}
#[cfg(feature = "kdf-argon2")]
#[derive(Debug, Clone)]
pub struct Argon2idConfig {
pub memory_kib: u32,
pub iterations: u32,
pub parallelism: u32,
}
#[cfg(feature = "kdf-argon2")]
impl Default for Argon2idConfig {
fn default() -> Self {
Self {
memory_kib: 19456,
iterations: 2,
parallelism: 1,
}
}
}
#[cfg(feature = "kdf-argon2")]
pub fn argon2id_derive<const N: usize>(password: &[u8], salt: &[u8]) -> Result<[u8; N], KdfError> {
argon2id_derive_with(password, salt, &Argon2idConfig::default())
}
#[cfg(feature = "kdf-argon2")]
pub fn argon2id_derive_with<const N: usize>(
password: &[u8],
salt: &[u8],
config: &Argon2idConfig,
) -> Result<[u8; N], KdfError> {
use argon2::{Algorithm, Argon2, Params, Version};
let params = Params::new(
config.memory_kib,
config.iterations,
config.parallelism,
Some(N),
)
.map_err(|e| KdfError::Argon2Failed(e.to_string()))?;
let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, params);
let mut output = [0u8; N];
argon2
.hash_password_into(password, salt, &mut output)
.map_err(|e| KdfError::Argon2Failed(e.to_string()))?;
Ok(output)
}
#[cfg(test)]
mod tests {
#[cfg(feature = "kdf-hkdf")]
mod hkdf_tests {
use super::super::hkdf_sha256;
#[test]
fn deterministic() {
let key1: [u8; 32] = hkdf_sha256(b"secret", None, b"test-info").unwrap();
let key2: [u8; 32] = hkdf_sha256(b"secret", None, b"test-info").unwrap();
assert_eq!(key1, key2);
}
#[test]
fn different_info_different_key() {
let key1: [u8; 32] = hkdf_sha256(b"secret", None, b"info-a").unwrap();
let key2: [u8; 32] = hkdf_sha256(b"secret", None, b"info-b").unwrap();
assert_ne!(key1, key2);
}
#[test]
fn different_salt_different_key() {
let key1: [u8; 32] = hkdf_sha256(b"secret", Some(b"salt-a"), b"info").unwrap();
let key2: [u8; 32] = hkdf_sha256(b"secret", Some(b"salt-b"), b"info").unwrap();
assert_ne!(key1, key2);
}
#[test]
fn variable_output_lengths() {
let key16: [u8; 16] = hkdf_sha256(b"secret", None, b"info").unwrap();
let key32: [u8; 32] = hkdf_sha256(b"secret", None, b"info").unwrap();
let key64: [u8; 64] = hkdf_sha256(b"secret", None, b"info").unwrap();
assert_eq!(&key16[..], &key32[..16]);
assert_eq!(&key16[..], &key64[..16]);
}
#[test]
fn rfc5869_test_vector() {
let ikm: [u8; 22] = [0x0b; 22];
let salt: [u8; 13] = [
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c,
];
let info: [u8; 10] = [0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9];
let expected: [u8; 42] = [
0x3c, 0xb2, 0x5f, 0x25, 0xfa, 0xac, 0xd5, 0x7a, 0x90, 0x43, 0x4f, 0x64, 0xd0, 0x36,
0x2f, 0x2a, 0x2d, 0x2d, 0x0a, 0x90, 0xcf, 0x1a, 0x5a, 0x4c, 0x5d, 0xb0, 0x2d, 0x56,
0xec, 0xc4, 0xc5, 0xbf, 0x34, 0x00, 0x72, 0x08, 0xd5, 0xb8, 0x87, 0x18, 0x58, 0x65,
];
let okm: [u8; 42] = hkdf_sha256(&ikm, Some(&salt), &info).unwrap();
assert_eq!(okm, expected);
}
}
#[cfg(feature = "kdf-argon2")]
mod argon2_tests {
use super::super::{Argon2idConfig, argon2id_derive, argon2id_derive_with};
#[test]
fn deterministic() {
let key1: [u8; 32] = argon2id_derive(b"password", b"unique-salt-16by").unwrap();
let key2: [u8; 32] = argon2id_derive(b"password", b"unique-salt-16by").unwrap();
assert_eq!(key1, key2);
}
#[test]
fn different_password_different_key() {
let key1: [u8; 32] = argon2id_derive(b"password-a", b"same-salt-16byte").unwrap();
let key2: [u8; 32] = argon2id_derive(b"password-b", b"same-salt-16byte").unwrap();
assert_ne!(key1, key2);
}
#[test]
fn different_salt_different_key() {
let key1: [u8; 32] = argon2id_derive(b"password", b"salt-aaaaaaaaaaaa").unwrap();
let key2: [u8; 32] = argon2id_derive(b"password", b"salt-bbbbbbbbbbbb").unwrap();
assert_ne!(key1, key2);
}
#[test]
fn custom_config() {
let config = Argon2idConfig {
memory_kib: 4096,
iterations: 1,
parallelism: 1,
};
let key: [u8; 32] =
argon2id_derive_with(b"password", b"custom-salt-16by", &config).unwrap();
let default_key: [u8; 32] = argon2id_derive(b"password", b"custom-salt-16by").unwrap();
assert_ne!(key, default_key);
}
}
}