Password Hashing
Recipe | Crates | Categories |
---|---|---|
Hash a Password, then Verify a Password Against the Hash | ||
scrypt | ||
bcrypt | ||
Salt and Hash a Password with PBKDF2 |
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
⮳ 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(()) }
Salt and Hash a Password with PBKDF2
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.