use super::*; use dcrypt_common::security::SecretVec;
use hex; use rand::RngCore; use std::ops::Deref;
const PASSWORD: [u8; 32] = [0x01; 32]; const SALT: [u8; 16] = [0x02; 16]; const SECRET: [u8; 8] = [0x03; 8]; const AD: [u8; 12] = [0x04; 12]; const SALT_LEN: usize = 16;
fn rfc_params(argon_type: Algorithm) -> Params<SALT_LEN> {
Params {
argon_type,
memory_cost: 32, time_cost: 3, parallelism: 4, salt: Salt::new(SALT),
ad: Some(Zeroizing::new(AD.to_vec())),
secret: Some(Zeroizing::new(SECRET.to_vec())),
output_len: 32, version: ARGON2_VERSION_1_3,
}
}
#[test]
fn blake2b_argon2_h0_param_block_exact() {
use crate::hash::blake2::BLAKE2B_IV;
let b = create_blake2b_for_h0();
assert_eq!(b.h[0] ^ BLAKE2B_IV[0], 0x0101_0040);
}
#[test]
fn argon2d_rfc_vector_a1() -> Result<()> {
let password = SecretVec::from_slice(&PASSWORD);
let argon2 = Argon2::new_with_params(rfc_params(Algorithm::Argon2d));
let hash = argon2.hash_password(password.as_ref())?;
println!(">>> argon2d hash = {}", hex::encode(&hash));
let expected_hex = "512b391b6f1162975371d30919734294f868e3be3984f3c1a13a4db9fabe4acb";
let expected_bytes = hex::decode(expected_hex).expect("Invalid expected hex");
assert_eq!(
hash.as_slice(),
expected_bytes.as_slice(),
"Argon2d RFC vector mismatch"
);
Ok(())
}
#[test]
fn argon2i_rfc_vector_a3() -> Result<()> {
let password = SecretVec::from_slice(&PASSWORD);
let argon2 = Argon2::new_with_params(rfc_params(Algorithm::Argon2i));
let hash = argon2.hash_password(password.as_ref())?;
println!(">>> argon2i hash = {}", hex::encode(&hash));
let expected_hex = "c814d9d1dc7f37aa13f0d77f2494bda1c8de6b016dd388d29952a4c4672b6ce8";
let expected_bytes = hex::decode(expected_hex).expect("Invalid expected hex");
assert_eq!(
hash.as_slice(),
expected_bytes.as_slice(),
"Argon2i RFC vector mismatch"
);
Ok(())
}
#[test]
fn argon2id_rfc_vector_a5() -> Result<()> {
let password = SecretVec::from_slice(&PASSWORD);
let argon2 = Argon2::new_with_params(rfc_params(Algorithm::Argon2id));
let hash = argon2.hash_password(password.as_ref())?;
println!(">>> argon2id hash = {}", hex::encode(&hash));
let expected_hex = "0d640df58d78766c08c037a34a8b53c9d01ef0452d75b65eb52520e96b01e659";
let expected_bytes = hex::decode(expected_hex).expect("Invalid expected hex");
assert_eq!(
hash.as_slice(),
expected_bytes.as_slice(),
"Argon2id RFC vector mismatch"
);
Ok(())
}
#[test]
fn argon2id_reference_implementation_vector() -> Result<()> {
const REF_SALT_LEN: usize = 8;
let password = SecretVec::from_slice(b"password");
let salt = Salt::<REF_SALT_LEN>::new(*b"somesalt");
let params = Params::<REF_SALT_LEN> {
argon_type: Algorithm::Argon2id,
memory_cost: 32,
time_cost: 2,
parallelism: 4,
salt,
ad: None,
secret: None,
output_len: 32,
version: ARGON2_VERSION_1_3,
};
let argon2 = Argon2::new_with_params(params);
let hash = argon2.hash_password(password.as_ref())?;
println!(
">>> argon2id reference implementation hash = {}",
hex::encode(&hash)
);
let expected_hex = "d74d7db154b312931625cde5a51f76bc52113b4b0515aa94952203b3cc45b800";
let expected_bytes = hex::decode(expected_hex).expect("Invalid expected hex");
assert_eq!(
hash.as_slice(),
expected_bytes.as_slice(),
"Argon2id reference implementation vector mismatch"
);
Ok(())
}
#[test]
fn argon2id_phc_string_hash_verify() -> Result<()> {
let mut pw_bytes = [b' '; 32];
let src_pw = b"complexP@$$w0rd!";
pw_bytes[..src_pw.len()].copy_from_slice(src_pw);
let password = SecretBytes::<32>::new(pw_bytes);
const SALT_LEN: usize = 16;
let mut salt_data_arr = [0u8; SALT_LEN];
rand::thread_rng().fill_bytes(&mut salt_data_arr);
let salt_struct = Salt::<SALT_LEN>::new(salt_data_arr);
let ad_data_vec = b"user@example.com".to_vec();
let params_for_hasher = Params::<SALT_LEN> {
argon_type: Algorithm::Argon2id,
memory_cost: 65536,
time_cost: 2,
parallelism: 4,
salt: salt_struct.clone(),
ad: Some(Zeroizing::new(ad_data_vec.clone())),
secret: None,
output_len: 32,
version: ARGON2_VERSION_1_3,
};
let argon2_hasher = Argon2::new_with_params(params_for_hasher);
let phc_hash_obj =
<Argon2<SALT_LEN> as PasswordHashFunction>::hash_password(&argon2_hasher, &password)?;
assert_eq!(phc_hash_obj.algorithm, "argon2id");
assert_eq!(
phc_hash_obj.param("v").unwrap().as_str(),
ARGON2_VERSION_1_3.to_string().as_str()
);
assert_eq!(phc_hash_obj.param("m").unwrap(), "65536");
assert_eq!(phc_hash_obj.param("t").unwrap(), "2");
assert_eq!(phc_hash_obj.param("p").unwrap(), "4");
assert_eq!(
phc_hash_obj.param("data").unwrap(),
&base64::engine::general_purpose::STANDARD_NO_PAD.encode(&ad_data_vec)
);
assert_eq!(phc_hash_obj.salt.deref(), salt_struct.as_ref());
assert_eq!(phc_hash_obj.hash.len(), 32);
let argon2_verifier = Argon2::<SALT_LEN>::new();
let is_valid = argon2_verifier.verify(&password, &phc_hash_obj)?;
assert!(is_valid, "Verification failed for the correct password");
let mut wrong_pw_bytes = [b' '; 32];
let src_wrong_pw = b"wrongpassword";
wrong_pw_bytes[..src_wrong_pw.len()].copy_from_slice(src_wrong_pw);
let wrong_password = SecretBytes::<32>::new(wrong_pw_bytes);
let is_invalid = argon2_verifier.verify(&wrong_password, &phc_hash_obj)?;
assert!(
!is_invalid,
"Verification succeeded for an incorrect password"
);
let mut tampered_hash_obj = phc_hash_obj.clone();
tampered_hash_obj
.params
.insert("m".to_string(), "32768".to_string());
let verify_tampered_params_result = argon2_verifier.verify(&password, &tampered_hash_obj)?;
assert!(
!verify_tampered_params_result,
"Verification succeeded with tampered memory cost"
);
let mut tampered_salt_hash_obj = phc_hash_obj.clone();
let mut salt_vec = tampered_salt_hash_obj.salt.to_vec();
salt_vec[0] ^= 0xff;
tampered_salt_hash_obj.salt = Zeroizing::new(salt_vec);
let verify_tampered_salt_result = argon2_verifier.verify(&password, &tampered_salt_hash_obj)?;
assert!(
!verify_tampered_salt_result,
"Verification succeeded with tampered salt"
);
let mut tampered_hash_val_obj = phc_hash_obj.clone();
let mut mutable_hash = tampered_hash_val_obj.hash.to_vec();
mutable_hash[0] ^= 0xff;
tampered_hash_val_obj.hash = Zeroizing::new(mutable_hash);
let verify_tampered_hash_result = argon2_verifier.verify(&password, &tampered_hash_val_obj)?;
assert!(
!verify_tampered_hash_result,
"Verification succeeded with tampered hash value"
);
Ok(())
}
#[test]
fn argon2_builder_overrides_work() -> Result<()> {
const SALT_LEN: usize = 16;
let base_salt_data = [0u8; SALT_LEN];
let base_salt = Salt::<SALT_LEN>::new(base_salt_data);
let base_kdf_params = Params {
argon_type: Algorithm::Argon2id,
memory_cost: 65536,
time_cost: 3,
parallelism: 2,
salt: base_salt.clone(),
ad: Some(Zeroizing::new(b"base_ad".to_vec())),
secret: Some(Zeroizing::new(b"base_secret".to_vec())),
output_len: 32,
version: ARGON2_VERSION_1_3,
};
let base_kdf = Argon2::<SALT_LEN>::new_with_params(base_kdf_params);
let ikm = b"my input key material";
let builder_salt_data = [1u8; SALT_LEN];
let builder_salt_slice = &builder_salt_data[..];
let builder_info = b"builder context info";
let output_len_override = 64;
let derived_key_via_builder = base_kdf
.builder()
.with_ikm(ikm)
.with_salt(builder_salt_slice)
.with_info(builder_info)
.with_output_length(output_len_override)
.derive()?;
assert_eq!(
derived_key_via_builder.len(),
output_len_override,
"Builder derived key length mismatch"
);
let mut manual_salt_data = [0u8; SALT_LEN];
manual_salt_data.copy_from_slice(builder_salt_slice);
let manual_params_for_comparison = Params {
argon_type: base_kdf.params.argon_type,
memory_cost: base_kdf.params.memory_cost,
time_cost: base_kdf.params.time_cost,
parallelism: base_kdf.params.parallelism,
salt: Salt::<SALT_LEN>::new(manual_salt_data), ad: Some(Zeroizing::new(builder_info.to_vec())), secret: base_kdf.params.secret.clone(), output_len: output_len_override, version: base_kdf.params.version,
};
let manual_kdf = Argon2::new_with_params(manual_params_for_comparison);
let manual_derived_key_vec = manual_kdf.hash_password(ikm)?.to_vec();
assert_eq!(
derived_key_via_builder, manual_derived_key_vec,
"Builder derivation differs from manual derivation with same params"
);
Ok(())
}
#[test]
fn generate_salt_correct_size() {
const TEST_SALT_SIZE: usize = 24;
let salt = Argon2::<TEST_SALT_SIZE>::generate_salt(&mut rand::rngs::OsRng);
assert_eq!(salt.as_ref().len(), TEST_SALT_SIZE);
let specific_salt =
Salt::<TEST_SALT_SIZE>::random_with_size(&mut rand::rngs::OsRng, TEST_SALT_SIZE).unwrap();
assert_eq!(specific_salt.as_ref().len(), TEST_SALT_SIZE);
}
#[test]
fn h_prime_1024_matches_rfc_a1_block0() -> Result<()> {
let mut h0_buffer_vec = Vec::with_capacity(ARGON2_PREHASH_SEED_LENGTH);
h0_buffer_vec.extend_from_slice(&4u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&32u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&32u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&3u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&0x13u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&0u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&32u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&PASSWORD); h0_buffer_vec.extend_from_slice(&16u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&SALT); h0_buffer_vec.extend_from_slice(&8u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&SECRET); h0_buffer_vec.extend_from_slice(&12u32.to_le_bytes()); h0_buffer_vec.extend_from_slice(&AD); let h0_buffer = Zeroizing::new(h0_buffer_vec);
use crate::hash::blake2::Blake2b; let mut h0_hasher = Blake2b::with_output_size(64);
h0_hasher.update(&h0_buffer)?;
let h0_digest = h0_hasher.finalize()?;
let computed_h0 = Zeroizing::new(h0_digest.as_ref().to_vec());
let expected_rfc_h0 = hex::decode(
"b8819791a0359660bb7709c85fa48f04d5d82c05c5f215ccdb885491717cf757082c28b951be381410b5fc2eb7274033b9fdc7ae672bcaac5d179097a4af3109"
).expect("Failed to decode expected RFC H0 hex");
assert_eq!(
computed_h0.as_slice(),
&expected_rfc_h0[..],
"Standard Blake2b H0 computation for RFC vector mismatch"
);
let mut block0_seed = Vec::with_capacity(computed_h0.len() + 8);
block0_seed.extend_from_slice(&computed_h0);
block0_seed.extend_from_slice(&0u32.to_le_bytes()); block0_seed.extend_from_slice(&0u32.to_le_bytes()); let block0_seed_zeroizing = Zeroizing::new(block0_seed);
let block0 = h_prime_variable_output(&block0_seed_zeroizing, ARGON2_BLOCK_SIZE)?;
let expected_block0_start = hex::decode("8a5c6f2c6bea2fdb3426f800be139471")
.expect("Failed to decode expected block0 start hex");
assert_eq!(
&block0[..16],
&expected_block0_start[..],
"H' computation for RFC vector block0 mismatch"
);
Ok(())
}
#[test]
fn debug_internal_argon2_h_prime() -> Result<()> {
let data = [1u8; 8];
let out_32 = h_prime_variable_output(&data, 32)?;
assert_eq!(out_32.len(), 32, "H' output length mismatch for 32 bytes");
let out_64 = h_prime_variable_output(&data, 64)?;
assert_eq!(out_64.len(), 64, "H' output length mismatch for 64 bytes");
let out_1024 = h_prime_variable_output(&data, 1024)?;
assert_eq!(
out_1024.len(),
1024,
"H' output length mismatch for 1024 bytes"
);
Ok(())
}
#[test]
fn h_prime_final_32_matches_rfc_a1_tag() -> Result<()> {
let mut final_block_xor = vec![0u8; ARGON2_BLOCK_SIZE];
for (i, byte) in final_block_xor.iter_mut().enumerate() {
*byte = (i % 256) as u8;
}
let tag = h_prime_variable_output(&final_block_xor, 32)?;
let current_output = h_prime_variable_output(&final_block_xor, 32)?;
assert_eq!(
tag, current_output,
"H' final compression should be consistent"
);
Ok(())
}