libpna 0.32.1

PNA(Portable-Network-Archive) decoding and encoding library
Documentation
//! Password hashing helpers.
use argon2::{Argon2, ParamsBuilder, Version};
use password_hash::{PasswordHash, PasswordHasher, SaltString};
use std::io;

pub(crate) fn argon2_with_salt<'a>(
    password: &'a [u8],
    algorithm: argon2::Algorithm,
    time_cost: Option<u32>,
    memory_cost: Option<u32>,
    parallelism_cost: Option<u32>,
    hash_length: usize,
    salt: &'a SaltString,
) -> io::Result<PasswordHash<'a>> {
    let mut builder = ParamsBuilder::default();
    if let Some(time_cost) = time_cost {
        builder.t_cost(time_cost);
    };
    if let Some(memory_cost) = memory_cost {
        builder.m_cost(memory_cost);
    };
    if let Some(parallelism_cost) = parallelism_cost {
        builder.p_cost(parallelism_cost);
    };
    let argon2 = builder
        .output_len(hash_length)
        .context(algorithm, Version::default())
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidInput, e))?;
    argon2
        .hash_password(password, salt)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

pub(crate) fn pbkdf2_with_salt<'a>(
    password: &'a [u8],
    algorithm: pbkdf2::Algorithm,
    params: pbkdf2::Params,
    salt: &'a SaltString,
) -> io::Result<PasswordHash<'a>> {
    pbkdf2::Pbkdf2
        .hash_password_customized(password, Some(algorithm.ident()), None, params, salt)
        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}

pub(crate) fn derive_password_hash<'a>(
    phsf: &'a str,
    password: &'a [u8],
) -> io::Result<PasswordHash<'a>> {
    let password_hash =
        PasswordHash::new(phsf).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
    let salt = password_hash.salt.ok_or_else(|| {
        io::Error::new(io::ErrorKind::InvalidData, "missing salt in password hash")
    })?;
    match password_hash.algorithm {
        argon2::ARGON2D_IDENT | argon2::ARGON2I_IDENT | argon2::ARGON2ID_IDENT => {
            let argon2 = Argon2::default();
            argon2
                .hash_password_customized(
                    password,
                    Some(password_hash.algorithm),
                    password_hash.version,
                    argon2::Params::try_from(&password_hash)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
                    salt,
                )
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
        }
        pbkdf2::Algorithm::PBKDF2_SHA256_IDENT | pbkdf2::Algorithm::PBKDF2_SHA512_IDENT => {
            pbkdf2::Pbkdf2
                .hash_password_customized(
                    password,
                    Some(password_hash.algorithm),
                    password_hash.version,
                    pbkdf2::Params::try_from(&password_hash)
                        .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?,
                    salt,
                )
                .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
        }
        a => Err(io::Error::new(
            io::ErrorKind::Unsupported,
            format!("Unsupported algorithm {a:?}"),
        )),
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::random;
    #[cfg(all(target_family = "wasm", target_os = "unknown"))]
    use wasm_bindgen_test::wasm_bindgen_test as test;

    #[test]
    fn derive_argon2() {
        let salt = random::salt_string();
        let mut ph = argon2_with_salt(
            b"pass",
            argon2::Algorithm::Argon2id,
            None,
            None,
            None,
            32,
            &salt,
        )
        .unwrap();
        ph.hash.take();
        assert_eq!(ph.hash, None);
        let ps = ph.to_string();
        let ph = derive_password_hash(&ps, b"pass").unwrap();
        assert!(ph.hash.is_some());
    }

    #[test]
    fn derive_pbkdf2() {
        let salt = random::salt_string();
        let mut ph = pbkdf2_with_salt(
            b"pass",
            pbkdf2::Algorithm::Pbkdf2Sha256,
            pbkdf2::Params::default(),
            &salt,
        )
        .unwrap();
        ph.hash.take();
        assert_eq!(ph.hash, None);
        let ps = ph.to_string();
        let ph = derive_password_hash(&ps, b"pass").unwrap();
        assert!(ph.hash.is_some());
    }
}