use anyhow::Result;
use argon2::{Algorithm, Argon2, Params, Version};
use rand_chacha::ChaCha20Rng;
use rand_core::{RngCore, SeedableRng};
use zeroize::{ZeroizeOnDrop, Zeroizing, zeroize_flat_type};
pub(crate) struct Rng(ChaCha20Rng);
impl Rng {
pub fn from_password_salt(password: &str, salt: &str) -> Result<Self> {
let mut key_material = Zeroizing::new([0u8; 32]);
let params =
Params::new(32 * 1024, 3, 1, None).map_err(|e| anyhow::anyhow!("Params::new: {e}"))?;
Argon2::new(Algorithm::Argon2id, Version::V0x13, params)
.hash_password_into(password.as_bytes(), salt.as_bytes(), &mut *key_material)
.map_err(|e| anyhow::anyhow!("argon2 failed: {e}"))?;
Ok(Rng(ChaCha20Rng::from_seed(*key_material)))
}
}
impl RngCore for Rng {
fn next_u32(&mut self) -> u32 {
self.0.next_u32()
}
fn next_u64(&mut self) -> u64 {
self.0.next_u64()
}
fn fill_bytes(&mut self, dst: &mut [u8]) {
self.0.fill_bytes(dst);
}
fn try_fill_bytes(&mut self, dst: &mut [u8]) -> std::result::Result<(), rand_core::Error> {
self.0.try_fill_bytes(dst)
}
}
impl Drop for Rng {
fn drop(&mut self) {
unsafe { zeroize_flat_type(self) }
}
}
impl ZeroizeOnDrop for Rng {}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vectors() -> Result<()> {
let mut rng = Rng::from_password_salt("test", "testsalt")?;
let res = rng.next_u64();
assert_eq!(0xfa6a0c49ba644eb2, res, "{res:x}");
Ok(())
}
}