Password Hashing

Password hashing is a crucial security practice that protects user passwords by one-way transforming them into a string of characters, called a "hash", which reverse-engineering into the password is computationally infeasible. This hash is then stored instead of the actual password. Password hashing protects against data breaches: If a database is compromised, attackers won't be able to see the actual passwords, only the hashed versions.

A key derivation function⮳ (KDF) is a cryptographic algorithm that derives one or more secret keys from a secret value, such as a master key, a password, or a passphrase, using a pseudorandom function (typically a cryptographic hash function or block cipher).

The original use for a KDF is key derivation, the generation of multiple keys from secret passwords or passphrases.

Despite their original use for key derivation, KDFs are possibly better known for their use in password hashing (password verification by hash comparison), as used by the passwd file or shadow password file. Password hash functions should be relatively expensive to calculate in case of brute-force attacks, and the key stretching of KDFs happen to provide this characteristic.

In that role, key derivation functions take a password, a salt, (and sometimes a cost factor) as inputs, then generate a password hash - deliberately slowly. Their purpose is to make each password guessing trial by an attacker who has obtained a password hash file expensive and therefore the cost of a guessing attack high or prohibitive. In cryptography, "salt" refers to non-secret, random data added to input data before hashing it.

Popular password hashing algorithms include bcrypt, Argon2 and scrypt.

Hash a Password, then Verify a Password Against the Hash

argon2 argon2-crates.io argon2-github argon2-lib.rs cat-authentication cat-cryptography cat-no-std

argon2 is a pure-Rust implementation of the 'Argon2'⮳ key derivation function, which is commonly used for secure password hashing.

use argon2::Argon2;
use argon2::password_hash::Error;
use argon2::password_hash::PasswordHash;
use argon2::password_hash::PasswordHasher;
use argon2::password_hash::PasswordVerifier;
use argon2::password_hash::SaltString;
use argon2::password_hash::rand_core::OsRng;

fn main() -> Result<(), Error> {
    let password_to_hash = b"super_secret_password"; // Bad password; don't actually use!

    let phc_string = password_hashing(password_to_hash)?;
    println!("Hashed password: {}", phc_string);

    // In a real application, save the PHC string to a secure database or file.
    // The PHC string contains the (one-way) hashed password and the salt, not
    // the password. Should an attacker gain access to the database or file,
    // they won't be able to retrieve the password.

    // When a user logs in, the password they provide is checked against the
    // saved PHC string: The correct password passes the verification
    // function
    let password_to_check = password_to_hash;
    let is_valid = password_verification(password_to_check, &phc_string)?;
    println!("The password is valid: {is_valid}");

    // An incorrect password fails the check
    assert!(!password_verification(b"random_guess", &phc_string)?);

    // Key derivation: derive a child key from a password and salt
    key_derivation()?;

    Ok(())
}

// Hash a password to a “PHC string” suitable for the purposes of password-based
// authentication.
fn password_hashing(password_to_save: &[u8]) -> Result<String, Error> {
    // Generate a random salt
    let salt = SaltString::generate(&mut OsRng);

    // Configure Argon2 with default params (Argon2id v19)
    let argon2 = Argon2::default();

    // Hash the password to a PHC string ($argon2id$v=19$...)
    let phc_string = argon2.hash_password(password_to_save, &salt)?.to_string();

    Ok(phc_string)
}

// Verify a given password against the provided PHC string.
// Returns true if the password is correct.
fn password_verification(
    password_to_check: &[u8],
    phc_string: &str,
) -> Result<bool, Error> {
    // Parsed representation of a PHC string
    let parsed_hash = PasswordHash::new(phc_string)?;

    // Verify the password
    Ok(Argon2::default()
        .verify_password(password_to_check, &parsed_hash)
        .is_ok())
}

// Transform a password into cryptographic keys that can be used for e.g.
// encryption.
fn key_derivation() -> Result<(), Error> {
    let password = b"passwd"; // Bad password; don't use!
    let salt = b"example salt, etc, etc"; // Salt should be unique per password

    let mut output_key_material = [0u8; 128]; // Can be any desired size

    // Hash a password and associated parameters into the provided output
    // buffer.
    Argon2::default().hash_password_into(
        password,
        salt,
        &mut output_key_material,
    )?;

    // `output_key_material` can now be used as a cryptographic key

    Ok(())
}

scrypt

scrypt scrypt-crates.io scrypt-github scrypt-lib.rs cat-authentication cat-cryptography cat-no-std

The scrypt key derivation function is designed to be far more secure against hardware brute-force attacks than alternative functions such as PBKDF2 or 'bcrypt'.

use std::error::Error;

