Password Hashing
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. The non-secret parameters are called "salt" in this context.
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.
Hash a password, then verify a password against the hash
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
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'⮳ 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.
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(()) }
For more algorithms, see Rust Crypto Password Hashes.