mirror of
https://github.com/RustCrypto/hashes.git
synced 2026-01-25 04:18:20 +00:00
bash-hash: minor refactor (#746)
This commit is contained in:
@@ -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`] | [](https://crates.io/crates/ascon-hash) | [](https://docs.rs/ascon-hash) | 1.85 | :green_heart: |
|
||||
| [`bash-hash`][bash_hash_stb] | [`belt‑hash`] | [](https://crates.io/crates/bash-hash) | [](https://docs.rs/bash-hash) | 1.85 | :green_heart: |
|
||||
| [BelT] hash | [`belt‑hash`] | [](https://crates.io/crates/belt-hash) | [](https://docs.rs/belt-hash) | 1.85 | :green_heart: |
|
||||
| [BLAKE2] | [`blake2`] | [](https://crates.io/crates/blake2) | [](https://docs.rs/blake2) | 1.85 | :green_heart: |
|
||||
| [FSB] | [`fsb`] | [](https://crates.io/crates/fsb) | [](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
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<V: Variant> {
|
||||
/// Specified in Section 7 of STB 34.101.77-2020.
|
||||
pub struct BashHashCore<OS: OutputSize> {
|
||||
state: [u64; STATE_WORDS],
|
||||
_variant: core::marker::PhantomData<V>,
|
||||
_pd: PhantomData<OS>,
|
||||
}
|
||||
|
||||
impl<V> BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
/// Calculate security level ℓ
|
||||
///
|
||||
/// According to section 5.3: ℓ = OutputSize * 8 / 2 = OutputSize * 4
|
||||
impl<OS: OutputSize> Clone for BashHashCore<OS> {
|
||||
#[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<OS: OutputSize> BashHashCore<OS> {
|
||||
/// Compress one data block
|
||||
fn compress_block(&mut self, block: &Block<Self>) {
|
||||
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<V> HashMarker for BashHashCore<V> where V: Variant {}
|
||||
impl<OS: OutputSize> HashMarker for BashHashCore<OS> {}
|
||||
|
||||
impl<V> BlockSizeUser for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
type BlockSize = V::BlockSize;
|
||||
impl<OS: OutputSize> BlockSizeUser for BashHashCore<OS> {
|
||||
type BlockSize = OS::BlockSize;
|
||||
}
|
||||
|
||||
impl<V> BufferKindUser for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> BufferKindUser for BashHashCore<OS> {
|
||||
type BufferKind = Eager;
|
||||
}
|
||||
|
||||
impl<V> OutputSizeUser for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
type OutputSize = V::OutputSize;
|
||||
impl<OS: OutputSize> OutputSizeUser for BashHashCore<OS> {
|
||||
type OutputSize = OS;
|
||||
}
|
||||
|
||||
impl<V> UpdateCore for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> UpdateCore for BashHashCore<OS> {
|
||||
#[inline]
|
||||
fn update_blocks(&mut self, blocks: &[Block<Self>]) {
|
||||
for block in blocks {
|
||||
@@ -96,85 +68,62 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> FixedOutputCore for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> FixedOutputCore for BashHashCore<OS> {
|
||||
fn finalize_fixed_core(&mut self, buffer: &mut Buffer<Self>, out: &mut Output<Self>) {
|
||||
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::<u8, V::BlockSize>::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);
|
||||
self.compress_block(&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);
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Default for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self {
|
||||
let mut state = [0u64; STATE_WORDS];
|
||||
|
||||
// 3. S ← 0^1472 || ⟨ℓ/4⟩_64
|
||||
let level = Self::get_level();
|
||||
state[23] = (level / 4) as u64;
|
||||
|
||||
Self {
|
||||
state,
|
||||
_variant: core::marker::PhantomData,
|
||||
// 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<V> Reset for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> Default for BashHashCore<OS> {
|
||||
#[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
|
||||
state[23] = (level / 4) as u64;
|
||||
|
||||
Self {
|
||||
state,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<OS: OutputSize> Reset for BashHashCore<OS> {
|
||||
#[inline]
|
||||
fn reset(&mut self) {
|
||||
*self = Default::default();
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> AlgorithmName for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> AlgorithmName for BashHashCore<OS> {
|
||||
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<V> fmt::Debug for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> fmt::Debug for BashHashCore<OS> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("BashHashCore { ... }")
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> Drop for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> Drop for BashHashCore<OS> {
|
||||
fn drop(&mut self) {
|
||||
#[cfg(feature = "zeroize")]
|
||||
{
|
||||
@@ -185,48 +134,31 @@ where
|
||||
}
|
||||
|
||||
#[cfg(feature = "zeroize")]
|
||||
impl<V> digest::zeroize::ZeroizeOnDrop for BashHashCore<V> where V: Variant {}
|
||||
impl<OS: OutputSize> digest::zeroize::ZeroizeOnDrop for BashHashCore<OS> {}
|
||||
|
||||
impl<V> SerializableState for BashHashCore<V>
|
||||
where
|
||||
V: Variant,
|
||||
{
|
||||
impl<OS: OutputSize> SerializableState for BashHashCore<OS> {
|
||||
type SerializedStateSize = U192;
|
||||
|
||||
fn serialize(&self) -> SerializedState<Self> {
|
||||
let mut dst = SerializedState::<Self>::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::<Self>::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<Self>,
|
||||
) -> Result<Self, DeserializeStateError> {
|
||||
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<Bash256>;
|
||||
pub(crate) type Bash384Core = BashHashCore<Bash384>;
|
||||
pub(crate) type Bash512Core = BashHashCore<Bash512>;
|
||||
|
||||
@@ -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<OS: OutputSize>(block_api::BashHashCore<OS>);
|
||||
// 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<U32>;
|
||||
/// `bash-hash-384` hasher state.
|
||||
pub type BashHash384 = BashHash<U48>;
|
||||
/// `bash-hash-512` hasher state.
|
||||
pub type BashHash512 = BashHash<U64>;
|
||||
|
||||
13
bash-hash/src/oids.rs
Normal file
13
bash-hash/src/oids.rs
Normal file
@@ -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");
|
||||
}
|
||||
40
bash-hash/src/serialize.rs
Normal file
40
bash-hash/src/serialize.rs
Normal file
@@ -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<OS: OutputSize> SerializableState for BashHash<OS>
|
||||
where
|
||||
U192: Add<OS::BlockSize>,
|
||||
OS::BlockSize: Add<U0>,
|
||||
Sum<U192, OS::BlockSize>: ArraySize,
|
||||
Sum<OS::BlockSize, U0>: ArraySize,
|
||||
{
|
||||
type SerializedStateSize = Sum<U192, OS::BlockSize>;
|
||||
|
||||
#[inline]
|
||||
fn serialize(&self) -> SerializedState<Self> {
|
||||
let mut res = SerializedState::<Self>::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<Self>,
|
||||
) -> Result<Self, DeserializeStateError> {
|
||||
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 })
|
||||
}
|
||||
}
|
||||
@@ -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 OutputSize for typenum::$variant {
|
||||
type BlockSize = typenum::$block_size;
|
||||
}
|
||||
)*
|
||||
};
|
||||
}
|
||||
|
||||
impl Variant for Bash384 {
|
||||
type BlockSize = U96;
|
||||
type OutputSize = U48;
|
||||
}
|
||||
|
||||
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;
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user