Argon2 Password Hash Verify

Hash passwords with argon2id at tuned cost parameters, verifying in constant time with encoded salt and version metadata.

RustSecurity Boundaries

Code

snippet
use argon2::password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use argon2::{Algorithm, Argon2, Params, Version};
use rand_core::OsRng;

fn tuned_argon2() -> Argon2<'static> {
    let params = Params::new(
        19_456, // 19 MiB 内存成本
        2,      // 时间成本(迭代次数)
        1,      // 并行度
        None,
    )
    .expect("valid parameters");

    Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
}

fn hash_password(plain: &str) -> Result<String, String> {
    let salt = SaltString::generate(&mut OsRng);
    tuned_argon2()
        .hash_password(plain.as_bytes(), &salt)
        .map(|hash| hash.to_string())
        .map_err(|err| format!("hash failed: {err}"))
}

fn verify_password(plain: &str, stored: &str) -> bool {
    let parsed = match PasswordHash::new(stored) {
        Ok(hash) => hash,
        Err(_) => return false, // 存储格式损坏视为验证失败
    };
    tuned_argon2()
        .verify_password(plain.as_bytes(), &parsed)
        .is_ok()
}

fn main() {
    let stored = hash_password("correct horse battery staple").unwrap();
    println!("format prefix: {}", &stored[..stored.find('$').unwrap()]);

    println!("wrong rejected: {}", !verify_password("wrong", &stored));
    println!("right accepted: {}", verify_password("correct horse battery staple", &stored));

    // 不同盐产出不同密文:同密码两次哈希不相等
    let again = hash_password("correct horse battery staple").unwrap();
    println!("salts differ: {}", stored != again);
}