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:
M | Cargo.toml | | | 7 | +++---- |
M | src/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]