Files
go-jdenticon/jdenticon-js/src/common/sha1.js
Kevin McIntyre f84b511895 init
2025-06-18 01:00:00 -04:00

133 lines
4.1 KiB
JavaScript

/**
* Jdenticon
* https://github.com/dmester/jdenticon
* Copyright © Daniel Mester Pirttijärvi
*/
/**
* Computes a SHA1 hash for any value and returns it as a hexadecimal string.
*
* This function is optimized for minimal code size and rather short messages.
*
* @param {string} message
*/
export function sha1(message) {
const HASH_SIZE_HALF_BYTES = 40;
const BLOCK_SIZE_WORDS = 16;
// Variables
// `var` is used to be able to minimize the number of `var` keywords.
var i = 0,
f = 0,
// Use `encodeURI` to UTF8 encode the message without any additional libraries
// We could use `unescape` + `encodeURI` to minimize the code, but that would be slightly risky
// since `unescape` is deprecated.
urlEncodedMessage = encodeURI(message) + "%80", // trailing '1' bit padding
// This can be changed to a preallocated Uint32Array array for greater performance and larger code size
data = [],
dataSize,
hashBuffer = [],
a = 0x67452301,
b = 0xefcdab89,
c = ~a,
d = ~b,
e = 0xc3d2e1f0,
hash = [a, b, c, d, e],
blockStartIndex = 0,
hexHash = "";
/**
* Rotates the value a specified number of bits to the left.
* @param {number} value Value to rotate
* @param {number} shift Bit count to shift.
*/
function rotl(value, shift) {
return (value << shift) | (value >>> (32 - shift));
}
// Message data
for ( ; i < urlEncodedMessage.length; f++) {
data[f >> 2] = data[f >> 2] |
(
(
urlEncodedMessage[i] == "%"
// Percent encoded byte
? parseInt(urlEncodedMessage.substring(i + 1, i += 3), 16)
// Unencoded byte
: urlEncodedMessage.charCodeAt(i++)
)
// Read bytes in reverse order (big endian words)
<< ((3 - (f & 3)) * 8)
);
}
// f is now the length of the utf8 encoded message
// 7 = 8 bytes (64 bit) for message size, -1 to round down
// >> 6 = integer division with block size
dataSize = (((f + 7) >> 6) + 1) * BLOCK_SIZE_WORDS;
// Message size in bits.
// SHA1 uses a 64 bit integer to represent the size, but since we only support short messages only the least
// significant 32 bits are set. -8 is for the '1' bit padding byte.
data[dataSize - 1] = f * 8 - 8;
// Compute hash
for ( ; blockStartIndex < dataSize; blockStartIndex += BLOCK_SIZE_WORDS) {
for (i = 0; i < 80; i++) {
f = rotl(a, 5) + e + (
// Ch
i < 20 ? ((b & c) ^ ((~b) & d)) + 0x5a827999 :
// Parity
i < 40 ? (b ^ c ^ d) + 0x6ed9eba1 :
// Maj
i < 60 ? ((b & c) ^ (b & d) ^ (c & d)) + 0x8f1bbcdc :
// Parity
(b ^ c ^ d) + 0xca62c1d6
) + (
hashBuffer[i] = i < BLOCK_SIZE_WORDS
// Bitwise OR is used to coerse `undefined` to 0
? (data[blockStartIndex + i] | 0)
: rotl(hashBuffer[i - 3] ^ hashBuffer[i - 8] ^ hashBuffer[i - 14] ^ hashBuffer[i - 16], 1)
);
e = d;
d = c;
c = rotl(b, 30);
b = a;
a = f;
}
hash[0] = a = ((hash[0] + a) | 0);
hash[1] = b = ((hash[1] + b) | 0);
hash[2] = c = ((hash[2] + c) | 0);
hash[3] = d = ((hash[3] + d) | 0);
hash[4] = e = ((hash[4] + e) | 0);
}
// Format hex hash
for (i = 0; i < HASH_SIZE_HALF_BYTES; i++) {
hexHash += (
(
// Get word (2^3 half-bytes per word)
hash[i >> 3] >>>
// Append half-bytes in reverse order
((7 - (i & 7)) * 4)
)
// Clamp to half-byte
& 0xf
).toString(16);
}
return hexHash;
}