Peppering Passwords in Rust


If you want people to do a thing, make that thing as easy as possible

Security professionals often admonish us "Don't write your own crypto; use established protocols and well-known libraries." I wonder if that advice would be more often followed if they made said libraries a little easier to use. Case in point: I'm managing passwords in a Rust program. I'm trying to be a good Boy Scout and follow the the OWASP Password Storage Cheat Sheet which recommends peppering in addition to salting your passwords. Fine. Under Password Hashing Algorithms, the first algorithm listed is Argon2id. OK.

There's a crate for that, with a nice example on the home page that demonstrates salting a password before hashing. No mention of peppering, however. In fact, a search for the word "pepper" in the docs returns nothing. Ah-hah: it turns-out that someone else had exactly the same question in the issues (three and a half years ago), leading to a feature request shortly thereafter. A PR was cut a month or two after that, but was never merged because the maintainer noticed that it was already supported (today, it's in a slightly different place).

So, if you're working in Rust, and you'd like to salt & pepper passwords using Argon2id, here's how you do it:

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

fn main() {
    let pepper: Vec<u8> = vec![1, 2, 3, 4];
    let hasher = Argon2::new_with_secret(
        &pepper,
        Algorithm::Argon2id,
        Version::default(),
        Params::default(),
    )
    .expect("Failed to build the hasher");
    let salt = SaltString::generate(&mut OsRng);
    let password_hash = hasher
        .hash_password(b"Hello", &salt)
        .expect("Failed to hash password");
    let password_hash_serialized = password_hash.serialize();
    println!("The password hash is: {}", password_hash_serialized);

    // Cool, cool. Now how do I verify the password?
    let hash = PasswordHash::new(password_hash_serialized.as_ref())
        .expect("Failed to construct PasswordHash");
    let new_hasher = Argon2::new_with_secret(
        &pepper,
        Algorithm::Argon2id,
        Version::default(),
        Params::default(),
    )
    .expect("Failed to build the new hasher");
    let result = new_hasher.verify_password(b"Hello", &hash);
    println!("The verification result is {:#?}", result);
}

02/12/25 17:21