#![cfg_attr(docsrs, doc(cfg(feature = "safe_api")))]
pub use super::hltypes::Password;
pub use crate::hazardous::kdf::argon2::CostParams;
pub use crate::hazardous::kdf::argon2::PasswordHash;
use super::hltypes::Salt;
use crate::{errors::UnknownCryptoError, hazardous::kdf::argon2};
pub const SALT_LENGTH: usize = 16;
pub const PWHASH_LENGTH: usize = 32;
pub const OWASP_ARGON2ID_1ST: CostParams = CostParams {
iterations: 1,
memory: 47104,
parallelism: 1,
};
pub const OWASP_ARGON2ID_2ND: CostParams = CostParams {
iterations: 2,
memory: 19456,
parallelism: 1,
};
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn hash_password(
password: &Password,
cost: &CostParams,
) -> Result<PasswordHash, UnknownCryptoError> {
let salt = Salt::generate()?;
argon2::Argon2::<argon2::ID, argon2::Sequential>::derive_key_encoded(
password.unprotected_as_ref(),
salt.as_ref(),
cost,
None,
None,
PWHASH_LENGTH,
)
}
#[must_use = "SECURITY WARNING: Ignoring a Result can have real security implications."]
pub fn hash_password_verify(
expected: &PasswordHash,
password: &Password,
) -> Result<(), UnknownCryptoError> {
argon2::Argon2::<argon2::ID, argon2::Sequential>::verify_encoded(
expected,
password.unprotected_as_ref(),
None,
None,
)
}
#[cfg(test)]
mod public {
use super::*;
mod test_password_hash {
use super::*;
#[quickcheck]
#[cfg(feature = "safe_api")]
fn prop_always_produce_valid_encoding(hash: String) -> bool {
if let Ok(res) = PasswordHash::try_from(hash.as_str()) {
assert!(PasswordHash::try_from(res.unprotected_as_ref::<str>()).is_ok());
}
true
}
}
mod test_pwhash_and_verify {
use super::*;
#[test]
fn test_argon2i_verify() {
let password = Password::try_from(&[0u8; 64]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let dk = hash_password(&password, &cost).unwrap();
assert!(hash_password_verify(&dk, &password).is_ok());
assert!(!dk.is_empty());
}
#[test]
fn test_argon2i_verify_err_modified_passwordhash() {
let password = Password::try_from(&[0u8; 64]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let dk = hash_password(&password, &cost).unwrap();
let mut pwd_mod = dk.unprotected_as_ref::<[u8]>().to_vec();
pwd_mod[0..32].copy_from_slice(&[0u8; 32]);
assert!(PasswordHash::try_from(&pwd_mod).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_memory() {
let password = Password::try_from(&[0u8; 64][..]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let dk = hash_password(&password, &cost).unwrap();
let encoded = dk.unprotected_as_ref::<str>();
let mut modified = encoded.to_string();
let memory_offset = modified.find("$m=4096").unwrap();
modified.replace_range(memory_offset..memory_offset + 7, "$m=2048");
let modified = PasswordHash::try_from(modified.as_str()).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_iterations() {
let password = Password::try_from(&[0u8; 64][..]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let dk = hash_password(&password, &cost).unwrap();
let encoded = dk.unprotected_as_ref::<str>();
let mut modified = encoded.to_string();
let iterations_offset = modified.find(",t=1").unwrap();
modified.replace_range(iterations_offset..iterations_offset + 4, ",t=2");
let modified = PasswordHash::try_from(modified.as_str()).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_memory_and_iterations() {
let password = Password::try_from(&[0u8; 64][..]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let dk = hash_password(&password, &cost).unwrap();
let encoded = dk.unprotected_as_ref::<str>();
let mut modified = encoded.to_string();
let memory_offset = modified.find("$m=4096").unwrap();
let iterations_offset = modified.find(",t=1").unwrap();
modified.replace_range(memory_offset..memory_offset + 7, "$m=2048");
modified.replace_range(iterations_offset..iterations_offset + 4, ",t=2");
let modified = PasswordHash::try_from(modified.as_str()).unwrap();
assert!(hash_password_verify(&modified, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_salt() {
let password = Password::try_from(&[0u8; 64][..]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let mut dk = hash_password(&password, &cost).unwrap();
dk.data.salt[0..16].copy_from_slice(&[0u8; 16]);
dk.data.encode_to_phc().unwrap();
assert!(hash_password_verify(&dk, &password).is_err());
}
#[test]
fn test_argon2i_verify_err_modified_salt_and_password() {
let password = Password::try_from(&[0u8; 64][..]).unwrap();
let cost = CostParams::new(1, 4096, 1).unwrap();
let mut dk = hash_password(&password, &cost).unwrap();
dk.data.salt[0..16].copy_from_slice(&[0u8; 16]);
dk.data.hash[0..16].copy_from_slice(&[0u8; 16]);
dk.data.encode_to_phc().unwrap();
assert!(hash_password_verify(&dk, &password).is_err());
}
}
}