use alloc::vec::Vec;
#[derive(Copy, Clone)]
struct Sha1 {
h: [u32; 5],
}
#[derive(Copy, Clone)]
pub(crate) enum HashType {
Sha0,
Sha1,
}
impl Sha1 {
const fn apply_chunk(self, chunk: [u8; 64], hash_type: HashType) -> Self {
let mut w = [0_u32; 80];
{
let mut i = 0;
while i < 80 {
if i < 16 {
w[i] = u32::from_be_bytes([
chunk[4 * i],
chunk[4 * i + 1],
chunk[4 * i + 2],
chunk[4 * i + 3],
]);
} else {
let rotate_amount = match hash_type {
HashType::Sha0 => 0,
HashType::Sha1 => 1,
};
w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(rotate_amount);
}
i += 1;
}
}
let mut h = self.h;
{
let mut idx = 0;
while idx < 80 {
let current_w = w[idx];
let (f, k) = match idx {
0..=19 => ((h[1] & h[2]) | ((!h[1]) & h[3]), 0x5a82_7999),
20..=39 => (h[1] ^ h[2] ^ h[3], 0x6ed9_eba1),
40..=59 => ((h[1] & h[2]) | (h[1] & h[3]) | (h[2] & h[3]), 0x8f1b_bcdc),
_ => (h[1] ^ h[2] ^ h[3], 0xca62_c1d6),
};
let temp = h[0]
.rotate_left(5)
.wrapping_add(f)
.wrapping_add(h[4])
.wrapping_add(k)
.wrapping_add(current_w);
h[4] = h[3];
h[3] = h[2];
h[2] = h[1].rotate_left(30);
h[1] = h[0];
h[0] = temp;
idx += 1;
}
}
Self {
h: [
self.h[0].wrapping_add(h[0]),
self.h[1].wrapping_add(h[1]),
self.h[2].wrapping_add(h[2]),
self.h[3].wrapping_add(h[3]),
self.h[4].wrapping_add(h[4]),
],
}
}
const fn hash_from_data(self) -> [u8; 20] {
let h = [
self.h[0].to_be_bytes(),
self.h[1].to_be_bytes(),
self.h[2].to_be_bytes(),
self.h[3].to_be_bytes(),
self.h[4].to_be_bytes(),
];
[
h[0][0], h[0][1], h[0][2], h[0][3], h[1][0], h[1][1], h[1][2], h[1][3], h[2][0],
h[2][1], h[2][2], h[2][3], h[3][0], h[3][1], h[3][2], h[3][3], h[4][0], h[4][1],
h[4][2], h[4][3],
]
}
const fn padding_value_at_idx(input_length: usize, idx: usize) -> u8 {
let padding_length = Self::padding_length_for_input_length(input_length);
if idx == 0 {
0b1000_0000
} else if idx <= padding_length - 9 {
0
} else {
let offset = idx + 8 - padding_length;
let bytes = (input_length as u64).wrapping_mul(8).to_be_bytes();
bytes[offset]
}
}
const fn get_num_chunks(data_length: usize) -> usize {
(data_length + Self::padding_length_for_input_length(data_length)) / 64
}
const fn get_chunk(data: &[u8], data_len: usize, chunk_idx: usize) -> [u8; 64] {
let mut chunk = [0; 64];
let mut i = 0;
while i < 64 {
if chunk_idx * 64 + i < data.len() {
chunk[i] = data[chunk_idx * 64 + i];
} else {
let padding_len = Self::padding_length_for_input_length(data_len);
let index_into_padding = chunk_idx * 64 + i - data.len();
if index_into_padding < padding_len {
chunk[i] = Self::padding_value_at_idx(data_len, index_into_padding);
} else {
panic!("unreachable: internal error");
}
}
i += 1;
}
chunk
}
const fn padding_length_for_input_length(input_length: usize) -> usize {
if input_length % 64 <= 55 {
64 - input_length % 64
} else {
128 - input_length % 64
}
}
const fn new() -> Self {
Self {
h: [
0x6745_2301,
0xefcd_ab89,
0x98ba_dcfe,
0x1032_5476,
0xc3d2_e1f0,
],
}
}
const fn from(hash: [u8; 20]) -> Self {
Self {
h: [
u32::from_be_bytes([hash[0], hash[1], hash[2], hash[3]]),
u32::from_be_bytes([hash[4], hash[5], hash[6], hash[7]]),
u32::from_be_bytes([hash[8], hash[9], hash[10], hash[11]]),
u32::from_be_bytes([hash[12], hash[13], hash[14], hash[15]]),
u32::from_be_bytes([hash[16], hash[17], hash[18], hash[19]]),
],
}
}
}
pub(crate) fn padding_for_length(input_length: usize) -> Vec<u8> {
let padding_length = padding_length_for_input_length(input_length);
let mut result = Vec::with_capacity(padding_length);
for i in 0..padding_length {
result.push(Sha1::padding_value_at_idx(input_length, i));
}
result
}
pub(crate) const fn padding_length_for_input_length(input_length: usize) -> usize {
Sha1::padding_length_for_input_length(input_length)
}
pub(crate) const fn compute_hash(input: &[u8], hash_type: HashType) -> [u8; 20] {
let num_chunks = Sha1::get_num_chunks(input.len());
let mut sha1 = Sha1::new();
let mut i = 0;
while i < num_chunks {
let chunk = Sha1::get_chunk(input, input.len(), i);
sha1 = sha1.apply_chunk(chunk, hash_type);
i += 1;
}
sha1.hash_from_data()
}
pub(crate) const fn extend_hash(
hash: [u8; 20],
length: usize,
additional_input: &[u8],
hash_type: HashType,
) -> [u8; 20] {
let len = length + padding_length_for_input_length(length) + additional_input.len();
let num_chunks = (additional_input.len() + padding_length_for_input_length(len)) / 64;
let mut sha1 = Sha1::from(hash);
let mut i = 0;
while i < num_chunks {
let chunk = Sha1::get_chunk(additional_input, len, i);
sha1 = sha1.apply_chunk(chunk, hash_type);
i += 1;
}
sha1.hash_from_data()
}
#[cfg(test)]
mod tests {
use crate::sha01;
use crate::sha01::HashType;
use alloc::string::String;
use alloc::vec::Vec;
#[test]
fn empty_hash() {
assert_eq!(
sha01::compute_hash(&[], HashType::Sha1),
[
0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60,
0x18, 0x90, 0xaf, 0xd8, 0x07, 0x09
]
);
}
#[test]
fn a_test() {
assert_eq!(
sha01::compute_hash(b"a", HashType::Sha1),
[
0x86, 0xf7, 0xe4, 0x37, 0xfa, 0xa5, 0xa7, 0xfc, 0xe1, 0x5d, 0x1d, 0xdc, 0xb9, 0xea,
0xea, 0xea, 0x37, 0x76, 0x67, 0xb8
]
);
}
#[test]
fn quick_brown_fox_test() {
assert_eq!(
sha01::compute_hash(
b"The quick brown fox jumps over the lazy dog",
HashType::Sha1
),
[
0x2f, 0xd4, 0xe1, 0xc6, 0x7a, 0x2d, 0x28, 0xfc, 0xed, 0x84, 0x9e, 0xe1, 0xbb, 0x76,
0xe7, 0x39, 0x1b, 0x93, 0xeb, 0x12
]
);
}
#[test]
fn quick_brown_fox_test_2() {
assert_eq!(
sha01::compute_hash(
b"The quick brown fox jumps over the lazy cog",
HashType::Sha1
),
[
0xde, 0x9f, 0x2c, 0x7f, 0xd2, 0x5e, 0x1b, 0x3a, 0xfa, 0xd3, 0xe8, 0x5a, 0x0b, 0xd1,
0x7d, 0x9b, 0x10, 0x0d, 0xb4, 0xb3
]
);
}
#[test]
fn abc_test() {
assert_eq!(
sha01::compute_hash(
b"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
HashType::Sha1
),
[
0x76, 0x1c, 0x45, 0x7b, 0xf7, 0x3b, 0x14, 0xd2, 0x7e, 0x9e, 0x92, 0x65, 0xc4, 0x6f,
0x4b, 0x4d, 0xda, 0x11, 0xf9, 0x40
]
);
}
#[test]
fn long_test() {
let mut input = String::new();
for _ in 0..10000 {
input.push_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
input.push_str("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
}
assert_eq!(input.len(), 1_000_000);
assert_eq!(
sha01::compute_hash(input.as_bytes(), HashType::Sha1),
[
0x34, 0xaa, 0x97, 0x3c, 0xd4, 0xc4, 0xda, 0xa4, 0xf6, 0x1e, 0xeb, 0x2b, 0xdb, 0xad,
0x27, 0x31, 0x65, 0x34, 0x01, 0x6f
]
);
}
#[test]
fn padding_length_tests() {
assert_eq!(sha01::padding_length_for_input_length(0), 64);
assert_eq!(sha01::padding_length_for_input_length(1), 63);
assert_eq!(sha01::padding_length_for_input_length(2), 62);
assert_eq!(sha01::padding_length_for_input_length(3), 61);
assert_eq!(sha01::padding_length_for_input_length(4), 60);
assert_eq!(sha01::padding_length_for_input_length(50), 14);
assert_eq!(sha01::padding_length_for_input_length(54), 10);
assert_eq!(sha01::padding_length_for_input_length(55), 9);
assert_eq!(sha01::padding_length_for_input_length(56), 64 + 8);
assert_eq!(sha01::padding_length_for_input_length(57), 64 + 7);
assert_eq!(sha01::padding_length_for_input_length(62), 64 + 2);
assert_eq!(sha01::padding_length_for_input_length(63), 64 + 1);
assert_eq!(sha01::padding_length_for_input_length(64), 64);
assert_eq!(sha01::padding_length_for_input_length(128), 64);
assert_eq!(sha01::padding_length_for_input_length(64 * 100_000), 64);
}
#[test]
fn test_hash_ext() {
let secret = b"count=10&lat=37.351&user_id=1&\
long=-119.827&waffle=eggo";
let hash = sha01::compute_hash(secret, HashType::Sha1);
let appended_str = b"&waffle=liege";
let combined_hash = sha01::extend_hash(hash, secret.len(), appended_str, HashType::Sha1);
let mut concatenation = Vec::<u8>::new();
concatenation.extend_from_slice(secret);
let intermediate_padding = sha01::padding_for_length(secret.len());
concatenation.extend_from_slice(intermediate_padding.as_slice());
concatenation.extend_from_slice(appended_str);
assert_eq!(
combined_hash,
sha01::compute_hash(concatenation.as_slice(), HashType::Sha1)
);
}
}