buup/transformers/
sha1_hash.rs

1use crate::{Transform, TransformError, TransformerCategory};
2
3// SHA-1 constants
4const H0: u32 = 0x67452301;
5const H1: u32 = 0xEFCDAB89;
6const H2: u32 = 0x98BADCFE;
7const H3: u32 = 0x10325476;
8const H4: u32 = 0xC3D2E1F0;
9
10/// SHA-1 hash transformer
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub struct Sha1Hash;
13
14// Default test input for SHA1 Hash
15// pub const DEFAULT_TEST_INPUT: &str = "buup text utility";
16
17impl Sha1Hash {
18    // Pads the message according to SHA-1 standard (RFC 3174)
19    fn pad_message(message: &[u8]) -> Vec<u8> {
20        let message_len_bits = (message.len() as u64) * 8;
21        let mut padded = message.to_vec();
22        padded.push(0x80); // Append '1' bit
23
24        // Append '0' bits until message length is congruent to 448 (mod 512)
25        while padded.len() % 64 != 56 {
26            padded.push(0x00);
27        }
28
29        // Append original message length as 64-bit big-endian integer
30        padded.extend_from_slice(&message_len_bits.to_be_bytes());
31
32        padded
33    }
34
35    // Processes a single 512-bit (64-byte) block
36    fn process_block(h: &mut [u32; 5], block: &[u8]) {
37        assert_eq!(block.len(), 64);
38
39        let mut w = [0u32; 80];
40        for (i, chunk) in block.chunks_exact(4).enumerate() {
41            w[i] = u32::from_be_bytes(chunk.try_into().unwrap());
42        }
43
44        for i in 16..80 {
45            w[i] = (w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]).rotate_left(1);
46        }
47
48        let mut a = h[0];
49        let mut b = h[1];
50        let mut c = h[2];
51        let mut d = h[3];
52        let mut e = h[4];
53
54        for (i, w_i) in w.iter().enumerate() {
55            let (f, k) = match i {
56                0..=19 => (((b & c) | (!b & d)), 0x5A827999),
57                20..=39 => ((b ^ c ^ d), 0x6ED9EBA1),
58                40..=59 => (((b & c) | (b & d) | (c & d)), 0x8F1BBCDC),
59                60..=79 => ((b ^ c ^ d), 0xCA62C1D6),
60                _ => unreachable!(), // Should not happen
61            };
62
63            let temp = a
64                .rotate_left(5)
65                .wrapping_add(f)
66                .wrapping_add(e)
67                .wrapping_add(k)
68                .wrapping_add(*w_i);
69
70            e = d;
71            d = c;
72            c = b.rotate_left(30);
73            b = a;
74            a = temp;
75        }
76
77        h[0] = h[0].wrapping_add(a);
78        h[1] = h[1].wrapping_add(b);
79        h[2] = h[2].wrapping_add(c);
80        h[3] = h[3].wrapping_add(d);
81        h[4] = h[4].wrapping_add(e);
82    }
83}
84
85impl Transform for Sha1Hash {
86    fn name(&self) -> &'static str {
87        "SHA-1 Hash"
88    }
89
90    fn id(&self) -> &'static str {
91        "sha1hash"
92    }
93
94    fn description(&self) -> &'static str {
95        "Computes the SHA-1 hash of the input text (Warning: SHA-1 is cryptographically weak)"
96    }
97
98    fn category(&self) -> TransformerCategory {
99        TransformerCategory::Crypto
100    }
101
102    fn transform(&self, input: &str) -> Result<String, TransformError> {
103        let message = input.as_bytes();
104        let padded_message = Self::pad_message(message);
105
106        let mut h = [H0, H1, H2, H3, H4]; // Initial hash values
107
108        for block in padded_message.chunks_exact(64) {
109            Self::process_block(&mut h, block);
110        }
111
112        // Convert the final hash state (h0-h4) to a hex string
113        let mut result = String::with_capacity(40); // SHA-1 output is 160 bits = 20 bytes = 40 hex chars
114        for val in h.iter() {
115            result.push_str(&format!("{:08x}", val));
116        }
117
118        Ok(result)
119    }
120
121    fn default_test_input(&self) -> &'static str {
122        "buup"
123    }
124}
125
126#[cfg(test)]
127mod tests {
128    use super::*;
129
130    #[test]
131    fn test_sha1_empty_string() {
132        let transformer = Sha1Hash;
133        let input = "";
134        let expected = "da39a3ee5e6b4b0d3255bfef95601890afd80709";
135        assert_eq!(transformer.transform(input).unwrap(), expected);
136    }
137
138    #[test]
139    fn test_sha1_simple_string() {
140        let transformer = Sha1Hash;
141        let input = transformer.default_test_input();
142        let expected = "fb68687a3bc7428da3ddeecabc907bea236ae70b";
143        assert_eq!(transformer.transform(input).unwrap(), expected);
144
145        let input_hw = "hello world";
146        let expected_hw = "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed";
147        assert_eq!(transformer.transform(input_hw).unwrap(), expected_hw);
148    }
149
150    #[test]
151    fn test_sha1_rfc3174_test_case_1() {
152        // Test Case 1 from RFC 3174
153        let transformer = Sha1Hash;
154        let input = "abc";
155        let expected = "a9993e364706816aba3e25717850c26c9cd0d89d";
156        assert_eq!(transformer.transform(input).unwrap(), expected);
157    }
158
159    #[test]
160    fn test_sha1_rfc3174_test_case_2() {
161        // Test Case 2 from RFC 3174
162        let transformer = Sha1Hash;
163        let input = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq";
164        let expected = "84983e441c3bd26ebaae4aa1f95129e5e54670f1";
165        assert_eq!(transformer.transform(input).unwrap(), expected);
166    }
167
168    #[test]
169    fn test_sha1_million_a_chars() {
170        // Test Case: 1 million 'a' characters
171        // This is a common, unofficial stress test vector, not from RFC 3174.
172        let transformer = Sha1Hash;
173        let input = String::from("a").repeat(1_000_000);
174        let expected = "34aa973cd4c4daa4f61eeb2bdbad27316534016f";
175        assert_eq!(transformer.transform(&input).unwrap(), expected);
176    }
177
178    #[test]
179    fn test_sha1_long_string_multiple_blocks() {
180        // String requiring multiple blocks processing
181        let transformer = Sha1Hash;
182        let input = "The quick brown fox jumps over the lazy dog.";
183        let expected = "408d94384216f890ff7a0c3528e8bed1e0b01621";
184        assert_eq!(transformer.transform(input).unwrap(), expected);
185    }
186}