如何用JavaScript实现Go语言嵌套HMAC实例的创建?
解决方案:手动实现嵌套HMAC逻辑模拟Go的行为
Go中嵌套调用hmac.New的核心是将内层HMAC实例作为外层HMAC的底层哈希函数,这要求外层HMAC在计算时,用内层HMAC完成标准HMAC流程中的两次哈希操作。由于JavaScript的crypto模块不支持直接传入自定义哈希函数,我们可以手动实现HMAC的通用逻辑,从而支持嵌套的哈希函数(包括HMAC实例)。
核心思路
标准HMAC的计算流程是:
- 处理密钥,若长度超过哈希块大小则先哈希密钥
- 生成ipad(密钥与0x36异或)和opad(密钥与0x5c异或)
- 计算
Hash(ipad || 数据)得到内层结果 - 计算
Hash(opad || 内层结果)得到最终HMAC值
当Hash是另一个HMAC时,只需将上述流程中的Hash替换为内层HMAC的运算即可。
实现代码
1. 基础版(一次性处理数据)
适用于不需要多次调用update的场景:
const crypto = require('crypto'); // 基础SHA256哈希函数 function sha256Hash(data) { return crypto.createHash('sha256').update(data).digest(); } // 通用HMAC实现,支持传入自定义哈希函数 function customHmac(customHash, key, data) { const blockSize = 64; // SHA256的块大小为64字节 let keyBuf = Buffer.from(key); // 密钥过长时先哈希 if (keyBuf.length > blockSize) { keyBuf = customHash(keyBuf); } const ipad = Buffer.alloc(blockSize, 0x36); const opad = Buffer.alloc(blockSize, 0x5c); // 生成ipad和opad for (let i = 0; i < keyBuf.length; i++) { ipad[i] ^= keyBuf[i]; opad[i] ^= keyBuf[i]; } // 计算内层和外层哈希 const innerHash = customHash(Buffer.concat([ipad, data])); return customHash(Buffer.concat([opad, innerHash])); } // 构建嵌套HMAC链 const SALT = 'your-salt-here'; const path = ['path0', 'path1', 'path2']; // 逐层定义嵌套HMAC函数 const innerMostHmac = (data) => customHmac(sha256Hash, SALT, data); const innerHmac = (data) => customHmac(innerMostHmac, path[0], data); const midHmac = (data) => customHmac(innerHmac, path[1], data); const outerHmac = (data) => customHmac(midHmac, path[2], data); // 使用示例 const testData = Buffer.from('test input'); const result = outerHmac(testData); console.log(result.toString('hex'));
2. 完整版(支持多次update,贴近Go接口)
如果需要模拟Go中hash.Hash的Write(对应JS的update)和Sum(对应digest)接口,可以用类和工厂函数实现:
const crypto = require('crypto'); // 创建嵌套HMAC实例的工厂函数,返回符合Hash接口的对象 function createNestedHmac(hashFactory, key) { const blockSize = 64; let keyBuf = Buffer.from(key); // 处理过长密钥:用底层哈希工厂哈希密钥 if (keyBuf.length > blockSize) { const hash = hashFactory(); keyBuf = hash.update(keyBuf).digest(); } const ipad = Buffer.alloc(blockSize, 0x36); const opad = Buffer.alloc(blockSize, 0x5c); for (let i = 0; i < keyBuf.length; i++) { ipad[i] ^= keyBuf[i]; opad[i] ^= keyBuf[i]; } // 返回支持update和digest的实例 return { _buffers: [], update(data) { this._buffers.push(Buffer.from(data)); return this; }, digest() { const combinedData = Buffer.concat(this._buffers); // 计算内层:底层哈希处理ipad+数据 const innerHash = hashFactory().update(Buffer.concat([ipad, combinedData])).digest(); // 计算外层:底层哈希处理opad+内层结果 return hashFactory().update(Buffer.concat([opad, innerHash])).digest(); } }; } // 模拟Go的嵌套创建方式 const SALT = 'your-salt-here'; const path = ['path0', 'path1', 'path2']; // 逐层构建哈希工厂 const innerMostFactory = () => createNestedHmac(() => crypto.createHash('sha256'), SALT); const innerFactory = () => createNestedHmac(innerMostFactory, path[0]); const midFactory = () => createNestedHmac(innerFactory, path[1]); const hmacf = createNestedHmac(midFactory, path[2]); // 使用示例:多次调用update hmacf.update('first part').update('second part'); const result = hmacf.digest(); console.log(result.toString('hex'));
验证一致性
上述代码完全遵循Go中HMAC嵌套的逻辑,无论是一次性数据还是多次更新,输出结果都会和Go版本完全一致。你可以用相同的测试数据对比两者的十六进制输出。
内容的提问来源于stack exchange,提问作者A Farmanbar
相关产品推荐
相关产品推荐

