Skip to main content

arcana/hash/
sha1.rs

1//! SHA-1 hash function (FIPS 180-4).
2//!
3//! 160-bit output, 512-bit (64-byte) blocks, Merkle-Damgard construction.
4//! **Deprecated for security use** — provided for legacy compatibility.
5
6use crate::Hasher;
7
8const H0: [u32; 5] = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0];
9
10/// SHA-1 hasher (FIPS 180-4). 160-bit output, 64-byte blocks.
11///
12/// **Collision-broken** since SHAttered (2017): do not use for any new
13/// digital signature design. Shipped here for legacy interop only --
14/// HMAC-SHA-1 in TLS 1.2 cipher suites, X.509 certificates from the
15/// early 2000s, Git's content-addressed storage.
16#[derive(Clone)]
17pub struct Sha1 {
18    state: [u32; 5],
19    buf: [u8; 64],
20    buf_len: usize,
21    total_len: u64,
22}
23
24fn compress(state: &mut [u32; 5], block: &[u8]) {
25    let mut w = [0u32; 80];
26    for i in 0..16 {
27        w[i] = u32::from_be_bytes([block[4 * i], block[4 * i + 1], block[4 * i + 2], block[4 * i + 3]]);
28    }
29    for i in 16..80 {
30        w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
31    }
32
33    let mut a = state[0];
34    let mut b = state[1];
35    let mut c = state[2];
36    let mut d = state[3];
37    let mut e = state[4];
38
39    for i in 0..80 {
40        let (f, k) = match i {
41            0..=19 => ((b & c) | ((!b) & d), 0x5A827999u32),
42            20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
43            40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
44            _ => (b ^ c ^ d, 0xCA62C1D6u32),
45        };
46        let temp = a
47            .rotate_left(5)
48            .wrapping_add(f)
49            .wrapping_add(e)
50            .wrapping_add(k)
51            .wrapping_add(w[i]);
52        e = d;
53        d = c;
54        c = b.rotate_left(30);
55        b = a;
56        a = temp;
57    }
58
59    state[0] = state[0].wrapping_add(a);
60    state[1] = state[1].wrapping_add(b);
61    state[2] = state[2].wrapping_add(c);
62    state[3] = state[3].wrapping_add(d);
63    state[4] = state[4].wrapping_add(e);
64}
65
66impl Hasher for Sha1 {
67    const OUTPUT_LEN: usize = 20;
68    const BLOCK_LEN: usize = 64;
69
70    fn new() -> Self {
71        Self {
72            state: H0,
73            buf: [0u8; 64],
74            buf_len: 0,
75            total_len: 0,
76        }
77    }
78
79    fn update(&mut self, data: &[u8]) {
80        let mut pos = 0;
81        self.total_len += data.len() as u64;
82
83        if self.buf_len > 0 {
84            let need = 64 - self.buf_len;
85            let take = need.min(data.len());
86            self.buf[self.buf_len..self.buf_len + take].copy_from_slice(&data[..take]);
87            self.buf_len += take;
88            pos = take;
89            if self.buf_len == 64 {
90                let block = self.buf;
91                compress(&mut self.state, &block);
92                self.buf_len = 0;
93            }
94        }
95
96        while pos + 64 <= data.len() {
97            compress(&mut self.state, &data[pos..pos + 64]);
98            pos += 64;
99        }
100
101        if pos < data.len() {
102            let remaining = data.len() - pos;
103            self.buf[..remaining].copy_from_slice(&data[pos..]);
104            self.buf_len = remaining;
105        }
106    }
107
108    fn finalize(self) -> Vec<u8> {
109        let mut out = vec![0u8; 20];
110        self.finalize_into(&mut out);
111        out
112    }
113
114    fn finalize_into(mut self, out: &mut [u8]) {
115        let bit_len = self.total_len * 8;
116        // Padding
117        let mut pad = [0u8; 72]; // max padding needed
118        pad[0] = 0x80;
119        let pad_len = if self.buf_len < 56 {
120            56 - self.buf_len
121        } else {
122            120 - self.buf_len
123        };
124        self.update(&pad[..pad_len]);
125        self.update(&bit_len.to_be_bytes());
126
127        for (i, word) in self.state.iter().enumerate() {
128            let bytes = word.to_be_bytes();
129            let start = i * 4;
130            if start + 4 <= out.len() {
131                out[start..start + 4].copy_from_slice(&bytes);
132            } else if start < out.len() {
133                let end = out.len() - start;
134                out[start..].copy_from_slice(&bytes[..end]);
135            }
136        }
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143    use crate::Hasher;
144
145    #[test]
146    fn test_sha1_empty() {
147        let digest = Sha1::hash(b"");
148        let expected: [u8; 20] = [
149            0xda, 0x39, 0xa3, 0xee, 0x5e, 0x6b, 0x4b, 0x0d, 0x32, 0x55, 0xbf, 0xef, 0x95, 0x60, 0x18, 0x90, 0xaf, 0xd8,
150            0x07, 0x09,
151        ];
152        assert_eq!(&digest[..], &expected[..]);
153    }
154
155    #[test]
156    fn test_sha1_abc() {
157        let digest = Sha1::hash(b"abc");
158        let expected: [u8; 20] = [
159            0xa9, 0x99, 0x3e, 0x36, 0x47, 0x06, 0x81, 0x6a, 0xba, 0x3e, 0x25, 0x71, 0x78, 0x50, 0xc2, 0x6c, 0x9c, 0xd0,
160            0xd8, 0x9d,
161        ];
162        assert_eq!(&digest[..], &expected[..]);
163    }
164}