pure Rust implementation of BLAKE2 based on RFC 7693
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

389 lines
13 KiB

// Copyright 2015 blake2-rfc Developers
//
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
use arrayvec::ArrayVec;
pub const SIGMA: [[usize; 16]; 10] = [
[ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15],
[14, 10, 4, 8, 9, 15, 13, 6, 1, 12, 0, 2, 11, 7, 5, 3],
[11, 8, 12, 0, 5, 2, 15, 13, 10, 14, 3, 6, 7, 1, 9, 4],
[ 7, 9, 3, 1, 13, 12, 11, 14, 2, 6, 5, 10, 4, 0, 15, 8],
[ 9, 0, 5, 7, 2, 4, 10, 15, 14, 1, 11, 12, 6, 8, 3, 13],
[ 2, 12, 6, 10, 0, 11, 8, 3, 4, 13, 7, 5, 15, 14, 1, 9],
[12, 5, 1, 15, 14, 13, 4, 10, 0, 7, 6, 3, 9, 2, 8, 11],
[13, 11, 7, 14, 12, 1, 3, 9, 5, 0, 15, 4, 8, 6, 2, 10],
[ 6, 15, 14, 9, 11, 3, 0, 8, 12, 2, 13, 7, 1, 4, 10, 5],
[10, 2, 8, 4, 7, 6, 1, 5, 15, 11, 9, 14, 3, 12, 13, 0],
];
macro_rules! blake2_impl {
($state:ident, $result:ident, $func:ident, $word:ident, $vec:ident,
$bytes:expr, $R1:expr, $R2:expr, $R3:expr, $R4:expr, $IV:expr) => {
use core::cmp;
#[cfg(feature = "std")]
use std::io;
use $crate::as_bytes::AsBytes;
use $crate::bytes::BytesExt;
use $crate::constant_time_eq::constant_time_eq;
use $crate::simd::{Vector4, $vec};
/// Container for a hash result.
///
/// This container uses a constant-time comparison for equality.
/// If a constant-time comparison is not necessary, the hash
/// result can be extracted with the `as_bytes` method.
#[derive(Clone, Copy, Debug)]
pub struct $result {
h: [$vec; 2],
nn: usize,
}
#[cfg_attr(feature = "cargo-clippy", allow(len_without_is_empty))]
impl $result {
/// Returns the contained hash result as a byte string.
#[inline]
pub fn as_bytes(&self) -> &[u8] { &self.h.as_bytes()[..self.nn] }
/// Returns the length of the hash result.
///
/// This is the same value that was used to create the hash
/// context.
#[inline]
pub fn len(&self) -> usize { self.nn }
}
impl AsRef<[u8]> for $result {
#[inline]
fn as_ref(&self) -> &[u8] { self.as_bytes() }
}
impl Eq for $result { }
impl PartialEq for $result {
#[inline]
fn eq(&self, other: &Self) -> bool {
constant_time_eq(self.as_bytes(), other.as_bytes())
}
}
impl PartialEq<[u8]> for $result {
#[inline]
fn eq(&self, other: &[u8]) -> bool {
constant_time_eq(self.as_bytes(), other)
}
}
/// State context.
#[derive(Clone, Debug)]
pub struct $state {
m: [$word; 16],
h: [$vec; 2],
t: u64,
nn: usize,
}
const IV: [$word; 8] = $IV;
#[inline(always)]
fn iv0() -> $vec { $vec::new(IV[0], IV[1], IV[2], IV[3]) }
#[inline(always)]
fn iv1() -> $vec { $vec::new(IV[4], IV[5], IV[6], IV[7]) }
/// Convenience function for all-in-one computation.
pub fn $func(nn: usize, k: &[u8], data: &[u8]) -> $result {
let mut state = $state::with_key(nn, k);
state.update(data);
state.finalize()
}
impl $state {
/// Creates a new hashing context without a key.
pub fn new(nn: usize) -> Self { Self::with_key(nn, &[]) }
/// Creates a new hashing context with a key.
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
pub fn with_key(nn: usize, k: &[u8]) -> Self {
let kk = k.len();
assert!(nn >= 1 && nn <= $bytes && kk <= $bytes);
let p0 = 0x01010000 ^ ((kk as $word) << 8) ^ (nn as $word);
let mut state = $state {
m: [0; 16],
h: [iv0() ^ $vec::new(p0, 0, 0, 0), iv1()],
t: 0,
nn: nn,
};
if kk > 0 {
state.m.as_mut_bytes().copy_bytes_from(k);
state.t = $bytes * 2;
}
state
}
#[doc(hidden)]
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
pub fn with_parameter_block(p: &[$word; 8]) -> Self {
let nn = p[0] as u8 as usize;
let kk = (p[0] >> 8) as u8 as usize;
assert!(nn >= 1 && nn <= $bytes && kk <= $bytes);
$state {
m: [0; 16],
h: [iv0() ^ $vec::new(p[0], p[1], p[2], p[3]),
iv1() ^ $vec::new(p[4], p[5], p[6], p[7])],
t: 0,
nn: nn,
}
}
/// Updates the hashing context with more data.
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
pub fn update(&mut self, data: &[u8]) {
let mut rest = data;
let off = (self.t % ($bytes * 2)) as usize;
if off != 0 || self.t == 0 {
let len = cmp::min(($bytes * 2) - off, rest.len());
let part = &rest[..len];
rest = &rest[part.len()..];
self.m.as_mut_bytes()[off..].copy_bytes_from(part);
self.t = self.t.checked_add(part.len() as u64)
.expect("hash data length overflow");
}
while rest.len() >= $bytes * 2 {
self.compress(0, 0);
let part = &rest[..($bytes * 2)];
rest = &rest[part.len()..];
self.m.as_mut_bytes().copy_bytes_from(part);
self.t = self.t.checked_add(part.len() as u64)
.expect("hash data length overflow");
}
if rest.len() > 0 {
self.compress(0, 0);
self.m.as_mut_bytes().copy_bytes_from(rest);
self.t = self.t.checked_add(rest.len() as u64)
.expect("hash data length overflow");
}
}
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation))]
fn finalize_with_flag(&mut self, f1: $word) {
let off = (self.t % ($bytes * 2)) as usize;
if off != 0 {
self.m.as_mut_bytes()[off..].set_bytes(0);
}
self.compress(!0, f1);
}
/// Consumes the hashing context and returns the resulting hash.
#[inline]
pub fn finalize(mut self) -> $result {
self.finalize_with_flag(0);
self.into_result()
}
#[doc(hidden)]
#[inline]
pub fn finalize_last_node(mut self) -> $result {
self.finalize_with_flag(!0);
self.into_result()
}
#[doc(hidden)]
pub fn finalize_inplace(&mut self) -> &[u8] {
self.finalize_with_flag(0);
self.result_inplace()
}
#[doc(hidden)]
pub fn finalize_last_node_inplace(&mut self) -> &[u8] {
self.finalize_with_flag(!0);
self.result_inplace()
}
#[inline]
fn into_result(self) -> $result {
$result {
h: [self.h[0].to_le(), self.h[1].to_le()],
nn: self.nn,
}
}
fn result_inplace(&mut self) -> &[u8] {
self.h[0] = self.h[0].to_le();
self.h[1] = self.h[1].to_le();
let result = &self.h.as_bytes()[..self.nn];
self.nn = 0; // poison self
result
}
#[inline(always)]
fn quarter_round(v: &mut [$vec; 4], rd: u32, rb: u32, m: $vec) {
v[0] = v[0].wrapping_add(v[1]).wrapping_add(m.from_le());
v[3] = (v[3] ^ v[0]).rotate_right_const(rd);
v[2] = v[2].wrapping_add(v[3]);
v[1] = (v[1] ^ v[2]).rotate_right_const(rb);
}
#[inline(always)]
fn shuffle(v: &mut [$vec; 4]) {
v[1] = v[1].shuffle_left_1();
v[2] = v[2].shuffle_left_2();
v[3] = v[3].shuffle_left_3();
}
#[inline(always)]
fn unshuffle(v: &mut [$vec; 4]) {
v[1] = v[1].shuffle_right_1();
v[2] = v[2].shuffle_right_2();
v[3] = v[3].shuffle_right_3();
}
#[inline(always)]
fn round(v: &mut [$vec; 4], m: &[$word; 16], s: &[usize; 16]) {
$state::quarter_round(v, $R1, $R2, $vec::gather(m,
s[ 0], s[ 2], s[ 4], s[ 6]));
$state::quarter_round(v, $R3, $R4, $vec::gather(m,
s[ 1], s[ 3], s[ 5], s[ 7]));
$state::shuffle(v);
$state::quarter_round(v, $R1, $R2, $vec::gather(m,
s[ 8], s[10], s[12], s[14]));
$state::quarter_round(v, $R3, $R4, $vec::gather(m,
s[ 9], s[11], s[13], s[15]));
$state::unshuffle(v);
}
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation, eq_op))]
fn compress(&mut self, f0: $word, f1: $word) {
use $crate::blake2::SIGMA;
let m = &self.m;
let h = &mut self.h;
let t0 = self.t as $word;
let t1 = match $bytes {
64 => 0,
32 => (self.t >> 32) as $word,
_ => unreachable!(),
};
let mut v = [
h[0],
h[1],
iv0(),
iv1() ^ $vec::new(t0, t1, f0, f1),
];
$state::round(&mut v, m, &SIGMA[0]);
$state::round(&mut v, m, &SIGMA[1]);
$state::round(&mut v, m, &SIGMA[2]);
$state::round(&mut v, m, &SIGMA[3]);
$state::round(&mut v, m, &SIGMA[4]);
$state::round(&mut v, m, &SIGMA[5]);
$state::round(&mut v, m, &SIGMA[6]);
$state::round(&mut v, m, &SIGMA[7]);
$state::round(&mut v, m, &SIGMA[8]);
$state::round(&mut v, m, &SIGMA[9]);
if $bytes > 32 {
$state::round(&mut v, m, &SIGMA[0]);
$state::round(&mut v, m, &SIGMA[1]);
}
h[0] = h[0] ^ (v[0] ^ v[2]);
h[1] = h[1] ^ (v[1] ^ v[3]);
}
}
impl Default for $state {
fn default() -> Self {
Self::new($bytes)
}
}
#[cfg(feature = "std")]
impl io::Write for $state {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.t.checked_add(buf.len() as u64).is_none() {
return Err(io::Error::new(io::ErrorKind::WriteZero,
"counter overflow"));
}
self.update(buf);
Ok(buf.len())
}
#[inline]
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
}
}
#[cfg_attr(feature = "cargo-clippy", allow(cast_possible_truncation, unreadable_literal))]
#[cold]
#[doc(hidden)]
pub fn selftest_seq(len: usize) -> ArrayVec<[u8; 1024]> {
use core::num::Wrapping;
let seed = Wrapping(len as u32);
let mut out = ArrayVec::new();
let mut a = Wrapping(0xDEAD4BAD) * seed;
let mut b = Wrapping(1);
for _ in 0..len {
let t = a + b;
a = b;
b = t;
out.push((t >> 24).0 as u8);
}
out
}
macro_rules! blake2_selftest_impl {
($state:ident, $func:ident, $res:expr, $md_len:expr, $in_len:expr) => {
/// Runs the self-test for this hash function.
#[cold]
pub fn selftest() {
use $crate::blake2::selftest_seq;
const BLAKE2_RES: [u8; 32] = $res;
const B2_MD_LEN: [usize; 4] = $md_len;
const B2_IN_LEN: [usize; 6] = $in_len;
let mut state = $state::new(32);
for &outlen in &B2_MD_LEN {
for &inlen in &B2_IN_LEN {
let data = selftest_seq(inlen);
let md = $func(outlen, &[], &data);
state.update(md.as_bytes());
let key = selftest_seq(outlen);
let md = $func(outlen, &key, &data);
state.update(md.as_bytes());
}
}
assert_eq!(&state.finalize(), &BLAKE2_RES[..]);
}
}
}