use crate::Hasher;
const H0: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
#[derive(Clone)]
pub struct Sha1 {
state: [u32; 5],
buf: [u8; 64],
buf_len: usize,
total_len: u64,
}
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 in 0..80 {
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(w[i]);
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);
}
impl Hasher 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 need = 64 - self.buf_len;
let take = need.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 remaining = data.len() - pos;
self.buf[..remaining].copy_from_slice(&data[pos..]);
self.buf_len = remaining;
}
}
fn finalize(self) -> Vec<u8> {
let mut out = vec![0u8; 20];
self.finalize_into(&mut out);
out
}
fn finalize_into(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() {
let bytes = word.to_be_bytes();
let start = i * 4;
if start + 4 <= out.len() {
out[start..start + 4].copy_from_slice(&bytes);
} else if start < out.len() {
let end = out.len() - start;
out[start..].copy_from_slice(&bytes[..end]);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Hasher;
#[test]
fn test_sha1_empty() {
let digest = Sha1::hash(b"");
let expected: [u8; 20] = [
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8,
0x07, 0x09,
];
assert_eq!(&digest[..], &expected[..]);
}
#[test]
fn test_sha1_abc() {
let digest = Sha1::hash(b"abc");
let expected: [u8; 20] = [
0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0,
0xd8, 0x9d,
];
assert_eq!(&digest[..], &expected[..]);
}
}