1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
//! Argon2 key derivation from a password

pub use argon2::{Algorithm, Version};

use super::KeyDerivation;
use crate::{
    error::Error,
    generic_array::typenum::{Unsigned, U16},
};

/// The size of the password salt
pub type SaltSize = U16;

/// The length of the password salt
pub const SALT_LENGTH: usize = SaltSize::USIZE;

/// Standard parameters for 'interactive' level
pub const PARAMS_INTERACTIVE: Params = Params {
    alg: Algorithm::Argon2i,
    version: Version::V0x13,
    mem_cost: 32768,
    time_cost: 4,
};
/// Standard parameters for 'moderate' level
pub const PARAMS_MODERATE: Params = Params {
    alg: Algorithm::Argon2i,
    version: Version::V0x13,
    mem_cost: 131072,
    time_cost: 6,
};

/// Parameters to the argon2 key derivation
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Params {
    alg: Algorithm,
    version: Version,
    mem_cost: u32,
    time_cost: u32,
}

/// Struct wrapping the KDF functionality
#[derive(Debug)]
pub struct Argon2<'a> {
    password: &'a [u8],
    salt: &'a [u8],
    params: Params,
}

impl<'a> Argon2<'a> {
    /// Create a new Argon2 key derivation instance
    pub fn new(password: &'a [u8], salt: &'a [u8], params: Params) -> Result<Self, Error> {
        if salt.len() < SALT_LENGTH {
            return Err(err_msg!(Usage, "Invalid salt for argon2i hash"));
        }
        Ok(Self {
            password,
            salt,
            params,
        })
    }
}

impl KeyDerivation for Argon2<'_> {
    fn derive_key_bytes(&mut self, key_output: &mut [u8]) -> Result<(), Error> {
        if key_output.len() > u32::MAX as usize {
            return Err(err_msg!(
                Usage,
                "Output length exceeds max for argon2i hash"
            ));
        }
        let context = argon2::Argon2::new(
            None,
            self.params.time_cost,
            self.params.mem_cost,
            1,
            self.params.version,
        )
        .map_err(|_| err_msg!(Unexpected, "Error creating hasher"))?;
        context
            .hash_password_into(self.params.alg, self.password, self.salt, &[], key_output)
            .map_err(|_| err_msg!(Unexpected, "Error deriving key"))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn expected() {
        let pass = b"my password";
        let salt = b"long enough salt";
        let mut output = [0u8; 32];
        Argon2::new(pass, salt, PARAMS_INTERACTIVE)
            .unwrap()
            .derive_key_bytes(&mut output)
            .unwrap();
        assert_eq!(
            output,
            hex!("9ef87bcf828c46c0136a0d1d9e391d713f75b327c6dc190455bd36c1bae33259")
        );
    }
}