use scrypt::Scrypt;
use scrypt::password_hash::PasswordHash;
use scrypt::password_hash::PasswordHasher;
use scrypt::password_hash::PasswordVerifier;
use scrypt::password_hash::SaltString;
use scrypt::password_hash::rand_core::OsRng;

fn main() -> Result<(), Box<dyn Error>> {
    // 1) When setting the password, hash it and store the hash

    // Password to be hashed
    let password = b"super_secret_password";

    // Generate a random salt
    let salt = SaltString::generate(&mut OsRng);

    // Hash the password to a PHC string ($scrypt$...)
    let password_hash: PasswordHash<'_> =
        Scrypt.hash_password(password, &salt)?;

    // Print the hashed password
    let password_hash = password_hash.to_string();
    println!("Hashed password: {}", password_hash);

    // 2) Later, in order to verify a password, the hash is retrieved from the
    // database, and the password is checked against it.

    let parsed_hash = PasswordHash::new(&password_hash)?;
    let is_valid = Scrypt.verify_password(password, &parsed_hash).is_ok();

    // Print the verification result
    println!("Password is valid: {}", is_valid);

    Ok(())
}

bcrypt

bcrypt bcrypt-crates.io bcrypt-github bcrypt-lib.rs

'bcrypt'⮳ is a password-hashing function. Besides incorporating a salt to protect against rainbow table attacks, 'bcrypt'⮳ is an adaptive function: over time, the iteration count can be increased to make it slower, so it remains resistant to brute-force search attacks even with increasing computation power. 'bcrypt'⮳ is not a key derivation function (KDF). For example, bcrypt cannot be used to derive a 512-bit key from a password.

bcrypt

use std::error::Error;

use bcrypt::DEFAULT_COST;
use bcrypt::hash;
use bcrypt::verify;

fn main() -> Result<(), Box<dyn Error>> {
    // 1) When setting the password, hash it and store the hash:

    // Password to be hashed
    let password = "super_secret_password";

    // Generate a password hash using the default cost.
    // The salt is generated randomly using the OS randomness
    let hashed_password: String = hash(password, DEFAULT_COST)?;

    println!("Hashed password: {}", hashed_password);

    // 2) Later, in order to verify a password, the hash is retrieved from the
    // database, and the password is checked against it:

    let is_valid = verify(password, &hashed_password)?;
    println!("Password is valid: {}", is_valid);

    Ok(())
}

Salt and Hash a Password with PBKDF2

ring ring-crates.io ring-github ring-lib.rs data-encoding data-encoding-crates.io data-encoding-github data-encoding-lib.rs cat-cryptography cat-encoding cat-no-std

Uses ring::pbkdf2⮳ to hash a salted password using the PBKDF2 key derivation function ring::pbkdf2::derive⮳. Verifies the hash is correct with ring::pbkdf2::verify⮳. The salt is generated using ring::rand::SecureRandom::fill⮳ which fills the salt byte array with securely generated random numbers.

use std::num::NonZeroU32;

use data_encoding::HEXUPPER;
use ring::digest;
use ring::error::Unspecified;
use ring::pbkdf2;
use ring::rand;
use ring::rand::SecureRandom;

fn main() -> Result<(), Unspecified> {
    const CREDENTIAL_LEN: usize = digest::SHA512_OUTPUT_LEN;
    let n_iter = NonZeroU32::new(100_000).unwrap();
    let rng = rand::SystemRandom::new();

    let mut salt = [0u8; CREDENTIAL_LEN];
    rng.fill(&mut salt)?;

    let password = "Guess Me If You Can!";
    let mut pbkdf2_hash = [0u8; CREDENTIAL_LEN];
    pbkdf2::derive(
        pbkdf2::PBKDF2_HMAC_SHA512,
        n_iter,
        &salt,
        password.as_bytes(),
        &mut pbkdf2_hash,
    );
    println!("Salt: {}", HEXUPPER.encode(&salt));
    println!("PBKDF2 hash: {}", HEXUPPER.encode(&pbkdf2_hash));

    let should_succeed = pbkdf2::verify(
        pbkdf2::PBKDF2_HMAC_SHA512,
        n_iter,
        &salt,
        password.as_bytes(),
        &pbkdf2_hash,
    );
    assert!(should_succeed.is_ok());

    let wrong_password = "Definitely not the correct password";
    let should_fail = pbkdf2::verify(
        pbkdf2::PBKDF2_HMAC_SHA512,
        n_iter,
        &salt,
        wrong_password.as_bytes(),
        &pbkdf2_hash,
    );
    assert!(should_fail.is_err());

    Ok(())
}

For more algorithms, see Rust Crypto Password Hashes.