← 中文资源

简体中文版本

Argon2 密码哈希与验证

用 argon2id 按调优成本参数哈希密码:随机盐随密文编码存储、验证走恒时比较、同密码两次哈希产出不同密文。

阅读英文原文

示例说明

argon2id 是内存困难的慢哈希,GPU 与 ASIC 都难以加速爆破。Params 的三要素——内存 19 MiB、迭代两次、单线程——是交互式登录的常见平衡点。SaltString 由操作系统随机源生成并直接编码进密文字符串:存储格式自带算法版本与全部参数,未来升级成本时旧哈希仍可按自身参数验证。

技术要点

  • 成本参数随密文编码持久化
  • 验证失败与格式损坏统一返回假
  • 随机盐保证同密码不同密文

代码解读

tuned_argon2 构造参数化实例;hash_password 每次生成新盐后哈希,输出是自描述的 PHC 格式字符串;verify_password 先解析存储哈希再恒时验证——解析失败直接拒绝不 panic。main 验证三件事:错误口令拒绝、正确口令通过、两次哈希因盐不同而不相等。

使用建议

参数提升前压测登录吞吐。存储侧可以并行保存哈希与参数版本,登录时透明升级重哈希。口令策略配合最小长度而非复杂度规则。

代码示例

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);
}