use blake2::digest::{Update, VariableOutput};
use blake2::Blake2bVar;
use crate::block::{self, Block, Matrix, ARGON2_BLOCK_BYTES};
use crate::octword::u64x2;
use std::error::Error;
use std::fmt;
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
pub enum Variant {
Argon2d = 0,
Argon2i = 1,
Argon2id = 2,
}
const DEF_B2HASH_LEN: usize = 64;
const SLICES_PER_LANE: u32 = 4;
pub mod defaults {
pub const PASSES: u32 = 3;
pub const KIB: u32 = 4096;
pub const LANES: u32 = 1;
pub const LENGTH: usize = 32;
}
fn split_u64(n: u64) -> (u32, u32) {
((n & 0xffffffff) as u32, (n >> 32) as u32)
}
fn as32le(k: u32) -> [u8; 4] {
k.to_le_bytes()
}
fn len32(t: &[u8]) -> [u8; 4] {
as32le(t.len() as u32)
}
macro_rules! b2hash {
($($bytes: expr),*) => {
{
let mut out: [u8; DEF_B2HASH_LEN] = [0u8; DEF_B2HASH_LEN];
b2hash!(&mut out; $($bytes),*);
out
}
};
($out: expr; $($bytes: expr),*) => {
{
let mut hasher = Blake2bVar::new($out.len()).unwrap();
$(hasher.update($bytes));*;
hasher.finalize_variable($out).unwrap();
}
};
}
fn h0(
lanes: u32,
hash_length: u32,
memory_kib: u32,
passes: u32,
version: u32,
variant: Variant,
p: &[u8],
s: &[u8],
k: &[u8],
x: &[u8],
) -> [u8; 72] {
let mut rv = [0_u8; 72];
b2hash!(&mut rv[0..DEF_B2HASH_LEN];
&as32le(lanes), &as32le(hash_length), &as32le(memory_kib),
&as32le(passes), &as32le(version), &as32le(variant as u32),
&len32(p), p,
&len32(s), s,
&len32(k), k,
&len32(x), x);
rv
}
#[derive(Debug, Eq, PartialEq)]
pub struct Argon2 {
passes: u32,
lanes: u32,
lanelen: u32,
kib: u32,
variant: Variant,
version: Version,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum Version {
_0x10 = 0x10,
_0x13 = 0x13,
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ParamErr {
TooFewPasses,
TooFewLanes,
TooManyLanes,
MinKiB(u64),
}
impl fmt::Display for ParamErr {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use ParamErr::*;
match *self {
TooFewPasses => write!(f, "Argon2 requires one or more passes to be run."),
TooFewLanes | TooManyLanes => write!(f, "The number of lanes must be between one and 2^24 - 1."),
MinKiB(k) => write!(f, "Memory parameter must be >= {} KiB.", k),
}
}
}
impl Error for ParamErr {}
impl Argon2 {
pub fn default(v: Variant) -> Argon2 {
Argon2::new(defaults::PASSES, defaults::LANES, defaults::KIB, v)
.ok()
.unwrap()
}
pub fn new(passes: u32, lanes: u32, kib: u32, variant: Variant) -> Result<Argon2, ParamErr> {
Argon2::with_version(passes, lanes, kib, variant, Version::_0x13)
}
pub(crate) fn with_version(
passes: u32,
lanes: u32,
kib: u32,
variant: Variant,
version: Version,
) -> Result<Argon2, ParamErr> {
if passes < 1 {
Result::Err(ParamErr::TooFewPasses)
} else if lanes < 1 {
Result::Err(ParamErr::TooFewLanes)
} else if 0x00ffffff < lanes {
Result::Err(ParamErr::TooManyLanes)
} else if (kib as u64) < 8 * lanes as u64 {
Result::Err(ParamErr::MinKiB(8 * lanes as u64))
} else {
Result::Ok(Argon2 {
passes,
lanes,
lanelen: kib / (4 * lanes) * 4,
kib,
variant,
version,
})
}
}
pub fn hash(&self, out: &mut [u8], p: &[u8], s: &[u8], k: &[u8], x: &[u8]) {
self.hash_impl(out, p, s, k, x, |_| {}, |_, _| {});
}
fn hash_impl<F, G>(
&self,
out: &mut [u8],
p: &[u8],
s: &[u8],
k: &[u8],
x: &[u8],
mut h0_fn: F,
mut pass_fn: G,
) where
F: FnMut(&[u8]),
G: FnMut(u32, &Matrix),
{
assert!(4 <= out.len() && out.len() <= 0xffffffff);
assert!(p.len() <= 0xffffffff);
assert!(8 <= s.len() && s.len() <= 0xffffffff);
assert!(k.len() <= 32);
assert!(x.len() <= 0xffffffff);
let mut blocks = Matrix::new(self.lanes, self.lanelen);
let h0 = h0(
self.lanes,
out.len() as u32,
self.kib,
self.passes,
self.version as u32,
self.variant,
p,
s,
k,
x,
);
h0_fn(&h0);
for lane in 0..self.lanes {
self.fill_first_slice(&mut blocks, h0, lane);
}
for slice in 1..SLICES_PER_LANE {
for lane in 0..self.lanes {
self.fill_slice(&mut blocks, 0, lane, slice, 0);
}
}
pass_fn(0, &blocks);
for p in 1..self.passes {
for slice in 0..SLICES_PER_LANE {
for lane in 0..self.lanes {
self.fill_slice(&mut blocks, p, lane, slice, 0);
}
}
pass_fn(p, &blocks); }
h_prime(out, &blocks.xor_column(self.lanelen - 1).as_u8());
}
fn fill_first_slice(&self, blks: &mut Matrix, mut h0: [u8; 72], lane: u32) {
h0[68..72].clone_from_slice(&as32le(lane));
h0[64..68].clone_from_slice(&as32le(0));
h_prime(blks[(lane, 0)].as_u8_mut(), &h0);
h0[64..68].clone_from_slice(&as32le(1));
h_prime(blks[(lane, 1)].as_u8_mut(), &h0);
self.fill_slice(blks, 0, lane, 0, 2);
}
fn fill_slice(&self, blks: &mut Matrix, pass: u32, lane: u32, slice: u32, offset: u32) {
let mut jgen = Gen2i::new(
offset as usize,
pass,
lane,
slice,
self.lanes * self.lanelen,
self.passes,
self.variant,
);
let slicelen = self.lanelen / SLICES_PER_LANE;
use Variant::*;
for idx in offset..slicelen {
let (j1, j2) = match self.variant {
Argon2i => jgen.nextj(),
Argon2d => {
let col = self.prev(slice * slicelen + idx);
split_u64((blks[(lane, col)])[0].0)
}
Argon2id if pass == 0 && slice < 2 => jgen.nextj(),
Argon2id => {
let col = self.prev(slice * slicelen + idx);
split_u64((blks[(lane, col)])[0].0)
}
};
self.fill_block(blks, pass, lane, slice, idx, j1, j2);
}
}
fn fill_block(
&self,
blks: &mut Matrix,
pass: u32,
lane: u32,
slice: u32,
idx: u32,
j1: u32,
j2: u32,
) {
let slicelen = self.lanelen / SLICES_PER_LANE;
let ls = self.lanes;
let z = index_alpha(pass, lane, slice, ls, idx, slicelen, j1, j2);
let zth = match (pass, slice) {
(0, 0) => (lane, z),
_ => (j2 % self.lanes, z),
};
let cur = (lane, slice * slicelen + idx);
let pre = (lane, self.prev(cur.1));
let (wr, rd, refblk) = blks.get3(cur, pre, zth);
match self.version {
Version::_0x10 => g(wr, rd, refblk),
Version::_0x13 => g_xor(wr, rd, refblk),
}
}
fn prev(&self, n: u32) -> u32 {
if n > 0 {
n - 1
} else {
self.lanelen - 1
}
}
pub fn params(&self) -> (Variant, u32, u32, u32, Version) {
(
self.variant,
self.kib,
self.passes,
self.lanes,
self.version,
)
}
}
pub fn argon2i_simple<P, S>(password: &P, salt: &S) -> [u8; defaults::LENGTH]
where
P: AsRef<[u8]> + ?Sized,
S: AsRef<[u8]> + ?Sized,
{
let mut out = [0; defaults::LENGTH];
let a2 = Argon2::default(Variant::Argon2i);
a2.hash(&mut out, password.as_ref(), salt.as_ref(), &[], &[]);
out
}
pub fn argon2d_simple<P, S>(password: &P, salt: &S) -> [u8; defaults::LENGTH]
where
P: AsRef<[u8]> + ?Sized,
S: AsRef<[u8]> + ?Sized,
{
let mut out = [0; defaults::LENGTH];
let a2 = Argon2::default(Variant::Argon2d);
a2.hash(&mut out, password.as_ref(), salt.as_ref(), &[], &[]);
out
}
pub fn argon2id_simple<P, S>(password: &P, salt: &S) -> [u8; defaults::LENGTH]
where
P: AsRef<[u8]> + ?Sized,
S: AsRef<[u8]> + ?Sized,
{
let mut out = [0; defaults::LENGTH];
let a2 = Argon2::default(Variant::Argon2id);
a2.hash(&mut out, password.as_ref(), salt.as_ref(), &[], &[]);
out
}
fn h_prime(out: &mut [u8], input: &[u8]) {
if out.len() <= DEF_B2HASH_LEN {
b2hash!(out; &len32(out), input);
} else {
let mut tmp = b2hash!(&len32(out), input);
out[0..DEF_B2HASH_LEN].clone_from_slice(&tmp);
let mut wr_at: usize = 32;
while out.len() - wr_at > DEF_B2HASH_LEN {
b2hash!(&mut tmp; &tmp);
out[wr_at..wr_at + DEF_B2HASH_LEN].clone_from_slice(&tmp);
wr_at += DEF_B2HASH_LEN / 2;
}
let len = out.len() - wr_at;
b2hash!(&mut out[wr_at..wr_at + len]; &tmp);
}
}
fn index_alpha(
pass: u32,
lane: u32,
slice: u32,
lanes: u32,
sliceidx: u32,
slicelen: u32,
j1: u32,
j2: u32,
) -> u32 {
let lanelen = slicelen * SLICES_PER_LANE;
let r: u32 = match (pass, slice, j2 % lanes == lane) {
(0, 0, _) => sliceidx - 1,
(0, _, false) => slice * slicelen - if sliceidx == 0 { 1 } else { 0 },
(0, _, true) => slice * slicelen + sliceidx - 1,
(_, _, false) => lanelen - slicelen - if sliceidx == 0 { 1 } else { 0 },
(_, _, true) => lanelen - slicelen + sliceidx - 1,
};
let (r_, j1_) = (r as u64, j1 as u64);
let relpos = (r_ - 1 - ((r_ * ((j1_ * j1_) >> 32)) >> 32)) as u32;
match (pass, slice) {
(0, _) | (_, 3) => relpos % lanelen,
_ => (slicelen * (slice + 1) + relpos) % lanelen,
}
}
struct Gen2i {
arg: Block,
pseudos: Block,
idx: usize,
}
impl Gen2i {
fn new(
start_at: usize,
pass: u32,
lane: u32,
slice: u32,
totblocks: u32,
totpasses: u32,
variant: Variant,
) -> Gen2i {
use crate::block::zero;
let mut rv = Gen2i {
arg: zero(),
pseudos: zero(),
idx: start_at,
};
let args = [
(pass, lane),
(slice, totblocks),
(totpasses, variant as u32),
];
for (k, (lo, hi)) in rv.arg.iter_mut().zip(args.into_iter()) {
*k = u64x2(lo as u64, hi as u64);
}
rv.more();
rv
}
fn more(&mut self) {
self.arg[3].0 += 1;
g_two(&mut self.pseudos, &self.arg);
}
fn nextj(&mut self) -> (u32, u32) {
let rv = split_u64(self.pseudos.as_u64()[self.idx]);
self.idx = (self.idx + 1) % per_kib!(u64);
if self.idx == 0 {
self.more();
}
rv
}
}
fn g(dest: &mut Block, lhs: &Block, rhs: &Block) {
for (d, (l, r)) in dest.iter_mut().zip(lhs.iter().zip(rhs.iter())) {
*d = *l ^ *r;
}
for row in 0..8 {
p_row(row, dest);
}
for col in 0..8 {
p_col(col, dest);
}
*dest ^= (lhs, rhs);
}
fn g_xor(dest: &mut Block, lhs: &Block, rhs: &Block) {
let mut tmp: Block = block::zero();
let lr = lhs.iter().zip(rhs.iter());
for ((d, t), (l, r)) in dest.iter_mut().zip(tmp.iter_mut()).zip(lr) {
*t = *l ^ *r;
*d = *d ^ *t;
}
for row in 0..8 {
p_row(row, &mut tmp);
}
for col in 0..8 {
p_col(col, &mut tmp);
}
*dest ^= &tmp;
}
fn g_two(dest: &mut Block, src: &Block) {
*dest = src.clone();
for row in 0..8 {
p_row(row, dest);
}
for col in 0..8 {
p_col(col, dest);
}
*dest ^= src;
let tmp: Block = dest.clone();
for row in 0..8 {
p_row(row, dest);
}
for col in 0..8 {
p_col(col, dest);
}
*dest ^= &tmp;
}
macro_rules! p {
($v0v1: expr, $v2v3: expr, $v4v5: expr, $v6v7: expr,
$v8v9: expr, $v10v11: expr, $v12v13: expr, $v14v15: expr) => {{
g_blake2b!($v0v1, $v4v5, $v8v9, $v12v13);
g_blake2b!($v2v3, $v6v7, $v10v11, $v14v15);
let (mut v7v4, mut v5v6) = $v4v5.cross_swap($v6v7);
let (mut v15v12, mut v13v14) = $v12v13.cross_swap($v14v15);
g_blake2b!($v0v1, v5v6, $v10v11, v15v12);
g_blake2b!($v2v3, v7v4, $v8v9, v13v14);
let (v4v5, v6v7) = v5v6.cross_swap(v7v4);
let (v12v13, v14v15) = v13v14.cross_swap(v15v12);
$v4v5 = v4v5;
$v6v7 = v6v7;
$v12v13 = v12v13;
$v14v15 = v14v15;
}};
}
macro_rules! g_blake2b {
($a: expr, $b: expr, $c: expr, $d: expr) => {
$a = $a + $b + $a.lower_mult($b) * u64x2(2, 2);
$d = ($d ^ $a).rotate_right(32);
$c = $c + $d + $c.lower_mult($d) * u64x2(2, 2);
$b = ($b ^ $c).rotate_right(24);
$a = $a + $b + $a.lower_mult($b) * u64x2(2, 2);
$d = ($d ^ $a).rotate_right(16);
$c = $c + $d + $c.lower_mult($d) * u64x2(2, 2);
$b = ($b ^ $c).rotate_right(63);
};
}
#[inline(always)]
fn p_row(row: usize, b: &mut Block) {
p!(
b[8 * row + 0],
b[8 * row + 1],
b[8 * row + 2],
b[8 * row + 3],
b[8 * row + 4],
b[8 * row + 5],
b[8 * row + 6],
b[8 * row + 7]
);
}
#[inline(always)]
#[allow(clippy::erasing_op)]
fn p_col(col: usize, b: &mut Block) {
p!(
b[8 * 0 + col],
b[8 * 1 + col],
b[8 * 2 + col],
b[8 * 3 + col],
b[8 * 4 + col],
b[8 * 5 + col],
b[8 * 6 + col],
b[8 * 7 + col]
);
}
#[cfg(test)]
mod tests {
use super::Argon2;
use super::{Variant, Version};
use crate::block;
use std::fmt::Write;
use std::fs::File;
use std::io::Read;
const TEST_OUTLEN: usize = 32;
const TEST_PWDLEN: usize = 32;
const TEST_SALTLEN: usize = 16;
const TEST_SECRETLEN: usize = 8;
const TEST_ADLEN: usize = 12;
macro_rules! w { ($($args: expr),*) => { let _ = write!($($args),*); }; }
macro_rules! wl { ($($args: expr),*) => { let _ = writeln!($($args),*); }; }
fn u8info(prefix: &str, bytes: &[u8], print_length: bool) -> String {
let bs = bytes
.iter()
.fold(String::new(), |xs, b| xs + &format!("{:02x} ", b));
let len = match print_length {
false => ": ".to_string(),
true => format!("[{}]: ", bytes.len()),
};
prefix.to_string() + &len + &bs
}
fn block_info(i: usize, b: &block::Block) -> String {
let blk = b.as_u64();
blk.iter()
.enumerate()
.fold(String::new(), |xs, (j, octword)| {
xs + "Block "
+ &format!("{:004} ", i)
+ &format!("[{:>3}]: ", j)
+ &format!("{:0016x}", octword)
+ "\n"
})
}
fn run_and_collect(
arg: &Argon2,
out: &mut [u8],
p: &[u8],
s: &[u8],
k: &[u8],
x: &[u8],
) -> (String, String) {
let (mut h0output, mut blockoutput) = (String::new(), String::new());
{
let h0fn = |h0: &[u8]| {
wl!(
&mut h0output,
"{}",
u8info("Pre-hashing digest", &h0[..super::DEF_B2HASH_LEN], false)
);
};
let passfn = |p: u32, matrix: &block::Matrix| {
wl!(&mut blockoutput, "\n After pass {}:", p);
for (i, block) in matrix.iter().enumerate() {
w!(&mut blockoutput, "{}", block_info(i, block));
}
};
arg.hash_impl(out, p, s, k, x, h0fn, passfn);
}
(h0output, blockoutput)
}
fn compare_kats(fexpected: &str, variant: Variant, vers: Version) {
let mut f = File::open(fexpected).unwrap();
let mut expected = String::new();
f.read_to_string(&mut expected).unwrap();
let (p, s) = (&[1; TEST_PWDLEN], &[2; TEST_SALTLEN]);
let (k, x) = (&[3; TEST_SECRETLEN], &[4; TEST_ADLEN]);
let mut out = [0 as u8; TEST_OUTLEN];
let a2 = Argon2::with_version(3, 4, 32, variant, vers).ok().unwrap();
let (h0, blocks) = run_and_collect(&a2, &mut out, p, s, k, x);
let mut rv = String::new();
wl!(rv, "=======================================");
wl!(
rv,
"{:?} version number {}",
a2.variant,
a2.version as usize
);
wl!(rv, "=======================================");
w!(rv, "Memory: {} KiB, Iterations: {}, ", a2.kib, a2.passes);
w!(rv, "Parallelism: {} lanes, ", a2.lanes);
wl!(rv, "Tag length: {} bytes", out.len());
wl!(rv, "{}", u8info("Password", p, true));
wl!(rv, "{}", u8info("Salt", s, true));
wl!(rv, "{}", u8info("Secret", k, true));
wl!(rv, "{}", u8info("Associated data", x, true));
w!(rv, "{}", h0 + &blocks);
wl!(rv, "{}", u8info("Tag", &out, false));
if expected.trim() != rv.trim() {
println!("{}", rv);
assert!(false);
}
}
#[test]
fn argon2i_kat() {
compare_kats("kats/0x10/argon2i", Variant::Argon2i, Version::_0x10);
compare_kats("kats/0x13/argon2i", Variant::Argon2i, Version::_0x13);
}
#[test]
fn argon2d_kat() {
compare_kats("kats/0x10/argon2d", Variant::Argon2d, Version::_0x10);
compare_kats("kats/0x13/argon2d", Variant::Argon2d, Version::_0x13);
}
#[test]
fn argon2id_kat() {
compare_kats("kats/0x10/argon2id", Variant::Argon2id, Version::_0x10);
compare_kats("kats/0x13/argon2id", Variant::Argon2id, Version::_0x13);
}
}