implement blake2b
This commit is contained in:
1 parent
208e8eda77
commit
1fcf38db41
6 files changed
+469
No files matched your search
@@ -0,0 +1,190 @@
|
||||
use super::compress::{IV, compress};
|
||||
|
||||
pub const BLOCK_SIZE: usize = 128;
|
||||
pub const MAX_DIGEST_SIZE: usize = 64;
|
||||
pub const MAX_KEY_SIZE: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Blake2bError {
|
||||
InvalidDigestLength,
|
||||
InvalidKeyLength,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Blake2b {
|
||||
h: [u64; 8],
|
||||
t: [u64; 2],
|
||||
f: [u64; 2],
|
||||
|
||||
buffer: [u8; BLOCK_SIZE],
|
||||
buffer_len: usize,
|
||||
|
||||
digest_len: usize,
|
||||
}
|
||||
|
||||
impl Blake2b {
|
||||
/// Create a BLAKE2b-512 hasher.
|
||||
pub fn new() -> Self {
|
||||
Self::with_output_length(MAX_DIGEST_SIZE).expect("64-byte BLAKE2b digest is valid")
|
||||
}
|
||||
|
||||
/// Create an unkeyed BLAKE2b hasher with a digest of `digest_len` bytes.
|
||||
///
|
||||
/// Valid digest lengths are 1..=64 bytes.
|
||||
pub fn with_output_length(digest_len: usize) -> Result<Self, Blake2bError> {
|
||||
Self::new_inner(digest_len, &[])
|
||||
}
|
||||
|
||||
/// Create a keyed BLAKE2b-512 hasher.
|
||||
///
|
||||
/// Valid key lengths are 0..=64 bytes.
|
||||
pub fn new_keyed(key: &[u8]) -> Result<Self, Blake2bError> {
|
||||
Self::new_keyed_with_output_length(MAX_DIGEST_SIZE, key)
|
||||
}
|
||||
|
||||
/// Create a keyed BLAKE2b hasher with a custom digest length.
|
||||
pub fn new_keyed_with_output_length(
|
||||
digest_len: usize,
|
||||
key: &[u8],
|
||||
) -> Result<Self, Blake2bError> {
|
||||
Self::new_inner(digest_len, key)
|
||||
}
|
||||
|
||||
fn new_inner(digest_len: usize, key: &[u8]) -> Result<Self, Blake2bError> {
|
||||
if !(1..=MAX_DIGEST_SIZE).contains(&digest_len) {
|
||||
return Err(Blake2bError::InvalidDigestLength);
|
||||
}
|
||||
|
||||
if key.len() > MAX_KEY_SIZE {
|
||||
return Err(Blake2bError::InvalidKeyLength);
|
||||
}
|
||||
|
||||
/*
|
||||
* BLAKE2b parameter block, first 64-bit word:
|
||||
*
|
||||
* bits 0.. 7 digest length
|
||||
* bits 8..15 key length
|
||||
* bits 16..23 fanout = 1
|
||||
* bits 24..31 depth = 1
|
||||
*
|
||||
* The remaining parameters use their sequential hashing defaults of zero.
|
||||
*/
|
||||
let param0 = (digest_len as u64) | ((key.len() as u64) << 8) | (1u64 << 16) | (1u64 << 24);
|
||||
|
||||
let mut h = IV;
|
||||
|
||||
// Parameter block is XORed into the IV during initialization.
|
||||
h[0] ^= param0;
|
||||
|
||||
let mut state = Self {
|
||||
h,
|
||||
t: [0, 0],
|
||||
f: [0, 0],
|
||||
buffer: [0u8; BLOCK_SIZE],
|
||||
buffer_len: 0,
|
||||
digest_len,
|
||||
};
|
||||
|
||||
/*
|
||||
* In keyed BLAKE2, the padded key is processed as the first 128 bytes block.
|
||||
*
|
||||
* We deliberately leave this block buffered until another block arrives or finalize() is called, so that the last-block flag is handled correctly.
|
||||
*/
|
||||
if !key.is_empty() {
|
||||
state.buffer[..key.len()].copy_from_slice(key);
|
||||
state.buffer_len = BLOCK_SIZE;
|
||||
}
|
||||
|
||||
Ok(state)
|
||||
}
|
||||
|
||||
/// Feed more input into the hash.
|
||||
pub fn update(&mut self, mut input: &[u8]) {
|
||||
while !input.is_empty() {
|
||||
if self.buffer_len == BLOCK_SIZE {
|
||||
self.increment_counter(BLOCK_SIZE as u64);
|
||||
|
||||
compress(&mut self.h, &self.buffer, self.t, self.f);
|
||||
|
||||
self.buffer = [0u8; BLOCK_SIZE];
|
||||
self.buffer_len = 0;
|
||||
}
|
||||
|
||||
let space = BLOCK_SIZE - self.buffer_len;
|
||||
let take = space.min(input.len());
|
||||
|
||||
self.buffer[self.buffer_len..self.buffer_len + take].copy_from_slice(&input[..take]);
|
||||
|
||||
self.buffer_len += take;
|
||||
input = &input[take..];
|
||||
}
|
||||
}
|
||||
|
||||
/// Finalize the hash and return the digest.
|
||||
///
|
||||
/// This consumes the hasher.
|
||||
pub fn finalize(mut self) -> Vec<u8> {
|
||||
// Count the final partial block
|
||||
self.increment_counter(self.buffer_len as u64);
|
||||
|
||||
// Set the final block flag
|
||||
self.f[0] = u64::MAX;
|
||||
|
||||
// Zero pad the remaining bytes
|
||||
for byte in &mut self.buffer[self.buffer_len..] {
|
||||
*byte = 0;
|
||||
}
|
||||
|
||||
compress(&mut self.h, &self.buffer, self.t, self.f);
|
||||
|
||||
let mut output = Vec::with_capacity(MAX_DIGEST_SIZE);
|
||||
|
||||
for word in self.h {
|
||||
output.extend_from_slice(&word.to_le_bytes());
|
||||
}
|
||||
|
||||
output.truncate(self.digest_len);
|
||||
output
|
||||
}
|
||||
|
||||
/// One-shot BLAKE2b-512.
|
||||
pub fn hash(input: &[u8]) -> Vec<u8> {
|
||||
let mut hasher = Self::new();
|
||||
hasher.update(input);
|
||||
hasher.finalize()
|
||||
}
|
||||
|
||||
/// One-shot BLAKE2b with a custom digest length.
|
||||
pub fn hash_with_output_length(
|
||||
input: &[u8],
|
||||
digest_len: usize,
|
||||
) -> Result<Vec<u8>, Blake2bError> {
|
||||
let mut hasher = Self::with_output_length(digest_len)?;
|
||||
hasher.update(input);
|
||||
Ok(hasher.finalize())
|
||||
}
|
||||
|
||||
/// One-shot keyed BLAKE2b-512.
|
||||
pub fn hash_keyed(input: &[u8], key: &[u8]) -> Result<Vec<u8>, Blake2bError> {
|
||||
let mut hasher = Self::new_keyed(key)?;
|
||||
hasher.update(input);
|
||||
Ok(hasher.finalize())
|
||||
}
|
||||
|
||||
/// Increment the 128-bit little-endian byte counter.
|
||||
fn increment_counter(&mut self, amount: u64) {
|
||||
let (low, overflow) = self.t[0].overflowing_add(amount);
|
||||
|
||||
self.t[0] = low;
|
||||
|
||||
if overflow {
|
||||
self.t[1] = self.t[1].wrapping_add(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Blake2b {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
use super::mix::g;
|
||||
|
||||
/// Initialization vector for BLAKE2b.
|
||||
pub(crate) const IV: [u64; 8] = [
|
||||
0x6a09e667f3bcc908,
|
||||
0xbb67ae8584caa73b,
|
||||
0x3c6ef372fe94f82b,
|
||||
0xa54ff53a5f1d36f1,
|
||||
0x510e527fade682d1,
|
||||
0x9b05688c2b3e6c1f,
|
||||
0x1f83d9abfb41bd6b,
|
||||
0x5be0cd19137e2179,
|
||||
];
|
||||
|
||||
/// BLAKE2b message word permutations.
|
||||
///
|
||||
/// There are 12 rounds. BLAKE2b repeats the first two permutations for the final two rounds.
|
||||
pub(crate) const SIGMA: [[usize; 16]; 12] = [
|
||||
[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],
|
||||
[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],
|
||||
];
|
||||
|
||||
/// Compress one 128-byte BLAKE2b block.
|
||||
///
|
||||
/// `t` is the 128-bit byte counter and `f` contains the final-block flags.
|
||||
#[inline(always)]
|
||||
pub(crate) fn compress(h: &mut [u64; 8], block: &[u8; 128], t: [u64; 2], f: [u64; 2]) {
|
||||
let mut m = [0u64; 16];
|
||||
|
||||
for (i, word) in m.iter_mut().enumerate() {
|
||||
let start = i * 8;
|
||||
|
||||
let bytes: [u8; 8] = block[start..start + 8]
|
||||
.try_into()
|
||||
.expect("BLAKE2b block must contain 128 bytes");
|
||||
|
||||
*word = u64::from_le_bytes(bytes);
|
||||
}
|
||||
|
||||
let mut v = [0u64; 16];
|
||||
|
||||
v[..8].copy_from_slice(h);
|
||||
v[8..].copy_from_slice(&IV);
|
||||
|
||||
// Inject byte counter.
|
||||
v[12] ^= t[0];
|
||||
v[13] ^= t[1];
|
||||
|
||||
// Inject finalization flags.
|
||||
v[14] ^= f[0];
|
||||
v[15] ^= f[1];
|
||||
|
||||
for s in SIGMA {
|
||||
// Column step
|
||||
g(&mut v, 0, 4, 8, 12, m[s[0]], m[s[1]]);
|
||||
g(&mut v, 1, 5, 9, 13, m[s[2]], m[s[3]]);
|
||||
g(&mut v, 2, 6, 10, 14, m[s[4]], m[s[5]]);
|
||||
g(&mut v, 3, 7, 11, 15, m[s[6]], m[s[7]]);
|
||||
|
||||
// Diagonal step
|
||||
g(&mut v, 0, 5, 10, 15, m[s[8]], m[s[9]]);
|
||||
g(&mut v, 1, 6, 11, 12, m[s[10]], m[s[11]]);
|
||||
g(&mut v, 2, 7, 8, 13, m[s[12]], m[s[13]]);
|
||||
g(&mut v, 3, 4, 9, 14, m[s[14]], m[s[15]]);
|
||||
}
|
||||
|
||||
for (hi, (vi, vj)) in h.iter_mut().zip(v[..8].iter().zip(v[8..].iter())) {
|
||||
*hi ^= *vi ^ *vj;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#[inline(always)]
|
||||
pub(crate) fn g(v: &mut [u64; 16], a: usize, b: usize, c: usize, d: usize, x: u64, y: u64) {
|
||||
v[a] = v[a].wrapping_add(v[b]).wrapping_add(x);
|
||||
v[d] = (v[d] ^ v[a]).rotate_right(32);
|
||||
|
||||
v[c] = v[c].wrapping_add(v[d]);
|
||||
v[b] = (v[b] ^ v[c]).rotate_right(24);
|
||||
|
||||
v[a] = v[a].wrapping_add(v[b]).wrapping_add(y);
|
||||
v[d] = (v[d] ^ v[a]).rotate_right(16);
|
||||
|
||||
v[c] = v[c].wrapping_add(v[d]);
|
||||
v[b] = (v[b] ^ v[c]).rotate_right(63);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
mod blake2b;
|
||||
mod compress;
|
||||
mod mix;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
pub use blake2b::{BLOCK_SIZE, Blake2b, Blake2bError, MAX_DIGEST_SIZE, MAX_KEY_SIZE};
|
||||
@@ -0,0 +1,176 @@
|
||||
use super::{Blake2b, Blake2bError, MAX_DIGEST_SIZE, MAX_KEY_SIZE};
|
||||
|
||||
fn hex(bytes: &[u8]) -> String {
|
||||
bytes.iter().map(|byte| format!("{byte:02x}")).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty() {
|
||||
assert_eq!(
|
||||
hex(&Blake2b::hash(b"")),
|
||||
"786a02f742015903c6c6fd852552d272\
|
||||
912f4740e15847618a86e217f71f5419\
|
||||
d25e1031afee585313896444934eb04b\
|
||||
903a685b1448b755d56f701afe9be2ce"
|
||||
.replace(' ', "")
|
||||
.replace('\n', "")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_blake2b_384() {
|
||||
assert_eq!(
|
||||
hex(&Blake2b::hash_with_output_length(b"", 48).unwrap()),
|
||||
"b32811423377f52d7862286ee1a72ee5\
|
||||
40524380fda1724a6f25d7978c6fd324\
|
||||
4a6caf0498812673c5e05ef583825100"
|
||||
.replace(' ', "")
|
||||
.replace('\n', "")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn abc() {
|
||||
assert_eq!(
|
||||
hex(&Blake2b::hash(b"abc")),
|
||||
"ba80a53f981c4d0d6a2797b69f12f6e94\
|
||||
c212f14685ac4b74b12bb6fdbffa2d17\
|
||||
d87c5392aab792dc252d5de4533cc951\
|
||||
8d38aa8dbf1925ab92386edd4009923"
|
||||
.replace(' ', "")
|
||||
.replace('\n', "")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn quick_brown_fox() {
|
||||
assert_eq!(
|
||||
hex(&Blake2b::hash(
|
||||
b"The quick brown fox jumps over the lazy dog"
|
||||
)),
|
||||
"a8add4bdddfd93e4877d2746e62817b116\
|
||||
364a1fa7bc148d95090bc7333b3673f8\
|
||||
2401cf7aa2e4cb1ecd90296e3f14cb541\
|
||||
3f8ed77be73045b13914cdcd6a918"
|
||||
.replace(' ', "")
|
||||
.replace('\n', "")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn output_lengths() {
|
||||
let expected = [
|
||||
(1, "6b"),
|
||||
(2, "ae1e"),
|
||||
(3, "8c45ed"),
|
||||
(4, "63906248"),
|
||||
(8, "d8bb14d833d59559"),
|
||||
(16, "cf4ab791c62b8d2b2109c90275287816"),
|
||||
(
|
||||
32,
|
||||
"bddd813c634239723171ef3fee98579b\
|
||||
94964e3bb1cb3e427262c8c068d52319",
|
||||
),
|
||||
(
|
||||
48,
|
||||
"6f56a82c8e7ef526dfe182eb5212f7db\
|
||||
9df1317e57815dbda46083fc30f54ee6\
|
||||
c66ba83be64b302d7cba6ce15bb556f4",
|
||||
),
|
||||
(
|
||||
64,
|
||||
"ba80a53f981c4d0d6a2797b69f12f6e94\
|
||||
c212f14685ac4b74b12bb6fdbffa2d17\
|
||||
d87c5392aab792dc252d5de4533cc951\
|
||||
8d38aa8dbf1925ab92386edd4009923",
|
||||
),
|
||||
];
|
||||
|
||||
for (digest_len, expected_hex) in expected {
|
||||
let expected_hex = expected_hex.replace(' ', "").replace('\n', "");
|
||||
|
||||
let digest = Blake2b::hash_with_output_length(b"abc", digest_len).unwrap();
|
||||
|
||||
assert_eq!(digest.len(), digest_len);
|
||||
assert_eq!(hex(&digest), expected_hex);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyed() {
|
||||
let digest = Blake2b::hash_keyed(b"", b"key").unwrap();
|
||||
|
||||
assert_eq!(
|
||||
hex(&digest),
|
||||
"5b3cfd8f422b490b764b55eceb330b500\
|
||||
c79cbefa9a928ad00202b8b3c5dd778a\
|
||||
81122570434a2e3b8bfd028d105dfefd\
|
||||
0a9576e88ed66de742ca9fbb5f8d2b6"
|
||||
.replace(' ', "")
|
||||
.replace('\n', "")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keyed_max_key_size() {
|
||||
let key = [0x42u8; MAX_KEY_SIZE];
|
||||
|
||||
let digest = Blake2b::hash_keyed(b"abc", &key).unwrap();
|
||||
|
||||
assert_eq!(digest.len(), MAX_DIGEST_SIZE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn streaming_matches_one_shot() {
|
||||
let input: Vec<u8> = (0..=255).cycle().take(10_000).collect();
|
||||
|
||||
let expected = Blake2b::hash(&input);
|
||||
|
||||
let mut hasher = Blake2b::new();
|
||||
|
||||
for chunk in input.chunks(37) {
|
||||
hasher.update(chunk);
|
||||
}
|
||||
|
||||
assert_eq!(hasher.finalize(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn block_boundaries() {
|
||||
for len in [0usize, 1, 127, 128, 129, 255, 256, 257, 1023, 1024, 1025] {
|
||||
let input: Vec<u8> = (0..len).map(|value| value as u8).collect();
|
||||
|
||||
let expected = Blake2b::hash(&input);
|
||||
|
||||
let mut hasher = Blake2b::new();
|
||||
|
||||
for chunk in input.chunks(17) {
|
||||
hasher.update(chunk);
|
||||
}
|
||||
|
||||
assert_eq!(hasher.finalize(), expected, "failed for input length {len}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_digest_lengths() {
|
||||
assert_eq!(
|
||||
Blake2b::with_output_length(0).unwrap_err(),
|
||||
Blake2bError::InvalidDigestLength
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
Blake2b::with_output_length(MAX_DIGEST_SIZE + 1).unwrap_err(),
|
||||
Blake2bError::InvalidDigestLength
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_key_length() {
|
||||
let key = vec![0u8; MAX_KEY_SIZE + 1];
|
||||
|
||||
assert_eq!(
|
||||
Blake2b::new_keyed(&key).unwrap_err(),
|
||||
Blake2bError::InvalidKeyLength
|
||||
);
|
||||
}
|
||||
@@ -3,6 +3,7 @@ use std::path::PathBuf;
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
|
||||
mod blake2b;
|
||||
mod file_crypt;
|
||||
mod kdf;
|
||||
mod keccak;
|
||||
|
||||
Reference in new issue
Block a user