const { bytesToBase64, bytesToBin, bytesToHex, stringToUtf8Bytes, toByteArray } = require('../../utils/binary-utils.js') const MASK_64 = 0xFFFFFFFFFFFFFFFFn const HASH_ALGORITHM_PRESETS = [ { key: 'md5', label: 'MD5', kind: 'hash', hash: 'md5' }, { key: 'sha1', label: 'SHA1', kind: 'hash', hash: 'sha1' }, { key: 'sha224', label: 'SHA224', kind: 'hash', hash: 'sha224' }, { key: 'sha256', label: 'SHA256', kind: 'hash', hash: 'sha256' }, { key: 'sha384', label: 'SHA384', kind: 'hash', hash: 'sha384' }, { key: 'sha512', label: 'SHA512', kind: 'hash', hash: 'sha512' }, { key: 'hmac-md5', label: 'HMAC-MD5', kind: 'hmac', hash: 'md5' }, { key: 'hmac-sha1', label: 'HMAC-SHA1', kind: 'hmac', hash: 'sha1' }, { key: 'hmac-sha224', label: 'HMAC-SHA224', kind: 'hmac', hash: 'sha224' }, { key: 'hmac-sha256', label: 'HMAC-SHA256', kind: 'hmac', hash: 'sha256' }, { key: 'hmac-sha384', label: 'HMAC-SHA384', kind: 'hmac', hash: 'sha384' }, { key: 'hmac-sha512', label: 'HMAC-SHA512', kind: 'hmac', hash: 'sha512' }, { key: 'pbkdf2', label: 'PBKDF2', kind: 'pbkdf2', hash: 'sha256' } ] const SHA256_K = [ 0x428A2F98, 0x71374491, 0xB5C0FBCF, 0xE9B5DBA5, 0x3956C25B, 0x59F111F1, 0x923F82A4, 0xAB1C5ED5, 0xD807AA98, 0x12835B01, 0x243185BE, 0x550C7DC3, 0x72BE5D74, 0x80DEB1FE, 0x9BDC06A7, 0xC19BF174, 0xE49B69C1, 0xEFBE4786, 0x0FC19DC6, 0x240CA1CC, 0x2DE92C6F, 0x4A7484AA, 0x5CB0A9DC, 0x76F988DA, 0x983E5152, 0xA831C66D, 0xB00327C8, 0xBF597FC7, 0xC6E00BF3, 0xD5A79147, 0x06CA6351, 0x14292967, 0x27B70A85, 0x2E1B2138, 0x4D2C6DFC, 0x53380D13, 0x650A7354, 0x766A0ABB, 0x81C2C92E, 0x92722C85, 0xA2BFE8A1, 0xA81A664B, 0xC24B8B70, 0xC76C51A3, 0xD192E819, 0xD6990624, 0xF40E3585, 0x106AA070, 0x19A4C116, 0x1E376C08, 0x2748774C, 0x34B0BCB5, 0x391C0CB3, 0x4ED8AA4A, 0x5B9CCA4F, 0x682E6FF3, 0x748F82EE, 0x78A5636F, 0x84C87814, 0x8CC70208, 0x90BEFFFA, 0xA4506CEB, 0xBEF9A3F7, 0xC67178F2 ] const SHA512_K = [ 0x428A2F98D728AE22n, 0x7137449123EF65CDn, 0xB5C0FBCFEC4D3B2Fn, 0xE9B5DBA58189DBBCn, 0x3956C25BF348B538n, 0x59F111F1B605D019n, 0x923F82A4AF194F9Bn, 0xAB1C5ED5DA6D8118n, 0xD807AA98A3030242n, 0x12835B0145706FBEn, 0x243185BE4EE4B28Cn, 0x550C7DC3D5FFB4E2n, 0x72BE5D74F27B896Fn, 0x80DEB1FE3B1696B1n, 0x9BDC06A725C71235n, 0xC19BF174CF692694n, 0xE49B69C19EF14AD2n, 0xEFBE4786384F25E3n, 0x0FC19DC68B8CD5B5n, 0x240CA1CC77AC9C65n, 0x2DE92C6F592B0275n, 0x4A7484AA6EA6E483n, 0x5CB0A9DCBD41FBD4n, 0x76F988DA831153B5n, 0x983E5152EE66DFABn, 0xA831C66D2DB43210n, 0xB00327C898FB213Fn, 0xBF597FC7BEEF0EE4n, 0xC6E00BF33DA88FC2n, 0xD5A79147930AA725n, 0x06CA6351E003826Fn, 0x142929670A0E6E70n, 0x27B70A8546D22FFCn, 0x2E1B21385C26C926n, 0x4D2C6DFC5AC42AEDn, 0x53380D139D95B3DFn, 0x650A73548BAF63DEn, 0x766A0ABB3C77B2A8n, 0x81C2C92E47EDAEE6n, 0x92722C851482353Bn, 0xA2BFE8A14CF10364n, 0xA81A664BBC423001n, 0xC24B8B70D0F89791n, 0xC76C51A30654BE30n, 0xD192E819D6EF5218n, 0xD69906245565A910n, 0xF40E35855771202An, 0x106AA07032BBD1B8n, 0x19A4C116B8D2D0C8n, 0x1E376C085141AB53n, 0x2748774CDF8EEB99n, 0x34B0BCB5E19B48A8n, 0x391C0CB3C5C95A63n, 0x4ED8AA4AE3418ACBn, 0x5B9CCA4F7763E373n, 0x682E6FF3D6B2B8A3n, 0x748F82EE5DEFB2FCn, 0x78A5636F43172F60n, 0x84C87814A1F0AB72n, 0x8CC702081A6439ECn, 0x90BEFFFA23631E28n, 0xA4506CEBDE82BDE9n, 0xBEF9A3F7B2C67915n, 0xC67178F2E372532Bn, 0xCA273ECEEA26619Cn, 0xD186B8C721C0C207n, 0xEADA7DD6CDE0EB1En, 0xF57D4F7FEE6ED178n, 0x06F067AA72176FBAn, 0x0A637DC5A2C898A6n, 0x113F9804BEF90DAEn, 0x1B710B35131C471Bn, 0x28DB77F523047D84n, 0x32CAAB7B40C72493n, 0x3C9EBE0A15C9BEBCn, 0x431D67C49C100D4Cn, 0x4CC5D4BECB3E42B6n, 0x597F299CFC657E2An, 0x5FCB6FAB3AD6FAECn, 0x6C44198C4A475817n ] function add32(...values) { return values.reduce((sum, value) => (sum + (value >>> 0)) >>> 0, 0) } function rotl32(value, bits) { return ((value << bits) | (value >>> (32 - bits))) >>> 0 } function rotr32(value, bits) { return ((value >>> bits) | (value << (32 - bits))) >>> 0 } function writeWord32BE(value, output) { output.push((value >>> 24) & 0xFF, (value >>> 16) & 0xFF, (value >>> 8) & 0xFF, value & 0xFF) } function writeWord32LE(value, output) { output.push(value & 0xFF, (value >>> 8) & 0xFF, (value >>> 16) & 0xFF, (value >>> 24) & 0xFF) } function padBlock64BE(bytes) { const message = toByteArray(bytes) const bitLength = BigInt(message.length) * 8n const padded = message.slice() padded.push(0x80) while (padded.length % 64 !== 56) padded.push(0) for (let shift = 56; shift >= 0; shift -= 8) { padded.push(Number((bitLength >> BigInt(shift)) & 0xFFn)) } return padded } function md5(bytes) { const shifts = [ 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 ] const constants = [] const message = toByteArray(bytes) const bitLength = BigInt(message.length) * 8n const padded = message.slice() for (let index = 0; index < 64; index += 1) { constants[index] = Math.floor(Math.abs(Math.sin(index + 1)) * 0x100000000) >>> 0 } padded.push(0x80) while (padded.length % 64 !== 56) padded.push(0) for (let shift = 0; shift < 64; shift += 8) { padded.push(Number((bitLength >> BigInt(shift)) & 0xFFn)) } let a0 = 0x67452301 let b0 = 0xEFCDAB89 let c0 = 0x98BADCFE let d0 = 0x10325476 for (let offset = 0; offset < padded.length; offset += 64) { const words = [] for (let index = 0; index < 16; index += 1) { const base = offset + index * 4 words[index] = ( (padded[base] & 0xFF) | ((padded[base + 1] & 0xFF) << 8) | ((padded[base + 2] & 0xFF) << 16) | ((padded[base + 3] & 0xFF) << 24) ) >>> 0 } let a = a0 let b = b0 let c = c0 let d = d0 for (let index = 0; index < 64; index += 1) { let f let g if (index < 16) { f = (b & c) | ((~b) & d) g = index } else if (index < 32) { f = (d & b) | ((~d) & c) g = (5 * index + 1) % 16 } else if (index < 48) { f = b ^ c ^ d g = (3 * index + 5) % 16 } else { f = c ^ (b | (~d)) g = (7 * index) % 16 } const next = d d = c c = b b = add32(b, rotl32(add32(a, f, constants[index], words[g]), shifts[index])) a = next } a0 = add32(a0, a) b0 = add32(b0, b) c0 = add32(c0, c) d0 = add32(d0, d) } const output = [] ;[a0, b0, c0, d0].forEach((word) => writeWord32LE(word, output)) return output } function sha1(bytes) { const padded = padBlock64BE(bytes) const words = [] let h0 = 0x67452301 let h1 = 0xEFCDAB89 let h2 = 0x98BADCFE let h3 = 0x10325476 let h4 = 0xC3D2E1F0 for (let offset = 0; offset < padded.length; offset += 64) { for (let index = 0; index < 16; index += 1) { const base = offset + index * 4 words[index] = ( ((padded[base] & 0xFF) << 24) | ((padded[base + 1] & 0xFF) << 16) | ((padded[base + 2] & 0xFF) << 8) | (padded[base + 3] & 0xFF) ) >>> 0 } for (let index = 16; index < 80; index += 1) { words[index] = rotl32(words[index - 3] ^ words[index - 8] ^ words[index - 14] ^ words[index - 16], 1) } let a = h0 let b = h1 let c = h2 let d = h3 let e = h4 for (let index = 0; index < 80; index += 1) { let f let k if (index < 20) { f = (b & c) | ((~b) & d) k = 0x5A827999 } else if (index < 40) { f = b ^ c ^ d k = 0x6ED9EBA1 } else if (index < 60) { f = (b & c) | (b & d) | (c & d) k = 0x8F1BBCDC } else { f = b ^ c ^ d k = 0xCA62C1D6 } const temp = add32(rotl32(a, 5), f, e, k, words[index]) e = d d = c c = rotl32(b, 30) b = a a = temp } h0 = add32(h0, a) h1 = add32(h1, b) h2 = add32(h2, c) h3 = add32(h3, d) h4 = add32(h4, e) } const output = [] ;[h0, h1, h2, h3, h4].forEach((word) => writeWord32BE(word, output)) return output } function sha256Family(bytes, mode) { const padded = padBlock64BE(bytes) const words = [] const hash = mode === 'sha224' ? [0xC1059ED8, 0x367CD507, 0x3070DD17, 0xF70E5939, 0xFFC00B31, 0x68581511, 0x64F98FA7, 0xBEFA4FA4] : [0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19] for (let offset = 0; offset < padded.length; offset += 64) { for (let index = 0; index < 16; index += 1) { const base = offset + index * 4 words[index] = ( ((padded[base] & 0xFF) << 24) | ((padded[base + 1] & 0xFF) << 16) | ((padded[base + 2] & 0xFF) << 8) | (padded[base + 3] & 0xFF) ) >>> 0 } for (let index = 16; index < 64; index += 1) { const s0 = rotr32(words[index - 15], 7) ^ rotr32(words[index - 15], 18) ^ (words[index - 15] >>> 3) const s1 = rotr32(words[index - 2], 17) ^ rotr32(words[index - 2], 19) ^ (words[index - 2] >>> 10) words[index] = add32(words[index - 16], s0, words[index - 7], s1) } let a = hash[0] let b = hash[1] let c = hash[2] let d = hash[3] let e = hash[4] let f = hash[5] let g = hash[6] let h = hash[7] for (let index = 0; index < 64; index += 1) { const s1 = rotr32(e, 6) ^ rotr32(e, 11) ^ rotr32(e, 25) const ch = (e & f) ^ ((~e) & g) const temp1 = add32(h, s1, ch, SHA256_K[index], words[index]) const s0 = rotr32(a, 2) ^ rotr32(a, 13) ^ rotr32(a, 22) const maj = (a & b) ^ (a & c) ^ (b & c) const temp2 = add32(s0, maj) h = g g = f f = e e = add32(d, temp1) d = c c = b b = a a = add32(temp1, temp2) } hash[0] = add32(hash[0], a) hash[1] = add32(hash[1], b) hash[2] = add32(hash[2], c) hash[3] = add32(hash[3], d) hash[4] = add32(hash[4], e) hash[5] = add32(hash[5], f) hash[6] = add32(hash[6], g) hash[7] = add32(hash[7], h) } const output = [] hash.slice(0, mode === 'sha224' ? 7 : 8).forEach((word) => writeWord32BE(word, output)) return output } function rotr64(value, bits) { const shift = BigInt(bits) return ((value >> shift) | (value << (64n - shift))) & MASK_64 } function writeWord64BE(value, output) { for (let shift = 56; shift >= 0; shift -= 8) { output.push(Number((value >> BigInt(shift)) & 0xFFn)) } } function readWord64BE(bytes, offset) { let value = 0n for (let index = 0; index < 8; index += 1) { value = (value << 8n) | BigInt(bytes[offset + index] & 0xFF) } return value } function padBlock128BE(bytes) { const message = toByteArray(bytes) const bitLength = BigInt(message.length) * 8n const padded = message.slice() padded.push(0x80) while (padded.length % 128 !== 112) padded.push(0) for (let shift = 120; shift >= 0; shift -= 8) { padded.push(Number((bitLength >> BigInt(shift)) & 0xFFn)) } return padded } function sha512Family(bytes, mode) { const padded = padBlock128BE(bytes) const words = [] const hash = mode === 'sha384' ? [ 0xCBBB9D5DC1059ED8n, 0x629A292A367CD507n, 0x9159015A3070DD17n, 0x152FECD8F70E5939n, 0x67332667FFC00B31n, 0x8EB44A8768581511n, 0xDB0C2E0D64F98FA7n, 0x47B5481DBEFA4FA4n ] : [ 0x6A09E667F3BCC908n, 0xBB67AE8584CAA73Bn, 0x3C6EF372FE94F82Bn, 0xA54FF53A5F1D36F1n, 0x510E527FADE682D1n, 0x9B05688C2B3E6C1Fn, 0x1F83D9ABFB41BD6Bn, 0x5BE0CD19137E2179n ] for (let offset = 0; offset < padded.length; offset += 128) { for (let index = 0; index < 16; index += 1) { words[index] = readWord64BE(padded, offset + index * 8) } for (let index = 16; index < 80; index += 1) { const s0 = rotr64(words[index - 15], 1) ^ rotr64(words[index - 15], 8) ^ (words[index - 15] >> 7n) const s1 = rotr64(words[index - 2], 19) ^ rotr64(words[index - 2], 61) ^ (words[index - 2] >> 6n) words[index] = (words[index - 16] + s0 + words[index - 7] + s1) & MASK_64 } let a = hash[0] let b = hash[1] let c = hash[2] let d = hash[3] let e = hash[4] let f = hash[5] let g = hash[6] let h = hash[7] for (let index = 0; index < 80; index += 1) { const s1 = rotr64(e, 14) ^ rotr64(e, 18) ^ rotr64(e, 41) const ch = (e & f) ^ ((MASK_64 ^ e) & g) const temp1 = (h + s1 + ch + SHA512_K[index] + words[index]) & MASK_64 const s0 = rotr64(a, 28) ^ rotr64(a, 34) ^ rotr64(a, 39) const maj = (a & b) ^ (a & c) ^ (b & c) const temp2 = (s0 + maj) & MASK_64 h = g g = f f = e e = (d + temp1) & MASK_64 d = c c = b b = a a = (temp1 + temp2) & MASK_64 } hash[0] = (hash[0] + a) & MASK_64 hash[1] = (hash[1] + b) & MASK_64 hash[2] = (hash[2] + c) & MASK_64 hash[3] = (hash[3] + d) & MASK_64 hash[4] = (hash[4] + e) & MASK_64 hash[5] = (hash[5] + f) & MASK_64 hash[6] = (hash[6] + g) & MASK_64 hash[7] = (hash[7] + h) & MASK_64 } const output = [] hash.slice(0, mode === 'sha384' ? 6 : 8).forEach((word) => writeWord64BE(word, output)) return output } function digestBytes(hash, bytes) { if (hash === 'md5') return md5(bytes) if (hash === 'sha1') return sha1(bytes) if (hash === 'sha224') return sha256Family(bytes, 'sha224') if (hash === 'sha256') return sha256Family(bytes, 'sha256') if (hash === 'sha384') return sha512Family(bytes, 'sha384') if (hash === 'sha512') return sha512Family(bytes, 'sha512') throw new Error('不支持的哈希算法') } function getBlockSize(hash) { return hash === 'sha384' || hash === 'sha512' ? 128 : 64 } function hmacBytes(hash, keyBytes, dataBytes) { const blockSize = getBlockSize(hash) let key = toByteArray(keyBytes) if (key.length > blockSize) { key = digestBytes(hash, key) } while (key.length < blockSize) key.push(0) const innerPad = [] const outerPad = [] for (let index = 0; index < blockSize; index += 1) { innerPad[index] = key[index] ^ 0x36 outerPad[index] = key[index] ^ 0x5C } return digestBytes(hash, outerPad.concat(digestBytes(hash, innerPad.concat(toByteArray(dataBytes))))) } function parsePositiveInteger(value, label, fallback, maxValue) { const numberValue = Number(value || fallback) if (!Number.isInteger(numberValue) || numberValue <= 0) { throw new Error(`${label}需为正整数`) } if (numberValue > maxValue) { throw new Error(`${label}不能超过 ${maxValue}`) } return numberValue } function pbkdf2Bytes(hash, passwordBytes, saltBytes, iterations, outputLength) { const rounds = parsePositiveInteger(iterations, '迭代次数', 1000, 100000) const length = parsePositiveInteger(outputLength, '输出长度', 32, 4096) const hashLength = digestBytes(hash, []).length const blockCount = Math.ceil(length / hashLength) const output = [] for (let blockIndex = 1; blockIndex <= blockCount; blockIndex += 1) { const blockIndexBytes = [ (blockIndex >>> 24) & 0xFF, (blockIndex >>> 16) & 0xFF, (blockIndex >>> 8) & 0xFF, blockIndex & 0xFF ] let previous = hmacBytes(hash, passwordBytes, toByteArray(saltBytes).concat(blockIndexBytes)) const block = previous.slice() for (let round = 1; round < rounds; round += 1) { previous = hmacBytes(hash, passwordBytes, previous) for (let index = 0; index < block.length; index += 1) { block[index] ^= previous[index] } } output.push(...block) } return output.slice(0, length) } function getPreset(key) { return HASH_ALGORITHM_PRESETS.find((preset) => preset.key === key) || HASH_ALGORITHM_PRESETS[0] } function calculateHash(bytes, config = {}) { const preset = getPreset(config.key) const dataBytes = toByteArray(bytes) let resultBytes if (preset.kind === 'hmac') { resultBytes = hmacBytes(preset.hash, stringToUtf8Bytes(config.hmacKey || ''), dataBytes) } else if (preset.kind === 'pbkdf2') { resultBytes = pbkdf2Bytes( preset.hash, dataBytes, stringToUtf8Bytes(config.pbkdf2Salt || ''), config.pbkdf2Iterations, config.pbkdf2Length ) } else { resultBytes = digestBytes(preset.hash, dataBytes) } return { base64: bytesToBase64(resultBytes), bin: bytesToBin(resultBytes), bytes: resultBytes, hex: bytesToHex(resultBytes), width: resultBytes.length * 8 } } module.exports = { HASH_ALGORITHM_PRESETS, bytesToBase64, bytesToBin, bytesToHex, calculateHash, digestBytes, hmacBytes, pbkdf2Bytes, stringToUtf8Bytes, toByteArray }