diff --git a/README.md b/README.md index abf30598..3a7735d7 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Additionally all crates do not require the standard library (i.e. `no_std` capab | Algorithm | Crate | Crates.io | Documentation | MSRV | [Security] | |-----------|-------|:---------:|:-------------:|:----:|:----------:| | [Ascon] hash | [`ascon‑hash`] | [![crates.io](https://img.shields.io/crates/v/ascon-hash.svg)](https://crates.io/crates/ascon-hash) | [![Documentation](https://docs.rs/ascon-hash/badge.svg)](https://docs.rs/ascon-hash) | 1.85 | :green_heart: | +| [`bash-hash`][bash_hash_stb] | [`belt‑hash`] | [![crates.io](https://img.shields.io/crates/v/bash-hash.svg)](https://crates.io/crates/bash-hash) | [![Documentation](https://docs.rs/bash-hash/badge.svg)](https://docs.rs/bash-hash) | 1.85 | :green_heart: | | [BelT] hash | [`belt‑hash`] | [![crates.io](https://img.shields.io/crates/v/belt-hash.svg)](https://crates.io/crates/belt-hash) | [![Documentation](https://docs.rs/belt-hash/badge.svg)](https://docs.rs/belt-hash) | 1.85 | :green_heart: | | [BLAKE2] | [`blake2`] | [![crates.io](https://img.shields.io/crates/v/blake2.svg)](https://crates.io/crates/blake2) | [![Documentation](https://docs.rs/blake2/badge.svg)](https://docs.rs/blake2) | 1.85 | :green_heart: | | [FSB] | [`fsb`] | [![crates.io](https://img.shields.io/crates/v/fsb.svg)](https://crates.io/crates/fsb) | [![Documentation](https://docs.rs/fsb/badge.svg)](https://docs.rs/fsb) | 1.85 | :green_heart: | @@ -235,6 +236,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (crates) [`ascon‑hash`]: ./ascon-hash +[`bash‑hash`]: ./bash-hash [`belt‑hash`]: ./belt-hash [`blake2`]: ./blake2 [`fsb`]: ./fsb @@ -278,6 +280,7 @@ Unless you explicitly state otherwise, any contribution intentionally submitted [//]: # (algorithms) [Ascon]: https://ascon.iaik.tugraz.at +[bash_hash_stb]: https://apmi.bsu.by/assets/files/std/bash-spec241.pdf [BelT]: https://ru.wikipedia.org/wiki/BelT [BLAKE2]: https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2 [FSB]: https://en.wikipedia.org/wiki/Fast_syndrome-based_hash diff --git a/bash-hash/Cargo.toml b/bash-hash/Cargo.toml index edfe02b6..984d54a7 100644 --- a/bash-hash/Cargo.toml +++ b/bash-hash/Cargo.toml @@ -14,7 +14,7 @@ categories = ["cryptography", "no-std"] [dependencies] digest = "0.11.0-rc.3" -bash-f = { version = "0.1.0" } +bash-f = "0.1" [dev-dependencies] digest = { version = "0.11.0-rc.3", features = ["dev"] } diff --git a/bash-hash/README.md b/bash-hash/README.md index 3fd56ed6..c2b42996 100644 --- a/bash-hash/README.md +++ b/bash-hash/README.md @@ -7,7 +7,7 @@ ![Rust Version][rustc-image] [![Project Chat][chat-image]][chat-link] -Pure Rust implementation of the bash hash function specified in [STB 34.101.31-2020]. +Pure Rust implementation of the bash hash function specified in [STB 34.101.77-2020]. ## Examples ```rust diff --git a/bash-hash/src/block_api.rs b/bash-hash/src/block_api.rs index 68e96d2a..6f7c6453 100644 --- a/bash-hash/src/block_api.rs +++ b/bash-hash/src/block_api.rs @@ -1,7 +1,6 @@ -use core::fmt; +use core::{fmt, marker::PhantomData}; use digest::{ HashMarker, Output, - array::Array, block_api::{ AlgorithmName, Block, BlockSizeUser, Buffer, BufferKindUser, Eager, FixedOutputCore, OutputSizeUser, Reset, UpdateCore, @@ -10,48 +9,33 @@ use digest::{ typenum::U192, }; -use crate::variants::{Bash256, Bash384, Bash512, Variant}; +use crate::OutputSize; use bash_f::{STATE_WORDS, bash_f}; -use digest::typenum::Unsigned; -/// Core Bash hasher state with generic security level. +/// Core `bash-hash` hasher generic over output size. /// -/// Implements bash-hash[ℓ] algorithm according to section 7 of STB 34.101.77-2020. -/// Parameters: -/// - BlockSize: block size r = (1536 - 4ℓ) / 8 bytes -/// - OutputSize: output size 2ℓ / 8 bytes -#[derive(Clone)] -pub struct BashHashCore { +/// Specified in Section 7 of STB 34.101.77-2020. +pub struct BashHashCore { state: [u64; STATE_WORDS], - _variant: core::marker::PhantomData, + _pd: PhantomData, } -impl BashHashCore -where - V: Variant, -{ - /// Calculate security level ℓ - /// - /// According to section 5.3: ℓ = OutputSize * 8 / 2 = OutputSize * 4 +impl Clone for BashHashCore { #[inline] - const fn get_level() -> usize { - // 3. ℓ ← OutSize * 8 / 2 - V::OutputSize::USIZE * 4 - } - - /// Calculate buffer size r in bytes - #[inline] - const fn get_r_bytes() -> usize { - V::BlockSize::USIZE + fn clone(&self) -> Self { + Self { + state: self.state, + _pd: PhantomData, + } } +} +impl BashHashCore { /// Compress one data block fn compress_block(&mut self, block: &Block) { - let r_bytes = Self::get_r_bytes(); - debug_assert_eq!(r_bytes % 8, 0); - // 4.1: S[...1536 - 4ℓ) ← Xi - for (dst, chunk) in self.state.iter_mut().zip(block[..r_bytes].chunks_exact(8)) { + // TODO: use `as_chunks` after MSRV is bumped to 1.88+ + for (dst, chunk) in self.state.iter_mut().zip(block.chunks_exact(8)) { // `chunk` is guaranteed to be 8 bytes long due to `r_bytes` being a multiple of 8 *dst = u64::from_le_bytes(chunk.try_into().unwrap()); } @@ -61,33 +45,21 @@ where } } -impl HashMarker for BashHashCore where V: Variant {} +impl HashMarker for BashHashCore {} -impl BlockSizeUser for BashHashCore -where - V: Variant, -{ - type BlockSize = V::BlockSize; +impl BlockSizeUser for BashHashCore { + type BlockSize = OS::BlockSize; } -impl BufferKindUser for BashHashCore -where - V: Variant, -{ +impl BufferKindUser for BashHashCore { type BufferKind = Eager; } -impl OutputSizeUser for BashHashCore -where - V: Variant, -{ - type OutputSize = V::OutputSize; +impl OutputSizeUser for BashHashCore { + type OutputSize = OS; } -impl UpdateCore for BashHashCore -where - V: Variant, -{ +impl UpdateCore for BashHashCore { #[inline] fn update_blocks(&mut self, blocks: &[Block]) { for block in blocks { @@ -96,85 +68,62 @@ where } } -impl FixedOutputCore for BashHashCore -where - V: Variant, -{ +impl FixedOutputCore for BashHashCore { fn finalize_fixed_core(&mut self, buffer: &mut Buffer, out: &mut Output) { - let pos = buffer.get_pos(); - // 1. Split(X || 01, r) - split message with appended 01 // 2: Xn ← Xn || 0^(1536-4ℓ-|Xn|) - pad last block with zeros - let mut padding_block = Array::::default(); - let block = buffer.pad_with_zeros(); - padding_block.copy_from_slice(&block); - padding_block[pos] = 0x40; + let pos = buffer.get_pos(); + let mut block = buffer.pad_with_zeros(); + block[pos] = 0x40; // 4. for i = 1, 2, ..., n, do: - self.compress_block(&padding_block); - - //5. Y ← S[...2ℓ) - self.state - .iter() - .flat_map(|w| w.to_le_bytes()) - .take(V::OutputSize::USIZE) - .zip(out.iter_mut()) - .for_each(|(src, dst)| *dst = src); + self.compress_block(&block); + + // 5. Y ← S[...2ℓ) + // TODO: use `as_chunks` after MSRV is bumped to 1.88+ + for (src, dst) in self.state.iter().zip(out.chunks_exact_mut(8)) { + dst.copy_from_slice(&src.to_le_bytes()); + } } } -impl Default for BashHashCore -where - V: Variant, -{ +impl Default for BashHashCore { #[inline] fn default() -> Self { let mut state = [0u64; STATE_WORDS]; + // 3. ℓ ← OutSize * 8 / 2 + let level = OS::USIZE * 4; // 3. S ← 0^1472 || ⟨ℓ/4⟩_64 - let level = Self::get_level(); state[23] = (level / 4) as u64; Self { state, - _variant: core::marker::PhantomData, + _pd: PhantomData, } } } -impl Reset for BashHashCore -where - V: Variant, -{ +impl Reset for BashHashCore { #[inline] fn reset(&mut self) { *self = Default::default(); } } -impl AlgorithmName for BashHashCore -where - V: Variant, -{ +impl AlgorithmName for BashHashCore { fn write_alg_name(f: &mut fmt::Formatter<'_>) -> fmt::Result { - let level = Self::get_level(); - write!(f, "Bash{}", level * 2) + write!(f, "BashHash{}", OS::USIZE) } } -impl fmt::Debug for BashHashCore -where - V: Variant, -{ +impl fmt::Debug for BashHashCore { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str("BashHashCore { ... }") } } -impl Drop for BashHashCore -where - V: Variant, -{ +impl Drop for BashHashCore { fn drop(&mut self) { #[cfg(feature = "zeroize")] { @@ -185,48 +134,31 @@ where } #[cfg(feature = "zeroize")] -impl digest::zeroize::ZeroizeOnDrop for BashHashCore where V: Variant {} +impl digest::zeroize::ZeroizeOnDrop for BashHashCore {} -impl SerializableState for BashHashCore -where - V: Variant, -{ +impl SerializableState for BashHashCore { type SerializedStateSize = U192; fn serialize(&self) -> SerializedState { - let mut dst = SerializedState::::default(); - - for (word, chunk) in self.state.iter().zip(dst.chunks_exact_mut(8)) { - // `word` is guaranteed to be 8 bytes long due to `STATE_WORDS` being a multiple of 8 - // and `chunk` being a slice of 8 bytes - chunk.copy_from_slice(&word.to_le_bytes()); + let mut res = SerializedState::::default(); + // TODO: use `as_chunks` after MSRV is bumped to 1.88+ + for (src, dst) in self.state.iter().zip(res.chunks_exact_mut(8)) { + dst.copy_from_slice(&src.to_le_bytes()); } - - dst + res } fn deserialize( serialized_state: &SerializedState, ) -> Result { let mut state = [0u64; STATE_WORDS]; - - for (dst, chunk) in state.iter_mut().zip(serialized_state.chunks_exact(8)) { - // `chunk` is guaranteed to be 8 bytes long due to `STATE_WORDS` being a multiple of 8 - // and `dst` being a slice of 8 bytes - *dst = u64::from_le_bytes(chunk.try_into().map_err(|_| DeserializeStateError)?); + // TODO: use `as_chunks` after MSRV is bumped to 1.88+ + for (src, dst) in serialized_state.chunks_exact(8).zip(state.iter_mut()) { + *dst = u64::from_le_bytes(src.try_into().unwrap()); } - Ok(Self { state, - _variant: core::marker::PhantomData, + _pd: PhantomData, }) } } - -// Standard Bash hash variants according to section 5.3 and 7.1 -// Bash256: ℓ = 128, output = 2ℓ = 256 bits, block = (1536 - 4×128)/8 = 128 bytes -// Bash384: ℓ = 192, output = 2ℓ = 384 bits, block = (1536 - 4×192)/8 = 96 bytes -// Bash512: ℓ = 256, output = 2ℓ = 512 bits, block = (1536 - 4×256)/8 = 64 bytes -pub(crate) type Bash256Core = BashHashCore; -pub(crate) type Bash384Core = BashHashCore; -pub(crate) type Bash512Core = BashHashCore; diff --git a/bash-hash/src/lib.rs b/bash-hash/src/lib.rs index 88aa243f..ade823ca 100644 --- a/bash-hash/src/lib.rs +++ b/bash-hash/src/lib.rs @@ -8,29 +8,30 @@ #![warn(missing_docs, unreachable_pub)] #![forbid(unsafe_code)] +use digest::typenum::{U32, U48, U64}; pub use digest::{self, Digest}; /// Block-level types pub mod block_api; +#[cfg(feature = "oid")] +mod oids; +mod serialize; mod variants; -digest::buffer_fixed!( - /// BASH256 hasher state. - pub struct BashHash256(block_api::Bash256Core); - oid: "1.2.112.0.2.0.34.101.77.11"; - impl: FixedHashTraits; -); +pub use variants::OutputSize; digest::buffer_fixed!( - /// BASH384 hasher state. - pub struct BashHash384(block_api::Bash384Core); - oid: "1.2.112.0.2.0.34.101.77.12"; - impl: FixedHashTraits; + /// `bash-hash` hasher state generic over output size. + pub struct BashHash(block_api::BashHashCore); + // note: `SerializableState` is implemented in the `serialize` module + // to work around issues with complex trait bounds + impl: BaseFixedTraits AlgorithmName Default Clone HashMarker + Reset FixedOutputReset ZeroizeOnDrop; ); -digest::buffer_fixed!( - /// BASH512 hasher state. - pub struct BashHash512(block_api::Bash512Core); - oid: "1.2.112.0.2.0.34.101.77.13"; - impl: FixedHashTraits; -); +/// `bash-hash-256` hasher state. +pub type BashHash256 = BashHash; +/// `bash-hash-384` hasher state. +pub type BashHash384 = BashHash; +/// `bash-hash-512` hasher state. +pub type BashHash512 = BashHash; diff --git a/bash-hash/src/oids.rs b/bash-hash/src/oids.rs new file mode 100644 index 00000000..80b1a747 --- /dev/null +++ b/bash-hash/src/oids.rs @@ -0,0 +1,13 @@ +use digest::const_oid::{AssociatedOid, ObjectIdentifier}; + +impl AssociatedOid for super::BashHash256 { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.11"); +} + +impl AssociatedOid for super::BashHash384 { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.12"); +} + +impl AssociatedOid for super::BashHash512 { + const OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.112.0.2.0.34.101.77.13"); +} diff --git a/bash-hash/src/serialize.rs b/bash-hash/src/serialize.rs new file mode 100644 index 00000000..0b829223 --- /dev/null +++ b/bash-hash/src/serialize.rs @@ -0,0 +1,40 @@ +use crate::{BashHash, OutputSize}; +use core::ops::Add; +use digest::{ + array::ArraySize, + block_buffer::BlockBuffer, + crypto_common::hazmat::{DeserializeStateError, SerializableState, SerializedState}, + typenum::{Sum, U0, U192}, +}; + +impl SerializableState for BashHash +where + U192: Add, + OS::BlockSize: Add, + Sum: ArraySize, + Sum: ArraySize, +{ + type SerializedStateSize = Sum; + + #[inline] + fn serialize(&self) -> SerializedState { + let mut res = SerializedState::::default(); + let (core_dst, buf_dst) = res.split_at_mut(192); + core_dst.copy_from_slice(&self.core.serialize()); + buf_dst.copy_from_slice(&self.buffer.serialize()); + res + } + + #[inline] + fn deserialize( + serialized_state: &SerializedState, + ) -> Result { + let (serialized_core, serialized_buf) = serialized_state.split_at(192); + + let core = SerializableState::deserialize(serialized_core.try_into().unwrap())?; + let buffer = BlockBuffer::deserialize(serialized_buf.try_into().unwrap()) + .map_err(|_| DeserializeStateError)?; + + Ok(Self { core, buffer }) + } +} diff --git a/bash-hash/src/variants.rs b/bash-hash/src/variants.rs index 53a68912..4b4bd1dc 100644 --- a/bash-hash/src/variants.rs +++ b/bash-hash/src/variants.rs @@ -1,43 +1,43 @@ -use digest::{ - array::ArraySize, - crypto_common::BlockSizes, - typenum::{U32, U48, U64, U96, U128}, -}; +use digest::{array::ArraySize, crypto_common::BlockSizes, typenum}; /// Sealed trait to prevent external implementations. -pub trait Sealed: Clone {} +pub trait Sealed {} -/// Trait for Bash hash variants. -pub trait Variant: Sealed { - type BlockSize: ArraySize + BlockSizes; - type OutputSize: ArraySize; +/// Trait implemented for output sizes supported by `bash-hash`. +/// +/// Supported output sizes form the following list: U4, U8, ..., U60, U64. +pub trait OutputSize: ArraySize + Sealed { + /// Block size in bytes computed as `192 - 2 * OutputSize`. + type BlockSize: BlockSizes; } -#[derive(Clone)] -/// `Bash256` variant with 256-bit output and 128-byte block size. -pub struct Bash256; -#[derive(Clone)] -/// `Bash384` variant with 384-bit output and 96-byte block size. -pub struct Bash384; -#[derive(Clone)] -/// `Bash512` variant with 512-bit output and 64-byte block size. -pub struct Bash512; +macro_rules! impl_sizes { + ($($variant:ident, $block_size:ident;)*) => { + $( + impl Sealed for typenum::$variant {} -impl Sealed for Bash256 {} -impl Sealed for Bash384 {} -impl Sealed for Bash512 {} - -impl Variant for Bash256 { - type BlockSize = U128; - type OutputSize = U32; -} - -impl Variant for Bash384 { - type BlockSize = U96; - type OutputSize = U48; + impl OutputSize for typenum::$variant { + type BlockSize = typenum::$block_size; + } + )* + }; } -impl Variant for Bash512 { - type BlockSize = U64; - type OutputSize = U64; -} +impl_sizes!( + U4, U184; + U8, U176; + U12, U168; + U16, U160; + U20, U152; + U24, U144; + U28, U136; + U32, U128; + U36, U120; + U40, U112; + U44, U104; + U48, U96; + U52, U88; + U56, U80; + U60, U72; + U64, U64; +);