Skip to main content

irox_tools/hash/
sha1.rs

1// SPDX-License-Identifier: MIT
2// Copyright 2025 IROX Contributors
3//
4
5//!
6//! Bog standard implementation of SHA1 / RFC3174.
7//!
8//! *THIS SHOULD NOT BE USED FOR ANYTHING SECURITY RELATED*
9
10#![allow(clippy::indexing_slicing)]
11
12use crate::buf::{Buffer, FixedBuf, RoundBuffer};
13use crate::hash::{HashAlgorithm, HashDigest};
14use core::ops::{BitAnd, BitOr, BitXor, Not};
15use irox_bits::{Bits, Error, MutBits};
16
17pub const BLOCK_SIZE: usize = 64;
18pub const OUTPUT_SIZE: usize = 20;
19
20///
21/// Implementation of [RFC 3174](https://datatracker.ietf.org/doc/html/rfc3174) based on the [Wikipedia](https://en.wikipedia.org/wiki/SHA-1#Examples_and_pseudocode) algorithm
22///
23///
24/// **THIS SHOULD NOT BE USED FOR ANYTHING SECURITY RELATED**
25#[derive(Clone)]
26pub struct SHA1 {
27    written_length: u64,
28    buf: RoundBuffer<BLOCK_SIZE, u8>,
29
30    h0: u32,
31    h1: u32,
32    h2: u32,
33    h3: u32,
34    h4: u32,
35}
36
37impl Default for SHA1 {
38    fn default() -> Self {
39        Self {
40            h0: 0x67452301,
41            h1: 0xEFCDAB89,
42            h2: 0x98BADCFE,
43            h3: 0x10325476,
44            h4: 0xC3D2E1F0,
45            written_length: 0,
46            buf: RoundBuffer::default(),
47        }
48    }
49}
50
51impl SHA1 {
52    #[must_use]
53    pub fn new() -> Self {
54        Self::default()
55    }
56    fn try_chomp(&mut self) {
57        if self.buf.len() < BLOCK_SIZE {
58            return;
59        }
60        let mut words: FixedBuf<80, u32> = FixedBuf::default();
61        for i in 0..16 {
62            words[i] = self.buf.read_be_u32().unwrap_or_default();
63        }
64        for i in 16..=79 {
65            let w1 = words.get(i - 3).copied().unwrap_or_default();
66            let w2 = words.get(i - 8).copied().unwrap_or_default();
67            let w3 = words.get(i - 14).copied().unwrap_or_default();
68            let w4 = words.get(i - 16).copied().unwrap_or_default();
69            words[i] = w1.bitxor(w2).bitxor(w3).bitxor(w4).rotate_left(1);
70        }
71
72        let mut a = self.h0;
73        let mut b = self.h1;
74        let mut c = self.h2;
75        let mut d = self.h3;
76        let mut e = self.h4;
77
78        for i in 0..=79 {
79            let mut f: u32 = 0;
80            let mut k: u32 = 0;
81            match i {
82                0..=19 => {
83                    f = b.bitand(c).bitxor(b.not().bitand(d));
84                    k = 0x5A827999;
85                }
86                20..=39 => {
87                    f = b.bitxor(c).bitxor(d);
88                    k = 0x6ED9EBA1;
89                }
90                40..=59 => {
91                    f = b.bitand(c).bitor(b.bitand(d)).bitor(c.bitand(d));
92                    k = 0x8F1BBCDC;
93                }
94                60..=79 => {
95                    f = b.bitxor(c).bitxor(d);
96                    k = 0xCA62C1D6;
97                }
98                _ => {
99                    // unreachable
100                }
101            }
102            let w = words.get(i as usize).copied().unwrap_or_default();
103            let temp = a
104                .rotate_left(5)
105                .wrapping_add(f)
106                .wrapping_add(e)
107                .wrapping_add(k)
108                .wrapping_add(w);
109            e = d;
110            d = c;
111            c = b.rotate_left(30);
112            b = a;
113            a = temp;
114        }
115        self.h0 = self.h0.wrapping_add(a);
116        self.h1 = self.h1.wrapping_add(b);
117        self.h2 = self.h2.wrapping_add(c);
118        self.h3 = self.h3.wrapping_add(d);
119        self.h4 = self.h4.wrapping_add(e);
120    }
121    ///
122    /// Finishes the hash and returns the result.
123    pub fn finish(mut self) -> [u8; OUTPUT_SIZE] {
124        let mut modlen64 = (self.written_length & 0x3F) as usize;
125        let mut pad: usize = 0;
126        if modlen64 >= 56 {
127            // append 64 bits/8 bytes;
128            pad += BLOCK_SIZE - modlen64;
129            modlen64 = 0;
130        }
131        pad += 56 - modlen64;
132        let _ = self.buf.push_back(0x80);
133        pad -= 1;
134        for _ in 0..pad {
135            self.try_chomp();
136            let _ = self.buf.push_back(0);
137        }
138        let _ = self.buf.write_be_u64(self.written_length << 3);
139        self.try_chomp();
140        let mut out: [u8; OUTPUT_SIZE] = [0; OUTPUT_SIZE];
141        let mut v = out.as_mut_slice();
142        let _ = v.write_be_u32(self.h0);
143        let _ = v.write_be_u32(self.h1);
144        let _ = v.write_be_u32(self.h2);
145        let _ = v.write_be_u32(self.h3);
146        let _ = v.write_be_u32(self.h4);
147        out
148    }
149
150    ///
151    /// Appends the bytes to the internal buffer.  NOTE: You must call 'finish' to get the final result.
152    pub fn write(&mut self, bytes: &[u8]) {
153        for b in bytes {
154            let _ = self.buf.push_back(*b);
155            self.written_length += 1;
156            self.try_chomp();
157        }
158    }
159
160    ///
161    /// Hashes the provided bytes.
162    pub fn hash(mut self, bytes: &[u8]) -> [u8; OUTPUT_SIZE] {
163        self.write(bytes);
164        self.finish()
165    }
166}
167
168impl MutBits for SHA1 {
169    fn write_u8(&mut self, val: u8) -> Result<(), Error> {
170        self.write(&[val]);
171        Ok(())
172    }
173}
174
175impl HashDigest<BLOCK_SIZE, OUTPUT_SIZE> for SHA1 {
176    fn write(&mut self, bytes: &[u8]) {
177        SHA1::write(self, bytes)
178    }
179
180    fn hash(self, bytes: &[u8]) -> [u8; OUTPUT_SIZE] {
181        SHA1::hash(self, bytes)
182    }
183
184    fn finish(self) -> [u8; OUTPUT_SIZE] {
185        SHA1::finish(self)
186    }
187
188    fn algorithm() -> HashAlgorithm {
189        HashAlgorithm::SHA1
190    }
191}
192
193#[cfg(test)]
194mod test {
195    use crate::hash::sha1::SHA1;
196    use crate::hex::from_hex_into;
197    use irox_bits::Error;
198
199    #[test]
200    pub fn test_sha1() -> Result<(), Error> {
201        let tests = [
202            ("DA39A3EE 5E6B4B0D 3255BFEF 95601890 AFD80709", ""),
203            ("86F7E437 FAA5A7FC E15D1DDC B9EAEAEA 377667B8", "a"),
204            ("A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D", "abc"),
205            (
206                "C12252CE DA8BE899 4D5FA029 0A47231C 1D16AAE3",
207                "message digest",
208            ),
209            (
210                "32D10C7B 8CF96570 CA04CE37 F2A19D84 240D3A89",
211                "abcdefghijklmnopqrstuvwxyz",
212            ),
213            (
214                "761C457B F73B14D2 7E9E9265 C46F4B4D DA11F940",
215                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789",
216            ),
217            (
218                "CF0800F7 644ACE3C B4C3FA33 388D3BA0 EA3C8B6E",
219                "0123456789012345678901234567890123456789012345678901234567890123",
220            ),
221            (
222                "50ABF570 6A150990 A08B2C5E A40FA0E5 85554732",
223                "12345678901234567890123456789012345678901234567890123456789012345678901234567890",
224            ),
225            (
226                "84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1",
227                "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq",
228            ),
229            (
230                "2FD4E1C6 7A2D28FC ED849EE1 BB76E739 1B93EB12",
231                "The quick brown fox jumps over the lazy dog",
232            ),
233            (
234                "DE9F2C7F D25E1B3A FAD3E85A 0BD17D9B 100DB4B3",
235                "The quick brown fox jumps over the lazy cog",
236            ),
237            (
238                "408D9438 4216F890 FF7A0C35 28E8BED1 E0B01621",
239                "The quick brown fox jumps over the lazy dog.",
240            ),
241        ];
242        for (hash, st) in tests {
243            let mut bbuf: [u8; 1024] = [0; 1024];
244            let mut buf = bbuf.as_mut_slice();
245            let wrote = from_hex_into(hash, &mut buf)?;
246            let hash = SHA1::default().hash(st.as_bytes());
247            assert_eq!(&bbuf[0..wrote], &hash);
248        }
249
250        Ok(())
251    }
252}