use super::{BLOCK, BlockBuffer};
#[derive(Clone)]
pub struct Sha1 {
state: [u32; 5],
buffer: BlockBuffer,
}
impl Sha1 {
pub const LEN: usize = 20;
#[must_use]
pub const fn new() -> Self {
Self {
state: [
0x6745_2301,
0xefcd_ab89,
0x98ba_dcfe,
0x1032_5476,
0xc3d2_e1f0,
],
buffer: BlockBuffer::new(),
}
}
#[must_use]
pub fn digest(data: &[u8]) -> [u8; Self::LEN] {
let mut hasher = Self::new();
hasher.update(data);
hasher.finalize()
}
pub fn update(&mut self, data: &[u8]) {
let state = &mut self.state;
self.buffer
.update(data, &mut |block| compress(state, block));
}
#[must_use]
pub fn finalize(mut self) -> [u8; Self::LEN] {
let length = self.buffer.bit_length().to_be_bytes();
let state = &mut self.state;
self.buffer
.finish(length, &mut |block| compress(state, block));
let mut out = [0u8; Self::LEN];
for (dst, word) in out.as_chunks_mut::<4>().0.iter_mut().zip(self.state) {
*dst = word.to_be_bytes();
}
out
}
}
impl Default for Sha1 {
fn default() -> Self {
Self::new()
}
}
macro_rules! rounds {
($w:expr, $k:expr, [$a:ident, $b:ident, $c:ident, $d:ident, $e:ident], $f:expr) => {
for &word in $w {
let temp = $a
.rotate_left(5)
.wrapping_add($f)
.wrapping_add($e)
.wrapping_add($k)
.wrapping_add(word);
$e = $d;
$d = $c;
$c = $b.rotate_left(30);
$b = $a;
$a = temp;
}
};
}
fn compress(state: &mut [u32; 5], block: &[u8; BLOCK]) {
let mut w = [0u32; 80];
for (word, bytes) in w.iter_mut().zip(block.as_chunks::<4>().0) {
*word = u32::from_be_bytes(*bytes);
}
for i in 16..80 {
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
}
let [mut a, mut b, mut c, mut d, mut e] = *state;
rounds!(&w[..20], 0x5a82_7999u32, [a, b, c, d, e], d ^ (b & (c ^ d)));
rounds!(&w[20..40], 0x6ed9_eba1u32, [a, b, c, d, e], b ^ c ^ d);
rounds!(
&w[40..60],
0x8f1b_bcdcu32,
[a, b, c, d, e],
(b & c) | (d & (b | c))
);
rounds!(&w[60..], 0xca62_c1d6u32, [a, b, c, d, e], b ^ c ^ d);
for (st, v) in state.iter_mut().zip([a, b, c, d, e]) {
*st = st.wrapping_add(v);
}
}
#[cfg(test)]
mod tests {
use super::Sha1;
fn hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{b:02x}")).collect()
}
#[test]
fn fips180_vectors() {
let vectors: &[(&[u8], &str)] = &[
(b"", "da39a3ee5e6b4b0d3255bfef95601890afd80709"),
(b"abc", "a9993e364706816aba3e25717850c26c9cd0d89d"),
(
b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
"84983e441c3bd26ebaae4aa1f95129e5e54670f1",
),
(
b"The quick brown fox jumps over the lazy dog",
"2fd4e1c67a2d28fced849ee1bb76e7391b93eb12",
),
];
for (input, expected) in vectors {
assert_eq!(hex(&Sha1::digest(input)), *expected);
}
}
#[test]
fn million_a() {
let mut hasher = Sha1::new();
let data = vec![b'a'; 1_000_000];
let mut rest = &data[..];
let mut step = 1;
while !rest.is_empty() {
let n = step.min(rest.len());
hasher.update(&rest[..n]);
rest = &rest[n..];
step = step * 3 + 1;
}
assert_eq!(
hex(&hasher.finalize()),
"34aa973cd4c4daa4f61eeb2bdbad27316534016f"
);
}
#[test]
fn padding_boundaries_match_one_shot() {
let data: Vec<u8> = (0..300u32)
.map(|i| (i.wrapping_mul(17) % 253) as u8)
.collect();
for len in [55, 56, 57, 63, 64, 65, 119, 120, 121, 128, 300] {
let one_shot = Sha1::digest(&data[..len]);
for split in [0, 1, 9, 56, 64, len / 2, len] {
let mut hasher = Sha1::new();
hasher.update(&data[..split.min(len)]);
hasher.update(&data[split.min(len)..len]);
assert_eq!(hasher.finalize(), one_shot, "len {len} split {split}");
}
}
}
}