cyrtophora

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit 269634a225784fa8a6f563b6b93e260b9ffe3638
parent a050da2b5e8711311b72b625ef5b0e78b497db69
Author: Jackson G. Kaindume <seestem@merely.tech>
Date:   Thu, 18 Aug 2022 13:06:43 +0200

[Password Hashing] use scrypt instead of PBKDF2

Diffstat:
MCargo.toml | 7+++----
Msrc/lib.rs | 94+++++++++++++++++++++++++------------------------------------------------------
2 files changed, 32 insertions(+), 69 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml @@ -3,10 +3,9 @@ name = "bizkit-crypto" version = "0.1.0" edition = "2021" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] anyhow = "1.0.57" ring = "0.16.20" chacha20poly1305 = "0.9.0" -base64 = "0.13.0" -\ No newline at end of file +base64 = "0.13.0" +scrypt = "0.10.0" +\ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs @@ -5,21 +5,16 @@ use chacha20poly1305::{ }; use ring::{ digest::{self, digest, Digest}, - pbkdf2, rand::SystemRandom, signature::{Ed25519KeyPair, KeyPair}, }; -use std::num::NonZeroU32; +use scrypt::{ + password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString}, + Scrypt, +}; -static PBKDF2_ALG: pbkdf2::Algorithm = pbkdf2::PBKDF2_HMAC_SHA256; const CREDENTIAL_LEN: usize = digest::SHA256_OUTPUT_LEN; -const PBKDF2_ITERATIONS: u32 = 100_000; -/// Database seed used to generate password hashes, -/// so that an attacker cannot crack the same user's password across -/// databases in the unfortunate but common case that the user has -/// used the same password for multiple systems. -const DATABASE_SEED: &str = - "The cipher has an interesting history: although its true origins are unknown"; + /// Nonce used when encrypting the Crypto's keypair (Crypto::encrypt_keypair_document()) const AED_NONCE: &str = "people the information "; pub type Credential = [u8; CREDENTIAL_LEN]; @@ -52,57 +47,28 @@ impl Crypto { Ok(cred) } - /// Generate pbkdf2 based hashed password - pub fn hash_password(username: &str, password: &str) -> Result<String> { - let pbkdf2_iterations: NonZeroU32 = NonZeroU32::new(PBKDF2_ITERATIONS).unwrap(); - let salt = Crypto::gen_password_salt(username); - let mut to_store: Credential = [0u8; CREDENTIAL_LEN]; - pbkdf2::derive( - PBKDF2_ALG, - pbkdf2_iterations, - &salt, - password.as_bytes(), - &mut to_store, - ); - - let hash = base64::encode(to_store); - Ok(hash) + /// Generate scrypt based hashed password + pub fn hash_password(password: &str) -> Result<String> { + let salt = SaltString::generate(&mut OsRng); + Ok(Scrypt + .hash_password(password.as_bytes(), &salt)? + .to_string()) } - /// Verify pbkdf2 based password hash - fn verify_password_hash(username: &str, attempted_password: &str, hash: &str) -> Result<bool> { - let pbkdf2_iterations: NonZeroU32 = NonZeroU32::new(PBKDF2_ITERATIONS).unwrap(); - let salt = Crypto::gen_password_salt(username); - let hash_bytes = base64::decode(hash)?; - let res = pbkdf2::verify( - PBKDF2_ALG, - pbkdf2_iterations, - &salt, - attempted_password.as_bytes(), - &hash_bytes, - ); - - match res { - Ok(()) => Ok(true), - Err(_e) => Ok(false), + /// Verify scrypt based password hash + fn verify_password_hash(attempted_password: &str, hash: &str) -> Result<bool> { + let parsed_hash = PasswordHash::new(hash)?; + + if Scrypt + .verify_password(attempted_password.as_bytes(), &parsed_hash) + .is_ok() + { + Ok(true) + } else { + Ok(false) } } - /// Generate password salt - /// The salt should have a user-specific component so that an attacker - /// cannot crack one password for multiple users in the database. It - /// should have a database-unique component so that an attacker cannot - /// crack the same user's password across databases in the unfortunate - /// but common case that the user has used the same password for - /// multiple systems. - fn gen_password_salt(username: &str) -> Vec<u8> { - let db_salt_component = DATABASE_SEED.as_bytes(); - let mut salt = Vec::with_capacity(db_salt_component.len() + username.as_bytes().len()); - salt.extend(db_salt_component.as_ref()); - salt.extend(username.as_bytes()); - salt - } - /// Encrypt the keypair (ed25519) document, using XChaCha20Poly1305 /// so that it can be safely persisted into the database. The /// Crypto's login password is used a the secret key. @@ -147,16 +113,14 @@ mod tests { #[test] fn hash_password() { let password1 = "a bad password"; - let username1 = "goldephoenix"; - let hash1 = Crypto::hash_password(username1, password1).unwrap(); + let hash1 = Crypto::hash_password(password1).unwrap(); let password2 = "a bad password"; - let username2 = "alexis"; - let hash2 = Crypto::hash_password(username2, password2).unwrap(); + let hash2 = Crypto::hash_password(password2).unwrap(); - // The generated hashes are all 32 bytes long - assert_eq!(hash1.len(), 44); - assert_eq!(hash2.len(), 44); + // The generated hashes are all 88 bytes long + assert_eq!(hash1.len(), 88); + assert_eq!(hash2.len(), 88); // Even if the user passwords are the same they do not generate // the same hashes because the username is also used as a @@ -164,8 +128,8 @@ mod tests { assert_ne!(hash1, hash2); // Verify password - assert!(Crypto::verify_password_hash(username1, "a bad password", &hash1).unwrap()); - assert!(!Crypto::verify_password_hash(username1, "wrong password", &hash1).unwrap()); + assert!(Crypto::verify_password_hash("a bad password", &hash1).unwrap()); + assert!(!Crypto::verify_password_hash("wrong password", &hash1).unwrap()); } #[test]