pub const BLOCK_SIZE: usize = 64;
pub const DIGEST_SIZE: usize = 20;
pub const INITIAL_STATE: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
pub const CONSTANTS: [u32; 4] = [0x5A827999, 0x6ED9EBA1, 0x8F1BBCDC, 0xCA62C1D6];
pub const STAGE_ROUNDS: usize = 20;
pub const ROUNDS: usize = STAGE_ROUNDS * CONSTANTS.len();
pub const SCHEDULE: usize = 16;
pub struct Sha1 {
state: [u32; 5],
block: [u8; BLOCK_SIZE],
filled: usize,
length: u64,
}
impl Sha1 {
pub fn new() -> Self {
Self { state: INITIAL_STATE, block: [0; BLOCK_SIZE], filled: 0, length: 0 }
}
pub fn update(&mut self, input: &[u8]) {
self.length = self.length.wrapping_add(input.len() as u64 * 8);
let mut input = input;
if self.filled > 0 {
let taken = (BLOCK_SIZE - self.filled).min(input.len());
self.block[self.filled..self.filled + taken].copy_from_slice(&input[..taken]);
self.filled += taken;
input = &input[taken..];
if self.filled < BLOCK_SIZE {
return;
}
let block = self.block;
self.compress(&block);
self.filled = 0;
}
let mut blocks = input.chunks_exact(BLOCK_SIZE);
for block in &mut blocks {
self.compress(block.try_into().expect("a block is a block"));
}
let rest = blocks.remainder();
self.block[..rest.len()].copy_from_slice(rest);
self.filled = rest.len();
}
#[inline]
pub fn extend(words: &mut [u32; SCHEDULE], round: usize) -> u32 {
if round < SCHEDULE {
return words[round];
}
let slot = round % SCHEDULE;
let extended = (words[(round + 13) % SCHEDULE] ^ words[(round + 8) % SCHEDULE] ^ words[(round + 2) % SCHEDULE] ^ words[slot]).rotate_left(1);
words[slot] = extended;
extended
}
#[inline]
pub fn mixed(stage: usize, b: u32, c: u32, d: u32) -> u32 {
match stage {
0 => d ^ (b & (c ^ d)),
2 => (b & c) | (d & (b | c)),
_ => b ^ c ^ d,
}
}
#[inline]
pub fn round(state: [u32; 5], stage: usize, word: u32) -> [u32; 5] {
let [a, b, c, d, e] = state;
let next = a
.rotate_left(5)
.wrapping_add(Self::mixed(stage, b, c, d))
.wrapping_add(e)
.wrapping_add(word)
.wrapping_add(CONSTANTS[stage]);
[next, a, b.rotate_left(30), c, d]
}
pub fn compress(&mut self, block: &[u8; BLOCK_SIZE]) {
let mut words = [0u32; SCHEDULE];
for (index, word) in words.iter_mut().enumerate() {
*word = u32::from_be_bytes([block[index * 4], block[index * 4 + 1], block[index * 4 + 2], block[index * 4 + 3]]);
}
let mut state = self.state;
for stage in 0..CONSTANTS.len() {
for round in stage * STAGE_ROUNDS..(stage + 1) * STAGE_ROUNDS {
state = Self::round(state, stage, Self::extend(&mut words, round));
}
}
for (held, added) in self.state.iter_mut().zip(state) {
*held = held.wrapping_add(added);
}
}
pub fn finish(mut self) -> [u8; DIGEST_SIZE] {
let length = self.length;
let mut padding = [0u8; BLOCK_SIZE * 2];
padding[0] = 0x80;
let zeroes = (BLOCK_SIZE + BLOCK_SIZE - 8 - (self.filled + 1) % BLOCK_SIZE) % BLOCK_SIZE;
let end = 1 + zeroes;
padding[end..end + 8].copy_from_slice(&length.to_be_bytes());
self.update(&padding[..end + 8]);
let mut digest = [0u8; DIGEST_SIZE];
for (index, word) in self.state.iter().enumerate() {
digest[index * 4..index * 4 + 4].copy_from_slice(&word.to_be_bytes());
}
digest
}
}
impl Default for Sha1 {
fn default() -> Self {
Self::new()
}
}
pub fn sha1(input: &[u8]) -> [u8; DIGEST_SIZE] {
let mut hash = Sha1::new();
hash.update(input);
hash.finish()
}