Skip to main content

rns_ctl/
sha1.rs

1/// SHA-1 hash (FIPS 180-4). Only used for WebSocket Sec-WebSocket-Accept.
2pub fn sha1(data: &[u8]) -> [u8; 20] {
3    let mut h0: u32 = 0x67452301;
4    let mut h1: u32 = 0xEFCDAB89;
5    let mut h2: u32 = 0x98BADCFE;
6    let mut h3: u32 = 0x10325476;
7    let mut h4: u32 = 0xC3D2E1F0;
8
9    let bit_len = (data.len() as u64) * 8;
10
11    // Pad: append 0x80, then zeros, then 8-byte big-endian bit length
12    let mut padded = data.to_vec();
13    padded.push(0x80);
14    while (padded.len() % 64) != 56 {
15        padded.push(0);
16    }
17    padded.extend_from_slice(&bit_len.to_be_bytes());
18
19    for chunk in padded.chunks_exact(64) {
20        let mut w = [0u32; 80];
21        for i in 0..16 {
22            w[i] = u32::from_be_bytes([
23                chunk[i * 4],
24                chunk[i * 4 + 1],
25                chunk[i * 4 + 2],
26                chunk[i * 4 + 3],
27            ]);
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, mut b, mut c, mut d, mut e) = (h0, h1, h2, h3, h4);
34
35        for i in 0..80 {
36            let (f, k) = match i {
37                0..=19 => ((b & c) | ((!b) & d), 0x5A827999u32),
38                20..=39 => (b ^ c ^ d, 0x6ED9EBA1u32),
39                40..=59 => ((b & c) | (b & d) | (c & d), 0x8F1BBCDCu32),
40                _ => (b ^ c ^ d, 0xCA62C1D6u32),
41            };
42            let temp = a
43                .rotate_left(5)
44                .wrapping_add(f)
45                .wrapping_add(e)
46                .wrapping_add(k)
47                .wrapping_add(w[i]);
48            e = d;
49            d = c;
50            c = b.rotate_left(30);
51            b = a;
52            a = temp;
53        }
54
55        h0 = h0.wrapping_add(a);
56        h1 = h1.wrapping_add(b);
57        h2 = h2.wrapping_add(c);
58        h3 = h3.wrapping_add(d);
59        h4 = h4.wrapping_add(e);
60    }
61
62    let mut out = [0u8; 20];
63    out[0..4].copy_from_slice(&h0.to_be_bytes());
64    out[4..8].copy_from_slice(&h1.to_be_bytes());
65    out[8..12].copy_from_slice(&h2.to_be_bytes());
66    out[12..16].copy_from_slice(&h3.to_be_bytes());
67    out[16..20].copy_from_slice(&h4.to_be_bytes());
68    out
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use crate::encode::to_hex;
75
76    #[test]
77    fn sha1_empty() {
78        let hash = sha1(b"");
79        assert_eq!(to_hex(&hash), "da39a3ee5e6b4b0d3255bfef95601890afd80709");
80    }
81
82    #[test]
83    fn sha1_abc() {
84        let hash = sha1(b"abc");
85        assert_eq!(to_hex(&hash), "a9993e364706816aba3e25717850c26c9cd0d89d");
86    }
87
88    #[test]
89    fn sha1_fox() {
90        let hash = sha1(b"The quick brown fox jumps over the lazy dog");
91        assert_eq!(to_hex(&hash), "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12");
92    }
93
94    #[test]
95    fn sha1_websocket_accept() {
96        // Test WebSocket Sec-WebSocket-Accept computation
97        let key = "dGhlIHNhbXBsZSBub25jZQ==";
98        let magic = "258EAFA5-E914-47DA-95CA-5AB5DC11D045";
99        let combined = format!("{}{}", key, magic);
100        let hash = sha1(combined.as_bytes());
101        assert_eq!(to_hex(&hash), "47255391f6ef808bbebc064b6eecf285b72b1ffd");
102        let accept = crate::encode::to_base64(&hash);
103        assert_eq!(accept, "RyVTkfbvgIu+vAZLbuzyhbcrH/0=");
104    }
105}