#![allow(clippy::indexing_slicing)]
use self::kernels::CompressBlocksFn;
use crate::{
hashes::crypto::dispatch_util::{SizeClassDispatch, len_hint_from_u64},
traits::Digest,
};
#[doc(hidden)]
pub(crate) mod dispatch;
#[doc(hidden)]
pub(crate) mod dispatch_tables;
pub(crate) mod kernels;
const BLOCK_LEN: usize = 64;
const H0: [u32; 8] = [
0xc105_9ed8,
0x367c_d507,
0x3070_dd17,
0xf70e_5939,
0xffc0_0b31,
0x6858_1511,
0x64f9_8fa7,
0xbefa_4fa4,
];
const MAX_MESSAGE_LEN: u64 = u64::MAX / 8;
#[derive(Clone)]
pub struct Sha224 {
state: [u32; 8],
block: [u8; BLOCK_LEN],
block_len: usize,
bytes_hashed: u64,
compress_blocks: CompressBlocksFn,
dispatch: Option<SizeClassDispatch<CompressBlocksFn>>,
}
impl core::fmt::Debug for Sha224 {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Sha224").finish_non_exhaustive()
}
}
impl Default for Sha224 {
#[inline]
fn default() -> Self {
use crate::hashes::crypto::sha256::kernels as sha256k;
Self {
state: H0,
block: [0u8; BLOCK_LEN],
block_len: 0,
bytes_hashed: 0,
compress_blocks: sha256k::compile_time_best(),
dispatch: None,
}
}
}
impl Sha224 {
#[inline]
#[must_use]
pub fn digest(data: &[u8]) -> [u8; 28] {
dispatch::digest(data)
}
#[inline]
fn select_compress(&mut self, incoming_len: usize) -> CompressBlocksFn {
let dispatch = match self.dispatch {
Some(d) => d,
None => {
let d = dispatch::compress_dispatch();
self.dispatch = Some(d);
d
}
};
let total = self
.bytes_hashed
.strict_add(self.block_len as u64)
.strict_add(incoming_len as u64);
let compress = dispatch.select(len_hint_from_u64(total));
self.compress_blocks = compress;
compress
}
#[inline]
fn update_with(&mut self, mut data: &[u8], compress_blocks: CompressBlocksFn) {
if data.is_empty() {
return;
}
debug_assert!(
self
.bytes_hashed
.strict_add(self.block_len as u64)
.strict_add(data.len() as u64)
<= MAX_MESSAGE_LEN,
"SHA-224: total input exceeds FIPS 180-4 maximum of 2^61 − 1 bytes"
);
if self.block_len != 0 {
let take = core::cmp::min(BLOCK_LEN.strict_sub(self.block_len), data.len());
self.block[self.block_len..self.block_len.strict_add(take)].copy_from_slice(&data[..take]);
self.block_len = self.block_len.strict_add(take);
data = &data[take..];
if self.block_len == BLOCK_LEN {
compress_blocks(&mut self.state, &self.block);
self.bytes_hashed = self.bytes_hashed.strict_add(BLOCK_LEN as u64);
self.block_len = 0;
}
}
let full_len = data.len().strict_sub(data.len() % BLOCK_LEN);
if full_len != 0 {
let (blocks, rest) = data.split_at(full_len);
compress_blocks(&mut self.state, blocks);
self.bytes_hashed = self.bytes_hashed.strict_add(blocks.len() as u64);
data = rest;
}
if !data.is_empty() {
self.block[..data.len()].copy_from_slice(data);
self.block_len = data.len();
}
}
#[inline]
fn finalize_inner_with(&self, compress_blocks: CompressBlocksFn) -> [u8; 28] {
let mut state = self.state;
let mut block = self.block;
let mut block_len = self.block_len;
let total_len = self.bytes_hashed.strict_add(block_len as u64);
block[block_len] = 0x80;
block_len = block_len.strict_add(1);
if block_len > 56 {
block[block_len..].fill(0);
compress_blocks(&mut state, &block);
block = [0u8; BLOCK_LEN];
block_len = 0;
}
block[block_len..56].fill(0);
let bit_len = total_len.strict_mul(8);
block[56..64].copy_from_slice(&bit_len.to_be_bytes());
compress_blocks(&mut state, &block);
let mut out = [0u8; 28];
for (chunk, &word) in out.chunks_exact_mut(4).zip(state.iter()) {
chunk.copy_from_slice(&word.to_be_bytes());
}
out
}
}
impl Drop for Sha224 {
fn drop(&mut self) {
for word in self.state.iter_mut() {
unsafe { core::ptr::write_volatile(word, 0) };
}
crate::traits::ct::zeroize(&mut self.block);
unsafe { core::ptr::write_volatile(&mut self.bytes_hashed, 0) };
unsafe { core::ptr::write_volatile(&mut self.block_len, 0) };
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
}
}
impl Digest for Sha224 {
const OUTPUT_SIZE: usize = 28;
type Output = [u8; 28];
#[inline]
fn new() -> Self {
Self::default()
}
#[inline]
fn update(&mut self, data: &[u8]) {
if data.is_empty() {
return;
}
use crate::hashes::crypto::sha256::kernels as sha256k;
if sha256k::COMPILE_TIME_HW {
self.update_with(data, sha256k::compile_time_best());
return;
}
let compress = self.select_compress(data.len());
self.update_with(data, compress);
}
#[inline]
fn finalize(&self) -> Self::Output {
use crate::hashes::crypto::sha256::kernels as sha256k;
if sha256k::COMPILE_TIME_HW {
return self.finalize_inner_with(sha256k::compile_time_best());
}
self.finalize_inner_with(self.compress_blocks)
}
#[inline]
fn reset(&mut self) {
*self = Self::default();
}
#[inline]
fn digest(data: &[u8]) -> Self::Output {
dispatch::digest(data)
}
}
#[cfg(test)]
mod tests {
use super::Sha224;
fn hex28(bytes: &[u8; 28]) -> alloc::string::String {
use alloc::string::String;
use core::fmt::Write;
let mut s = String::new();
for &b in bytes {
write!(&mut s, "{:02x}", b).unwrap();
}
s
}
#[test]
fn known_vectors() {
assert_eq!(
hex28(&Sha224::digest(b"")),
"d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f"
);
assert_eq!(
hex28(&Sha224::digest(b"abc")),
"23097d223405d8228642a477bda255b32aadbce4bda0b3f7e36c9da7"
);
assert_eq!(
hex28(&Sha224::digest(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
)),
"75388b16512776cc5dba5da1fd890150b0c6455cb4f58b1952522525"
);
#[cfg(not(miri))]
{
let mut million_a = alloc::vec![b'a'; 1_000_000];
assert_eq!(
hex28(&Sha224::digest(&million_a)),
"20794655980c91d8bbb4c1ea97618a4bf03f42581948b2ee4ee7ad67"
);
million_a.clear();
}
}
}