|
| 1 | +/** |
| 2 | + * Pure-JS SHA-1 implementation (FIPS 180-4). |
| 3 | + * Replaces the `sha1` npm package to avoid pulling in a Node.js `Buffer` |
| 4 | + * polyfill (~28 KB) when bundled for the browser. |
| 5 | + */ |
| 6 | +/** Compute SHA-1 hex digest of a UTF-8 string (FIPS 180-4). */ |
| 7 | +export function sha1(message: string): string { |
| 8 | + // Encode UTF-8 |
| 9 | + const bytes: number[] = []; |
| 10 | + for (let i = 0; i < message.length; i++) { |
| 11 | + let c = message.charCodeAt(i); |
| 12 | + if (c < 0x80) { |
| 13 | + bytes.push(c); |
| 14 | + } else if (c < 0x800) { |
| 15 | + bytes.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); |
| 16 | + } else if (c >= 0xd800 && c <= 0xdbff && i + 1 < message.length) { |
| 17 | + const next = message.charCodeAt(++i); |
| 18 | + c = 0x10000 + ((c & 0x3ff) << 10) + (next & 0x3ff); |
| 19 | + bytes.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); |
| 20 | + } else { |
| 21 | + bytes.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); |
| 22 | + } |
| 23 | + } |
| 24 | + |
| 25 | + // Pre-processing: pad to 512-bit blocks |
| 26 | + const bitLen = bytes.length * 8; |
| 27 | + bytes.push(0x80); |
| 28 | + while (bytes.length % 64 !== 56) bytes.push(0); |
| 29 | + // Append length as 64-bit big-endian (high 32 bits always 0 for strings < 512 MB) |
| 30 | + bytes.push(0, 0, 0, 0); |
| 31 | + bytes.push((bitLen >>> 24) & 0xff, (bitLen >>> 16) & 0xff, (bitLen >>> 8) & 0xff, bitLen & 0xff); |
| 32 | + |
| 33 | + // Initialize hash values |
| 34 | + let h0 = 0x67452301; |
| 35 | + let h1 = 0xefcdab89; |
| 36 | + let h2 = 0x98badcfe; |
| 37 | + let h3 = 0x10325476; |
| 38 | + let h4 = 0xc3d2e1f0; |
| 39 | + |
| 40 | + // Process each 512-bit block |
| 41 | + const w = new Array<number>(80); |
| 42 | + for (let offset = 0; offset < bytes.length; offset += 64) { |
| 43 | + for (let i = 0; i < 16; i++) { |
| 44 | + w[i] = |
| 45 | + (bytes[offset + i * 4] << 24) | |
| 46 | + (bytes[offset + i * 4 + 1] << 16) | |
| 47 | + (bytes[offset + i * 4 + 2] << 8) | |
| 48 | + bytes[offset + i * 4 + 3]; |
| 49 | + } |
| 50 | + for (let i = 16; i < 80; i++) { |
| 51 | + const n = w[i - 3] ^ w[i - 8] ^ w[i - 14] ^ w[i - 16]; |
| 52 | + w[i] = (n << 1) | (n >>> 31); |
| 53 | + } |
| 54 | + |
| 55 | + let a = h0, |
| 56 | + b = h1, |
| 57 | + c = h2, |
| 58 | + d = h3, |
| 59 | + e = h4; |
| 60 | + |
| 61 | + for (let i = 0; i < 80; i++) { |
| 62 | + let f: number, k: number; |
| 63 | + if (i < 20) { |
| 64 | + f = (b & c) | (~b & d); |
| 65 | + k = 0x5a827999; |
| 66 | + } else if (i < 40) { |
| 67 | + f = b ^ c ^ d; |
| 68 | + k = 0x6ed9eba1; |
| 69 | + } else if (i < 60) { |
| 70 | + f = (b & c) | (b & d) | (c & d); |
| 71 | + k = 0x8f1bbcdc; |
| 72 | + } else { |
| 73 | + f = b ^ c ^ d; |
| 74 | + k = 0xca62c1d6; |
| 75 | + } |
| 76 | + const temp = (((a << 5) | (a >>> 27)) + f + e + k + w[i]) | 0; |
| 77 | + e = d; |
| 78 | + d = c; |
| 79 | + c = (b << 30) | (b >>> 2); |
| 80 | + b = a; |
| 81 | + a = temp; |
| 82 | + } |
| 83 | + |
| 84 | + h0 = (h0 + a) | 0; |
| 85 | + h1 = (h1 + b) | 0; |
| 86 | + h2 = (h2 + c) | 0; |
| 87 | + h3 = (h3 + d) | 0; |
| 88 | + h4 = (h4 + e) | 0; |
| 89 | + } |
| 90 | + |
| 91 | + // Produce hex digest |
| 92 | + let hex = ''; |
| 93 | + for (const h of [h0, h1, h2, h3, h4]) { |
| 94 | + const part = (h >>> 0).toString(16); |
| 95 | + hex += (part.length < 8 ? '00000000'.slice(part.length) : '') + part; |
| 96 | + } |
| 97 | + return hex; |
| 98 | +} |
0 commit comments