use std::{
convert::TryFrom,
io::{Cursor, Read},
};
use argon2::{Config, ThreadMode, Variant, Version};
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
use rand::rngs::OsRng;
use rand::Rng;
#[cfg(feature = "wbindgen")]
use wasm_bindgen::prelude::*;
use super::Error;
use super::Result;
#[cfg_attr(feature = "wbindgen", wasm_bindgen(inspectable))]
#[derive(Clone)]
pub struct Argon2Parameters {
pub length: u32,
pub lanes: u32,
pub memory: u32,
pub iterations: u32,
variant: Variant,
version: Version,
dc_version: u32,
associated_data: Vec<u8>,
secret_key: Vec<u8>,
salt: Vec<u8>,
}
impl Default for Argon2Parameters {
fn default() -> Self {
let mut salt = vec![0u8; 16];
OsRng.fill(salt.as_mut_slice());
Argon2Parameters {
associated_data: Vec::new(),
secret_key: Vec::new(),
length: 32,
lanes: 1,
memory: 4096,
iterations: 2,
variant: Variant::Argon2id,
version: Version::Version13,
dc_version: 1,
salt,
}
}
}
impl From<Argon2Parameters> for Vec<u8> {
fn from(mut params: Argon2Parameters) -> Self {
let mut data = Vec::with_capacity(
5 * 4 + 2 + 2 * 4 + params.associated_data.len() + params.salt.len(),
);
data.write_u32::<LittleEndian>(params.dc_version).unwrap();
data.write_u32::<LittleEndian>(params.length).unwrap();
data.write_u32::<LittleEndian>(params.lanes).unwrap();
data.write_u32::<LittleEndian>(params.memory).unwrap();
data.write_u32::<LittleEndian>(params.iterations).unwrap();
data.write_u8(params.variant.as_u32() as u8).unwrap();
data.write_u8(params.version.as_u32() as u8).unwrap();
data.write_u32::<LittleEndian>(params.associated_data.len() as u32)
.unwrap();
data.append(&mut params.associated_data);
data.write_u32::<LittleEndian>(params.salt.len() as u32)
.unwrap();
data.append(&mut params.salt);
data
}
}
impl TryFrom<&[u8]> for Argon2Parameters {
type Error = Error;
fn try_from(data: &[u8]) -> Result<Self> {
let mut data_cursor = Cursor::new(data);
let dc_version = data_cursor.read_u32::<LittleEndian>()?;
let length = data_cursor.read_u32::<LittleEndian>()?;
let lanes = data_cursor.read_u32::<LittleEndian>()?;
let memory = data_cursor.read_u32::<LittleEndian>()?;
let iterations = data_cursor.read_u32::<LittleEndian>()?;
let (variant, version) = match (
Variant::from_u32(data_cursor.read_u8()? as u32),
Version::from_u32(data_cursor.read_u8()? as u32),
) {
(Ok(variant), Ok(version)) => (variant, version),
_ => return Err(Error::InvalidData),
};
let associated_data_length = data_cursor.read_u32::<LittleEndian>()? as usize;
let remaining = data.len() - (data_cursor.position() as usize);
if remaining < associated_data_length {
return Err(Error::InvalidLength);
}
let mut associated_data = vec![0u8; associated_data_length];
data_cursor.read_exact(&mut associated_data)?;
let salt_length = data_cursor.read_u32::<LittleEndian>()? as usize;
let remaining = data.len() - (data_cursor.position() as usize);
if remaining < salt_length {
return Err(Error::InvalidLength);
}
let mut salt = vec![0u8; salt_length];
data_cursor.read_exact(&mut salt)?;
Ok(Argon2Parameters {
associated_data,
secret_key: Vec::new(),
length,
lanes,
memory,
iterations,
variant,
version,
dc_version,
salt,
})
}
}
impl Argon2Parameters {
pub fn compute(&self, password: &[u8]) -> Result<Vec<u8>> {
let config = Config {
ad: &self.associated_data,
secret: &self.secret_key,
hash_length: self.length,
lanes: self.lanes,
mem_cost: self.memory,
time_cost: self.iterations,
variant: self.variant,
version: self.version,
#[cfg(target_arch = "wasm32")]
thread_mode: ThreadMode::Sequential,
#[cfg(not(target_arch = "wasm32"))]
thread_mode: ThreadMode::Parallel,
};
Ok(argon2::hash_raw(password, &self.salt, &config)?)
}
}
#[test]
fn test_argon2() {
use std::convert::TryInto;
let mut config = Argon2Parameters::default();
config.iterations = 2;
config.memory = 32;
let hash1 = config.compute(b"Password1").unwrap();
let config_vec: Vec<u8> = config.into();
assert_ne!(config_vec.len(), 0);
let config: Argon2Parameters = config_vec.as_slice().try_into().unwrap();
let hash2 = config.compute(b"Password1").unwrap();
let hash3 = config.compute(b"Password2").unwrap();
let mut config = Argon2Parameters::default();
config.iterations = 2;
config.memory = 32;
let hash4 = config.compute(b"Password1").unwrap();
let mut config5 = Argon2Parameters::default();
config5.iterations = 2;
config5.memory = 32;
config5.length = 41;
let hash5 = config5.compute(b"Password1").unwrap();
assert_eq!(hash1.len(), config.length as usize);
assert_eq!(hash1, hash2);
assert_ne!(hash1, hash3);
assert_ne!(hash1, hash4);
assert_eq!(hash5.len(), config5.length as usize);
}