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}