rok-core 0.6.1

Core primitives for the rok ecosystem — errors, crypto, i18n, config, DI, and more
Documentation
use password_hash::{rand_core::OsRng, PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
use scrypt::{Params, Scrypt};

use crate::crypto::hash::{config::ScryptConfig, driver::HashDriver, HashError};

pub(crate) struct ScryptDriver {
    config: ScryptConfig,
}

impl ScryptDriver {
    pub(crate) fn new(config: ScryptConfig) -> Self {
        Self { config }
    }

    fn params(&self) -> Result<Params, HashError> {
        Params::new(
            self.config.log_n,
            self.config.r,
            self.config.p,
            Params::RECOMMENDED_LEN,
        )
        .map_err(|e| HashError::InvalidParams(e.to_string()))
    }
}

impl HashDriver for ScryptDriver {
    fn hash(&self, password: &str) -> Result<String, HashError> {
        let params = self.params()?;
        let salt = SaltString::generate(&mut OsRng);
        let hash = Scrypt
            .hash_password_customized(password.as_bytes(), None, None, params, &salt)
            .map_err(|e| HashError::HashFailed(e.to_string()))?;
        Ok(hash.to_string())
    }

    fn verify(&self, password: &str, hash: &str) -> Result<bool, HashError> {
        let parsed = PasswordHash::new(hash).map_err(|_| HashError::InvalidFormat)?;
        Ok(Scrypt.verify_password(password.as_bytes(), &parsed).is_ok())
    }

    fn needs_rehash(&self, hash: &str) -> bool {
        let Ok(parsed) = PasswordHash::new(hash) else {
            return true;
        };
        if parsed.algorithm.as_str() != "scrypt" {
            return true;
        }
        let mut ln = 0u32;
        let mut r = 0u32;
        let mut p = 0u32;
        for (key, val) in parsed.params.iter() {
            match key.as_str() {
                "ln" => {
                    if let Ok(n) = val.decimal() {
                        ln = n;
                    }
                }
                "r" => {
                    if let Ok(n) = val.decimal() {
                        r = n;
                    }
                }
                "p" => {
                    if let Ok(n) = val.decimal() {
                        p = n;
                    }
                }
                _ => {}
            }
        }
        (ln as u8) < self.config.log_n || r < self.config.r || p < self.config.p
    }
}