use entity::master_pass; use once_cell::sync::Lazy; use rand::{rngs::OsRng, RngCore}; use scrypt::{scrypt, Params}; use sea_orm::ActiveValue::Set; use subtle::ConstantTimeEq; const HASH_LENGTH: usize = 64; const SALT_LENGTH: usize = 64; static PARAMS: Lazy = Lazy::new(|| Params::new(14, 8, 1, HASH_LENGTH).unwrap()); /// Hashes the password with Scrypt with the given salt #[inline] fn hash_password(password: &[u8], salt: &[u8]) -> [u8; HASH_LENGTH] { let mut password_hash = [0; HASH_LENGTH]; scrypt(password, salt, &PARAMS, &mut password_hash).unwrap(); password_hash } pub trait VerifyMasterPassExt { fn verify(&self, password: &str) -> bool; } impl VerifyMasterPassExt for master_pass::Model { /// Checks that the given password hash matches the one of the model #[inline] fn verify(&self, password: &str) -> bool { let hashed = hash_password(password.as_bytes(), &self.salt); hashed.ct_eq(&self.password_hash).into() } } pub trait MasterPassFromUnencryptedExt { fn from_unencrypted(user_id: u64, password: &str) -> master_pass::ActiveModel; } impl MasterPassFromUnencryptedExt for master_pass::ActiveModel { /// Hashes the password and creates an ActiveModel with all fields set to Set variant #[inline] fn from_unencrypted(user_id: u64, password: &str) -> Self { let mut salt = [0; SALT_LENGTH]; OsRng.fill_bytes(&mut salt); let password_hash = hash_password(password.as_bytes(), &salt); Self { user_id: Set(user_id), salt: Set(salt.into()), password_hash: Set(password_hash.into()), } } }