| 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 | |
| 5 | const std = @import("../std.zig"); |
| 6 | const mem = std.mem; |
| 7 | const math = std.math; |
| 8 | const Sha1 = @This(); |
| 9 | |
| 10 | pub const block_length = 64; |
| 11 | pub const digest_length = 20; |
| 12 | pub const Options = struct {}; |
| 13 | |
| 14 | s: [5]u32, |
| 15 | /// Streaming Cache |
| 16 | buf: [64]u8 = undefined, |
| 17 | buf_len: u8 = 0, |
| 18 | total_len: u64 = 0, |
| 19 | |
| 20 | pub 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 | |
| 33 | pub 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 | |
| 39 | pub 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 | |
| 63 | pub fn peek(d: Sha1) [digest_length]u8 { |
| 64 | var copy = d; |
| 65 | return copy.finalResult(); |
| 66 | } |
| 67 | |
| 68 | pub 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 | |
| 98 | pub fn finalResult(d: *Sha1) [digest_length]u8 { |
| 99 | var result: [digest_length]u8 = undefined; |
| 100 | d.final(&result); |
| 101 | return result; |
| 102 | } |
| 103 | |
| 104 | fn 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 | |
| 251 | const 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 | |
| 271 | const htest = @import("test.zig"); |
| 272 | |
| 273 | test "sha1 single" { |
| 274 | try htest.assertEqualHash(Sha1, "da39a3ee5e6b4b0d3255bfef95601890afd80709", ""); |
| 275 | try htest.assertEqualHash(Sha1, "a9993e364706816aba3e25717850c26c9cd0d89d", "abc"); |
| 276 | try htest.assertEqualHash(Sha1, "a49b2446a02c645bf419f995b67091253a04a259", "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu"); |
| 277 | } |
| 278 | |
| 279 | test "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 | |
| 299 | test "sha1 aligned final" { |
| 300 | var block: [Sha1.block_length]u8 = @splat(0); |
| 301 | var out: [Sha1.digest_length]u8 = undefined; |
| 302 | |
| 303 | var h = Sha1.init(.{}); |
| 304 | h.update(&block); |
| 305 | h.final(out[0..]); |
| 306 | } |