From 1ae6abe049c6ef9c4420fc42c47e2cae0e9fc1f1 Mon Sep 17 00:00:00 2001 From: adlee-was-taken Date: Sat, 11 Apr 2026 22:55:03 -0400 Subject: [PATCH] feat: add Argon2id key derivation with tests --- crates/idfoto-core/src/crypto.rs | 103 +++++++++++++++++++++++++++++++ crates/idfoto-core/src/lib.rs | 3 + 2 files changed, 106 insertions(+) create mode 100644 crates/idfoto-core/src/crypto.rs diff --git a/crates/idfoto-core/src/crypto.rs b/crates/idfoto-core/src/crypto.rs new file mode 100644 index 0000000..94c7299 --- /dev/null +++ b/crates/idfoto-core/src/crypto.rs @@ -0,0 +1,103 @@ +use argon2::{Algorithm, Argon2, Params, Version}; +use serde::{Deserialize, Serialize}; + +use crate::error::{IdfotoError, Result}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KdfParams { + pub argon2_m: u32, + pub argon2_t: u32, + pub argon2_p: u32, +} + +impl Default for KdfParams { + fn default() -> Self { + Self { + argon2_m: 65536, + argon2_t: 3, + argon2_p: 4, + } + } +} + +pub fn derive_master_key( + passphrase: &[u8], + image_secret: &[u8; 32], + salt: &[u8; 32], + params: &KdfParams, +) -> Result<[u8; 32]> { + let argon2_params = Params::new( + params.argon2_m, + params.argon2_t, + params.argon2_p, + Some(32), + ) + .map_err(|e| IdfotoError::Kdf(e.to_string()))?; + + let argon2 = Argon2::new(Algorithm::Argon2id, Version::V0x13, argon2_params); + + // Concatenate passphrase + image_secret as the password input + let mut password = Vec::with_capacity(passphrase.len() + 32); + password.extend_from_slice(passphrase); + password.extend_from_slice(image_secret); + + let mut output = [0u8; 32]; + argon2 + .hash_password_into(&password, salt, &mut output) + .map_err(|e| IdfotoError::Kdf(e.to_string()))?; + + Ok(output) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn fast_params() -> KdfParams { + KdfParams { + argon2_m: 256, + argon2_t: 1, + argon2_p: 1, + } + } + + #[test] + fn derive_master_key_deterministic() { + let passphrase = b"test-passphrase"; + let image_secret = [0x42u8; 32]; + let salt = [0x01u8; 32]; + let params = fast_params(); + + let key1 = derive_master_key(passphrase, &image_secret, &salt, ¶ms).unwrap(); + let key2 = derive_master_key(passphrase, &image_secret, &salt, ¶ms).unwrap(); + + assert_eq!(key1, key2); + } + + #[test] + fn derive_master_key_different_passphrase() { + let image_secret = [0x42u8; 32]; + let salt = [0x01u8; 32]; + let params = fast_params(); + + let key1 = derive_master_key(b"passphrase-one", &image_secret, &salt, ¶ms).unwrap(); + let key2 = derive_master_key(b"passphrase-two", &image_secret, &salt, ¶ms).unwrap(); + + assert_ne!(key1, key2); + } + + #[test] + fn derive_master_key_different_image_secret() { + let passphrase = b"test-passphrase"; + let salt = [0x01u8; 32]; + let params = fast_params(); + + let image_secret1 = [0x11u8; 32]; + let image_secret2 = [0x22u8; 32]; + + let key1 = derive_master_key(passphrase, &image_secret1, &salt, ¶ms).unwrap(); + let key2 = derive_master_key(passphrase, &image_secret2, &salt, ¶ms).unwrap(); + + assert_ne!(key1, key2); + } +} diff --git a/crates/idfoto-core/src/lib.rs b/crates/idfoto-core/src/lib.rs index 92a114f..45c83c0 100644 --- a/crates/idfoto-core/src/lib.rs +++ b/crates/idfoto-core/src/lib.rs @@ -1,2 +1,5 @@ pub mod error; pub use error::{IdfotoError, Result}; + +pub mod crypto; +pub use crypto::{derive_master_key, KdfParams};