pub mod error;
use error::*;
use argon2_sys::{ARGON2_DEFAULT_FLAGS, argon2_context, argon2_ctx};
#[cfg(feature = "zeroize")]
use zeroize::Zeroize;
pub const RECOMMENDED_HASH_LENGTH: u64 = 64;
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Default, Ord)]
#[repr(u32)]
pub enum Algorithm {
Argon2d = 0,
Argon2i = 1,
#[default]
Argon2id = 2,
}
#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Version {
V0x10 = 0x10,
#[default]
V0x13 = 0x13,
}
#[derive(Default, Clone, Debug, PartialEq, Eq)]
pub struct Argon2 {
pub m_cost: u32,
pub t_cost: u32,
pub p_cost: u32,
pub hash_length: u64,
pub algorithm: Algorithm,
pub version: Version,
}
impl Argon2 {
pub fn new(m_cost: u32, t_cost: u32, p_cost: u32) -> Self {
Self {
m_cost,
t_cost,
p_cost,
hash_length: RECOMMENDED_HASH_LENGTH,
..Default::default()
}
}
pub fn with_algorithm(mut self, algorithm: Algorithm) -> Self {
self.algorithm = algorithm;
self
}
pub fn with_version(mut self, version: Version) -> Self {
self.version = version;
self
}
pub fn with_hash_length(mut self, hash_length: u64) -> Self {
self.hash_length = hash_length;
self
}
pub fn hash_password(&self, password: &str, mut salt: Vec<u8>) -> Result<Vec<u8>, Argon2Error> {
let mut hash_buffer = vec![0u8; self.hash_length as usize];
let mut context = argon2_context {
out: hash_buffer.as_mut_ptr(),
outlen: self.hash_length as u32,
pwd: password.as_bytes().as_ptr() as *mut u8,
pwdlen: password.len() as u32,
salt: salt.as_mut_ptr(),
saltlen: salt.len() as u32,
secret: std::ptr::null_mut(),
secretlen: 0,
ad: std::ptr::null_mut(),
adlen: 0,
t_cost: self.t_cost,
m_cost: self.m_cost,
lanes: self.p_cost,
threads: self.p_cost,
version: self.version as u32,
allocate_cbk: None,
free_cbk: None,
flags: ARGON2_DEFAULT_FLAGS,
};
let code = unsafe { argon2_ctx(&mut context, self.algorithm as u32) };
#[cfg(feature = "zeroize")]
salt.zeroize();
if code != 0 {
return Err(map_argon2_error(code));
}
Ok(hash_buffer)
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(28);
let version = self.version as u32;
buf.extend_from_slice(&self.m_cost.to_le_bytes());
buf.extend_from_slice(&self.t_cost.to_le_bytes());
buf.extend_from_slice(&self.p_cost.to_le_bytes());
buf.extend_from_slice(&self.hash_length.to_le_bytes());
buf.extend_from_slice(&(self.algorithm as u32).to_le_bytes());
buf.extend_from_slice(&version.to_le_bytes());
buf
}
pub fn decode(data: &[u8]) -> Result<Self, Argon2Error> {
if data.len() < 28 {
return Err(Argon2Error::DecodingFail);
}
let m_cost = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
let t_cost = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
let p_cost = u32::from_le_bytes([data[8], data[9], data[10], data[11]]);
let hash_length = u64::from_le_bytes([
data[12], data[13], data[14], data[15], data[16], data[17], data[18], data[19],
]);
let alg_u32 = u32::from_le_bytes([data[20], data[21], data[22], data[23]]);
let version_u32 = u32::from_le_bytes([data[24], data[25], data[26], data[27]]);
let algorithm = match alg_u32 {
0 => Algorithm::Argon2d,
1 => Algorithm::Argon2i,
2 => Algorithm::Argon2id,
_ => return Err(Argon2Error::DecodingFail),
};
let version = match version_u32 {
0x10 => Version::V0x10,
0x13 => Version::V0x13,
_ => return Err(Argon2Error::DecodingFail),
};
Ok(Self {
m_cost,
t_cost,
p_cost,
hash_length,
algorithm,
version,
})
}
}
impl Argon2 {
pub fn very_fast() -> Self {
Self {
m_cost: 128_000,
t_cost: 8,
p_cost: 1,
hash_length: RECOMMENDED_HASH_LENGTH,
..Default::default()
}
}
pub fn fast() -> Self {
Self {
m_cost: 256_000,
t_cost: 16,
p_cost: 1,
hash_length: RECOMMENDED_HASH_LENGTH,
..Default::default()
}
}
pub fn balanced() -> Self {
Self {
m_cost: 1024_000,
t_cost: 8,
p_cost: 1,
hash_length: RECOMMENDED_HASH_LENGTH,
..Default::default()
}
}
pub fn slow() -> Self {
Self {
m_cost: 2048_000,
t_cost: 8,
p_cost: 1,
hash_length: RECOMMENDED_HASH_LENGTH,
..Default::default()
}
}
pub fn very_slow() -> Self {
Self {
m_cost: 3072_000,
t_cost: 8,
p_cost: 1,
hash_length: RECOMMENDED_HASH_LENGTH,
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_argon2() -> Result<(), Argon2Error> {
let argon2 = Argon2::very_fast();
let salt = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16];
let hash = argon2.hash_password("password", salt)?;
assert_eq!(hash.len(), 64);
Ok(())
}
#[test]
fn test_encode_decode() -> Result<(), Argon2Error> {
let argon2 = Argon2::balanced();
let encoded = argon2.encode();
assert_eq!(encoded.len(), 28);
let decoded = Argon2::decode(&encoded)?;
assert_eq!(argon2, decoded);
Ok(())
}
}