use crate::Digest;
const H0: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
fn compress(state: &mut [u32; 5], block: &[u8]) {
let mut w = [0u32; 80];
for i in 0..16 {
w[i] = u32::from_be_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
}
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 = state[0];
let mut b = state[1];
let mut c = state[2];
let mut d = state[3];
let mut e = state[4];
for (i, &wi) in w.iter().enumerate() {
let (f, k) = match i {
0..=19 => ((b & c) | ((!b) & d), 0x5A827999u32),
20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
_ => (b ^ c ^ d, 0xCA62C1D6u32),
};
let temp = a
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(e)
.wrapping_add(k)
.wrapping_add(wi);
e = d;
d = c;
c = b.rotate_left(30);
b = a;
a = temp;
}
state[0] = state[0].wrapping_add(a);
state[1] = state[1].wrapping_add(b);
state[2] = state[2].wrapping_add(c);
state[3] = state[3].wrapping_add(d);
state[4] = state[4].wrapping_add(e);
}
#[derive(Clone)]
pub struct Sha1 {
state: [u32; 5],
buf: [u8; 64],
buf_len: usize,
total_len: u64,
}
impl Digest for Sha1 {
const OUTPUT_LEN: usize = 20;
const BLOCK_LEN: usize = 64;
fn new() -> Self {
Self {
state: H0,
buf: [0u8; 64],
buf_len: 0,
total_len: 0,
}
}
fn update(&mut self, data: &[u8]) {
let mut pos = 0;
self.total_len += data.len() as u64;
if self.buf_len > 0 {
let take = (64 - self.buf_len).min(data.len());
self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
self.buf_len += take;
pos = take;
if self.buf_len == 64 {
let block = self.buf;
compress(&mut self.state, &block);
self.buf_len = 0;
}
}
while pos + 64 <= data.len() {
compress(&mut self.state, &data[pos..pos + 64]);
pos += 64;
}
if pos < data.len() {
let rem = data.len() - pos;
self.buf[..rem].copy_from_slice(&data[pos..]);
self.buf_len = rem;
}
}
fn finalize(mut self, out: &mut [u8]) {
let bit_len = self.total_len * 8;
let mut pad = [0u8; 72];
pad[0] = 0x80;
let pad_len = if self.buf_len < 56 {
56 - self.buf_len
} else {
120 - self.buf_len
};
self.update(&pad[..pad_len]);
self.update(&bit_len.to_be_bytes());
for (i, word) in self.state.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha1_kat() {
let mut out = [0u8; 20];
Sha1::digest(b"abc", &mut out);
assert_eq!(
out,
[
0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c,
0xd0, 0xd8, 0x9d,
]
);
Sha1::digest(b"", &mut out);
assert_eq!(out[..4], [0xda, 0x39, 0xa3, 0xee]);
}
}