master
1//! The SHA-1 function is now considered cryptographically broken.
2//! Namely, it is feasible to find multiple inputs producing the same hash.
3//! For a fast-performing, cryptographically secure hash function, see SHA512/256, BLAKE2 or BLAKE3.
4
5const std = @import("../std.zig");
6const mem = std.mem;
7const math = std.math;
8const Sha1 = @This();
9
10pub const block_length = 64;
11pub const digest_length = 20;
12pub const Options = struct {};
13
14s: [5]u32,
15/// Streaming Cache
16buf: [64]u8 = undefined,
17buf_len: u8 = 0,
18total_len: u64 = 0,
19
20pub fn init(options: Options) Sha1 {
21 _ = options;
22 return .{
23 .s = [_]u32{
24 0x67452301,
25 0xEFCDAB89,
26 0x98BADCFE,
27 0x10325476,
28 0xC3D2E1F0,
29 },
30 };
31}
32
33pub fn hash(b: []const u8, out: *[digest_length]u8, options: Options) void {
34 var d = Sha1.init(options);
35 d.update(b);
36 d.final(out);
37}
38
39pub fn update(d: *Sha1, b: []const u8) void {
40 var off: usize = 0;
41
42 // Partial buffer exists from previous update. Copy into buffer then hash.
43 if (d.buf_len != 0 and d.buf_len + b.len >= 64) {
44 off += 64 - d.buf_len;
45 @memcpy(d.buf[d.buf_len..][0..off], b[0..off]);
46
47 d.round(d.buf[0..]);
48 d.buf_len = 0;
49 }
50
51 // Full middle blocks.
52 while (off + 64 <= b.len) : (off += 64) {
53 d.round(b[off..][0..64]);
54 }
55
56 // Copy any remainder for next pass.
57 @memcpy(d.buf[d.buf_len..][0 .. b.len - off], b[off..]);
58 d.buf_len += @as(u8, @intCast(b[off..].len));
59
60 d.total_len += b.len;
61}
62
63pub fn peek(d: Sha1) [digest_length]u8 {
64 var copy = d;
65 return copy.finalResult();
66}
67
68pub fn final(d: *Sha1, out: *[digest_length]u8) void {
69 // The buffer here will never be completely full.
70 @memset(d.buf[d.buf_len..], 0);
71
72 // Append padding bits.
73 d.buf[d.buf_len] = 0x80;
74 d.buf_len += 1;
75
76 // > 448 mod 512 so need to add an extra round to wrap around.
77 if (64 - d.buf_len < 8) {
78 d.round(d.buf[0..]);
79 @memset(d.buf[0..], 0);
80 }
81
82 // Append message length.
83 var i: usize = 1;
84 var len = d.total_len >> 5;
85 d.buf[63] = @as(u8, @intCast(d.total_len & 0x1f)) << 3;
86 while (i < 8) : (i += 1) {
87 d.buf[63 - i] = @as(u8, @intCast(len & 0xff));
88 len >>= 8;
89 }
90
91 d.round(d.buf[0..]);
92
93 for (d.s, 0..) |s, j| {
94 mem.writeInt(u32, out[4 * j ..][0..4], s, .big);
95 }
96}
97
98pub fn finalResult(d: *Sha1) [digest_length]u8 {
99 var result: [digest_length]u8 = undefined;
100 d.final(&result);
101 return result;
102}
103
104fn round(d: *Sha1, b: *const [64]u8) void {
105 var s: [16]u32 = undefined;
106
107 var v: [5]u32 = [_]u32{
108 d.s[0],
109 d.s[1],
110 d.s[2],
111 d.s[3],
112 d.s[4],
113 };
114
115 const round0a = comptime [_]RoundParam{
116 .abcdei(0, 1, 2, 3, 4, 0),
117 .abcdei(4, 0, 1, 2, 3, 1),
118 .abcdei(3, 4, 0, 1, 2, 2),
119 .abcdei(2, 3, 4, 0, 1, 3),
120 .abcdei(1, 2, 3, 4, 0, 4),
121 .abcdei(0, 1, 2, 3, 4, 5),
122 .abcdei(4, 0, 1, 2, 3, 6),
123 .abcdei(3, 4, 0, 1, 2, 7),
124 .abcdei(2, 3, 4, 0, 1, 8),
125 .abcdei(1, 2, 3, 4, 0, 9),
126 .abcdei(0, 1, 2, 3, 4, 10),
127 .abcdei(4, 0, 1, 2, 3, 11),
128 .abcdei(3, 4, 0, 1, 2, 12),
129 .abcdei(2, 3, 4, 0, 1, 13),
130 .abcdei(1, 2, 3, 4, 0, 14),
131 .abcdei(0, 1, 2, 3, 4, 15),
132 };
133 inline for (round0a) |r| {
134 s[r.i] = mem.readInt(u32, b[r.i * 4 ..][0..4], .big);
135
136 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
137 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
138 }
139
140 const round0b = comptime [_]RoundParam{
141 .abcdei(4, 0, 1, 2, 3, 16),
142 .abcdei(3, 4, 0, 1, 2, 17),
143 .abcdei(2, 3, 4, 0, 1, 18),
144 .abcdei(1, 2, 3, 4, 0, 19),
145 };
146 inline for (round0b) |r| {
147 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
148 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
149
150 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x5A827999 +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) | (~v[r.b] & v[r.d]));
151 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
152 }
153
154 const round1 = comptime [_]RoundParam{
155 .abcdei(0, 1, 2, 3, 4, 20),
156 .abcdei(4, 0, 1, 2, 3, 21),
157 .abcdei(3, 4, 0, 1, 2, 22),
158 .abcdei(2, 3, 4, 0, 1, 23),
159 .abcdei(1, 2, 3, 4, 0, 24),
160 .abcdei(0, 1, 2, 3, 4, 25),
161 .abcdei(4, 0, 1, 2, 3, 26),
162 .abcdei(3, 4, 0, 1, 2, 27),
163 .abcdei(2, 3, 4, 0, 1, 28),
164 .abcdei(1, 2, 3, 4, 0, 29),
165 .abcdei(0, 1, 2, 3, 4, 30),
166 .abcdei(4, 0, 1, 2, 3, 31),
167 .abcdei(3, 4, 0, 1, 2, 32),
168 .abcdei(2, 3, 4, 0, 1, 33),
169 .abcdei(1, 2, 3, 4, 0, 34),
170 .abcdei(0, 1, 2, 3, 4, 35),
171 .abcdei(4, 0, 1, 2, 3, 36),
172 .abcdei(3, 4, 0, 1, 2, 37),
173 .abcdei(2, 3, 4, 0, 1, 38),
174 .abcdei(1, 2, 3, 4, 0, 39),
175 };
176 inline for (round1) |r| {
177 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
178 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
179
180 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x6ED9EBA1 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
181 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
182 }
183
184 const round2 = comptime [_]RoundParam{
185 .abcdei(0, 1, 2, 3, 4, 40),
186 .abcdei(4, 0, 1, 2, 3, 41),
187 .abcdei(3, 4, 0, 1, 2, 42),
188 .abcdei(2, 3, 4, 0, 1, 43),
189 .abcdei(1, 2, 3, 4, 0, 44),
190 .abcdei(0, 1, 2, 3, 4, 45),
191 .abcdei(4, 0, 1, 2, 3, 46),
192 .abcdei(3, 4, 0, 1, 2, 47),
193 .abcdei(2, 3, 4, 0, 1, 48),
194 .abcdei(1, 2, 3, 4, 0, 49),
195 .abcdei(0, 1, 2, 3, 4, 50),
196 .abcdei(4, 0, 1, 2, 3, 51),
197 .abcdei(3, 4, 0, 1, 2, 52),
198 .abcdei(2, 3, 4, 0, 1, 53),
199 .abcdei(1, 2, 3, 4, 0, 54),
200 .abcdei(0, 1, 2, 3, 4, 55),
201 .abcdei(4, 0, 1, 2, 3, 56),
202 .abcdei(3, 4, 0, 1, 2, 57),
203 .abcdei(2, 3, 4, 0, 1, 58),
204 .abcdei(1, 2, 3, 4, 0, 59),
205 };
206 inline for (round2) |r| {
207 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
208 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
209
210 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0x8F1BBCDC +% s[r.i & 0xf] +% ((v[r.b] & v[r.c]) ^ (v[r.b] & v[r.d]) ^ (v[r.c] & v[r.d]));
211 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
212 }
213
214 const round3 = comptime [_]RoundParam{
215 .abcdei(0, 1, 2, 3, 4, 60),
216 .abcdei(4, 0, 1, 2, 3, 61),
217 .abcdei(3, 4, 0, 1, 2, 62),
218 .abcdei(2, 3, 4, 0, 1, 63),
219 .abcdei(1, 2, 3, 4, 0, 64),
220 .abcdei(0, 1, 2, 3, 4, 65),
221 .abcdei(4, 0, 1, 2, 3, 66),
222 .abcdei(3, 4, 0, 1, 2, 67),
223 .abcdei(2, 3, 4, 0, 1, 68),
224 .abcdei(1, 2, 3, 4, 0, 69),
225 .abcdei(0, 1, 2, 3, 4, 70),
226 .abcdei(4, 0, 1, 2, 3, 71),
227 .abcdei(3, 4, 0, 1, 2, 72),
228 .abcdei(2, 3, 4, 0, 1, 73),
229 .abcdei(1, 2, 3, 4, 0, 74),
230 .abcdei(0, 1, 2, 3, 4, 75),
231 .abcdei(4, 0, 1, 2, 3, 76),
232 .abcdei(3, 4, 0, 1, 2, 77),
233 .abcdei(2, 3, 4, 0, 1, 78),
234 .abcdei(1, 2, 3, 4, 0, 79),
235 };
236 inline for (round3) |r| {
237 const t = s[(r.i - 3) & 0xf] ^ s[(r.i - 8) & 0xf] ^ s[(r.i - 14) & 0xf] ^ s[(r.i - 16) & 0xf];
238 s[r.i & 0xf] = math.rotl(u32, t, @as(u32, 1));
239
240 v[r.e] = v[r.e] +% math.rotl(u32, v[r.a], @as(u32, 5)) +% 0xCA62C1D6 +% s[r.i & 0xf] +% (v[r.b] ^ v[r.c] ^ v[r.d]);
241 v[r.b] = math.rotl(u32, v[r.b], @as(u32, 30));
242 }
243
244 d.s[0] +%= v[0];
245 d.s[1] +%= v[1];
246 d.s[2] +%= v[2];
247 d.s[3] +%= v[3];
248 d.s[4] +%= v[4];
249}
250
251const RoundParam = struct {
252 a: usize,
253 b: usize,
254 c: usize,
255 d: usize,
256 e: usize,
257 i: u32,
258
259 fn abcdei(a: usize, b: usize, c: usize, d: usize, e: usize, i: u32) RoundParam {
260 return .{
261 .a = a,
262 .b = b,
263 .c = c,
264 .d = d,
265 .e = e,
266 .i = i,
267 };
268 }
269};
270
271const htest = @import("test.zig");
272
273test "sha1 single" {
274 try htest.assertEqualHash(Sha1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", "");
275 try htest.assertEqualHash(Sha1, "a9993e364706816aba3e25717850c26c9cd0d89d", "abc");
276 try htest.assertEqualHash(Sha1, "a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu");
277}
278
279test "sha1 streaming" {
280 var h = Sha1.init(.{});
281 var out: [20]u8 = undefined;
282
283 h.final(&out);
284 try htest.assertEqual("da39a3ee5e6b4b0d3255bfef95601890afd80709", out[0..]);
285
286 h = Sha1.init(.{});
287 h.update("abc");
288 h.final(&out);
289 try htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
290
291 h = Sha1.init(.{});
292 h.update("a");
293 h.update("b");
294 h.update("c");
295 h.final(&out);
296 try htest.assertEqual("a9993e364706816aba3e25717850c26c9cd0d89d", out[0..]);
297}
298
299test "sha1 aligned final" {
300 var block = [_]u8{0} ** Sha1.block_length;
301 var out: [Sha1.digest_length]u8 = undefined;
302
303 var h = Sha1.init(.{});
304 h.update(&block);
305 h.final(out[0..]);
306}