use alloc::string::String;
const H0: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
const K: [u32; 4] = [0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6];
#[derive(Clone)]
pub struct Sha1 {
h: [u32; 5],
block: [u8; 64],
used: usize,
len: u64,
}
impl Default for Sha1 {
fn default() -> Self {
Self::new()
}
}
impl Sha1 {
pub fn new() -> Self {
Self {
h: H0,
block: [0u8; 64],
used: 0,
len: 0,
}
}
pub fn update(&mut self, data: &[u8]) {
self.len = self.len.wrapping_add(data.len() as u64);
let mut rest = data;
if self.used > 0 {
let want = 64 - self.used;
let take = core::cmp::min(want, rest.len());
self.block[self.used..self.used + take].copy_from_slice(&rest[..take]);
self.used += take;
rest = &rest[take..];
if self.used == 64 {
let block = self.block;
self.compress(&block);
self.used = 0;
}
}
let mut chunks = rest.chunks_exact(64);
for chunk in &mut chunks {
let mut block = [0u8; 64];
block.copy_from_slice(chunk);
self.compress(&block);
}
let tail = chunks.remainder();
if !tail.is_empty() {
self.block[..tail.len()].copy_from_slice(tail);
self.used = tail.len();
}
}
pub fn finish(mut self) -> [u8; 20] {
let bit_len = self.len.wrapping_mul(8);
self.block[self.used] = 0x80;
self.used += 1;
if self.used > 56 {
for b in &mut self.block[self.used..] {
*b = 0;
}
let block = self.block;
self.compress(&block);
self.used = 0;
}
for b in &mut self.block[self.used..56] {
*b = 0;
}
self.block[56..].copy_from_slice(&bit_len.to_be_bytes());
let block = self.block;
self.compress(&block);
let mut out = [0u8; 20];
for (i, word) in self.h.iter().enumerate() {
out[i * 4..i * 4 + 4].copy_from_slice(&word.to_be_bytes());
}
out
}
fn compress(&mut self, block: &[u8; 64]) {
let mut w = [0u32; 80];
for (i, word) in w.iter_mut().enumerate().take(16) {
*word = u32::from_be_bytes([
block[i * 4],
block[i * 4 + 1],
block[i * 4 + 2],
block[i * 4 + 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, mut b, mut c, mut d, mut e) =
(self.h[0], self.h[1], self.h[2], self.h[3], self.h[4]);
for (i, &wi) in w.iter().enumerate() {
let (f, k) = match i / 20 {
0 => ((b & c) | ((!b) & d), K[0]),
1 => (b ^ c ^ d, K[1]),
2 => ((b & c) | (b & d) | (c & d), K[2]),
_ => (b ^ c ^ d, K[3]),
};
let tmp = 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 = tmp;
}
self.h[0] = self.h[0].wrapping_add(a);
self.h[1] = self.h[1].wrapping_add(b);
self.h[2] = self.h[2].wrapping_add(c);
self.h[3] = self.h[3].wrapping_add(d);
self.h[4] = self.h[4].wrapping_add(e);
}
}
#[inline]
pub fn sha1(data: &[u8]) -> [u8; 20] {
let mut h = Sha1::new();
h.update(data);
h.finish()
}
pub fn sha1_hex(data: &[u8]) -> String {
let digest = sha1(data);
let mut out = String::with_capacity(40);
const HEX: &[u8; 16] = b"0123456789abcdef";
for b in digest {
out.push(HEX[(b >> 4) as usize] as char);
out.push(HEX[(b & 0x0f) as usize] as char);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec::Vec;
#[test]
fn fips_vectors() {
assert_eq!(sha1_hex(b""), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
assert_eq!(sha1_hex(b"abc"), "a9993e364706816aba3e25717850c26c9cd0d89d");
assert_eq!(
sha1_hex(b"abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"),
"84983e441c3bd26ebaae4aa1f95129e5e54670f1"
);
}
#[test]
fn million_a_vector() {
let mut h = Sha1::new();
let chunk = [b'a'; 1000];
for _ in 0..1000 {
h.update(&chunk);
}
let digest = h.finish();
assert_eq!(hex(&digest), "34aa973cd4c4daa4f61eeb2bdbad27316534016f");
}
#[test]
fn streaming_matches_one_shot_at_every_boundary() {
let data: Vec<u8> = (0u16..300).map(|i| (i % 251) as u8).collect();
let want = sha1(&data);
for split in 0..=data.len() {
let mut h = Sha1::new();
h.update(&data[..split]);
h.update(&data[split..]);
assert_eq!(h.finish(), want, "split at {split}");
}
}
#[test]
fn websocket_accept_rfc_vector() {
let key = b"dGhlIHNhbXBsZSBub25jZQ==";
let mut h = Sha1::new();
h.update(key);
h.update(b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
let digest = h.finish();
assert_eq!(hex(&digest), "b37a4f2cc0624f1690f64606cf385945b2bec4ea");
}
fn hex(b: &[u8]) -> String {
let mut s = String::new();
for x in b {
s.push_str(&format!("{x:02x}"));
}
s
}
}