cryptography/hash/
sha1.rs1use super::Digest;
8
9const IV: [u32; 5] = [
11 0x6745_2301,
12 0xEFCD_AB89,
13 0x98BA_DCFE,
14 0x1032_5476,
15 0xC3D2_E1F0,
16];
17
18#[inline]
19fn compress(state: &mut [u32; 5], block: &[u8; 64]) {
20 let mut schedule = [0u32; 80];
21 for (i, chunk) in block.chunks_exact(4).enumerate() {
22 schedule[i] = u32::from_be_bytes(chunk.try_into().unwrap());
23 }
24 for word_idx in 16..80 {
25 schedule[word_idx] = (schedule[word_idx - 3]
26 ^ schedule[word_idx - 8]
27 ^ schedule[word_idx - 14]
28 ^ schedule[word_idx - 16])
29 .rotate_left(1);
30 }
31
32 let mut a_reg = state[0];
33 let mut b_reg = state[1];
34 let mut c_reg = state[2];
35 let mut d_reg = state[3];
36 let mut e_reg = state[4];
37
38 for (round_idx, &schedule_word) in schedule.iter().enumerate() {
39 let (round_mix, round_const) = match round_idx {
47 0..=19 => ((b_reg & c_reg) | ((!b_reg) & d_reg), 0x5A82_7999),
48 20..=39 => (b_reg ^ c_reg ^ d_reg, 0x6ED9_EBA1),
49 40..=59 => (
50 (b_reg & c_reg) | (b_reg & d_reg) | (c_reg & d_reg),
51 0x8F1B_BCDC,
52 ),
53 _ => (b_reg ^ c_reg ^ d_reg, 0xCA62_C1D6),
54 };
55 let next_a = a_reg
56 .rotate_left(5)
57 .wrapping_add(round_mix)
58 .wrapping_add(e_reg)
59 .wrapping_add(round_const)
60 .wrapping_add(schedule_word);
61 e_reg = d_reg;
62 d_reg = c_reg;
63 c_reg = b_reg.rotate_left(30);
64 b_reg = a_reg;
65 a_reg = next_a;
66 }
67
68 state[0] = state[0].wrapping_add(a_reg);
69 state[1] = state[1].wrapping_add(b_reg);
70 state[2] = state[2].wrapping_add(c_reg);
71 state[3] = state[3].wrapping_add(d_reg);
72 state[4] = state[4].wrapping_add(e_reg);
73}
74
75#[derive(Clone)]
76pub struct Sha1 {
77 state: [u32; 5],
78 block: [u8; 64],
79 pos: usize,
80 bit_len: u64,
81}
82
83impl Default for Sha1 {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89impl Sha1 {
90 pub const BLOCK_LEN: usize = 64;
91 pub const OUTPUT_LEN: usize = 20;
92
93 #[must_use]
94 pub fn new() -> Self {
95 Self {
96 state: IV,
97 block: [0u8; 64],
98 pos: 0,
99 bit_len: 0,
100 }
101 }
102
103 pub fn update(&mut self, mut data: &[u8]) {
104 while !data.is_empty() {
105 let take = (64 - self.pos).min(data.len());
106 self.block[self.pos..self.pos + take].copy_from_slice(&data[..take]);
107 self.pos += take;
108 data = &data[take..];
109
110 if self.pos == 64 {
111 compress(&mut self.state, &self.block);
112 self.block = [0u8; 64];
113 self.pos = 0;
114 self.bit_len = self.bit_len.wrapping_add(512);
115 }
116 }
117 }
118
119 #[must_use]
120 pub fn finalize(mut self) -> [u8; 20] {
121 self.bit_len = self.bit_len.wrapping_add((self.pos as u64) * 8);
122
123 self.block[self.pos] = 0x80;
124 self.pos += 1;
125
126 if self.pos > 56 {
127 self.block[self.pos..].fill(0);
128 compress(&mut self.state, &self.block);
129 self.block = [0u8; 64];
130 self.pos = 0;
131 }
132
133 self.block[self.pos..56].fill(0);
134 self.block[56..].copy_from_slice(&self.bit_len.to_be_bytes());
135 compress(&mut self.state, &self.block);
136
137 let mut out = [0u8; 20];
138 for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
139 chunk.copy_from_slice(&word.to_be_bytes());
140 }
141 out
142 }
143
144 #[must_use]
145 pub fn digest(data: &[u8]) -> [u8; 20] {
146 let mut h = Self::new();
147 h.update(data);
148 h.finalize()
149 }
150
151 fn finalize_into_reset(&mut self, out: &mut [u8; 20]) {
152 self.bit_len = self.bit_len.wrapping_add((self.pos as u64) * 8);
153
154 self.block[self.pos] = 0x80;
155 self.pos += 1;
156
157 if self.pos > 56 {
158 self.block[self.pos..].fill(0);
159 compress(&mut self.state, &self.block);
160 self.block = [0u8; 64];
161 self.pos = 0;
162 }
163
164 self.block[self.pos..56].fill(0);
165 self.block[56..].copy_from_slice(&self.bit_len.to_be_bytes());
166 compress(&mut self.state, &self.block);
167
168 for (chunk, word) in out.chunks_exact_mut(4).zip(self.state.iter()) {
169 chunk.copy_from_slice(&word.to_be_bytes());
170 }
171
172 self.zeroize();
173 }
174}
175
176impl Digest for Sha1 {
177 const BLOCK_LEN: usize = 64;
178 const OUTPUT_LEN: usize = 20;
179
180 fn new() -> Self {
181 Self::new()
182 }
183
184 fn update(&mut self, data: &[u8]) {
185 self.update(data);
186 }
187
188 fn finalize_into(self, out: &mut [u8]) {
189 assert_eq!(out.len(), 20, "wrong digest length");
190 out.copy_from_slice(&self.finalize());
191 }
192
193 fn finalize_reset(&mut self, out: &mut [u8]) {
194 let out: &mut [u8; 20] = out.try_into().expect("wrong digest length");
195 self.finalize_into_reset(out);
196 }
197
198 fn zeroize(&mut self) {
199 crate::ct::zeroize_slice(self.state.as_mut_slice());
200 crate::ct::zeroize_slice(self.block.as_mut_slice());
201 self.pos = 0;
202 self.bit_len = 0;
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 fn hex(bytes: &[u8]) -> String {
211 let mut out = String::with_capacity(bytes.len() * 2);
212 for b in bytes {
213 use core::fmt::Write;
214 let _ = write!(&mut out, "{b:02x}");
215 }
216 out
217 }
218
219 #[test]
220 fn sha1_empty() {
221 assert_eq!(
222 hex(&Sha1::digest(b"")),
223 "da39a3ee5e6b4b0d3255bfef95601890afd80709"
224 );
225 }
226
227 #[test]
228 fn sha1_abc_streaming() {
229 let mut h = Sha1::new();
230 h.update(b"a");
231 h.update(b"b");
232 h.update(b"c");
233 assert_eq!(
234 hex(&h.finalize()),
235 "a9993e364706816aba3e25717850c26c9cd0d89d"
236 );
237 }
238
239 #[test]
240 fn sha1_matches_openssl() {
241 let msg = b"The quick brown fox jumps over the lazy dog";
242 let Some(expected) = crate::test_utils::run_openssl(&["dgst", "-sha1", "-binary"], msg) else {
243 return;
244 };
245 assert_eq!(Sha1::digest(msg).as_slice(), expected.as_slice());
246 }
247}