寻求纯JavaScript环境下的密码学安全DRNG方案
原生JavaScript实现密码学安全确定性随机数生成器(HMAC_DRBG)
核心方案:基于Web Crypto的HMAC_DRBG
HMAC_DRBG是NIST标准化的密码学安全确定性随机数生成器,完全可以通过Web Crypto API原生实现,不需要额外依赖。它能满足你的需求:只要共享种子一致,多客户端就能生成完全相同的输出,且具备密码学安全性。
实现思路
利用Web Crypto提供的HMAC算法实现HMAC_DRBG的核心逻辑,遵循NIST SP 800-90A规范的基本流程:
- 初始化:用安全共享的种子初始化HMAC_DRBG的状态。
- 生成:通过HMAC迭代生成所需长度的随机字节序列。
- 结合Web Crypto的deriveKey:将生成的随机字节作为密钥材料,导入为AES密钥,或直接作为
deriveKey的输入派生密钥。
原生实现代码
class HMACDRBG { /** * 初始化HMAC_DRBG * @param {ArrayBuffer} seed 共享种子(至少32字节,推荐对应SHA-256长度) * @param {string} hashAlgorithm 哈希算法,默认SHA-256 */ constructor(seed, hashAlgorithm = 'SHA-256') { this.hashAlgorithm = hashAlgorithm; this.seed = seed; this.state = null; this.reseedCounter = 1; this._initialize(); } async _initialize() { // 初始化HMAC密钥 const hmacKey = await crypto.subtle.importKey( 'raw', this.seed, { name: 'HMAC', hash: this.hashAlgorithm }, false, ['sign'] ); // 生成初始状态:V = 0x01重复哈希长度次,K = HMAC(K, V || 0x00 || seed) const hashLength = this.hashAlgorithm === 'SHA-256' ? 32 : 64; // SHA-512对应64字节 const V = new Uint8Array(hashLength).fill(0x01); const temp = new Uint8Array([...V, 0x00, ...new Uint8Array(this.seed)]); const K = await crypto.subtle.sign('HMAC', hmacKey, temp); this.state = { K, V }; } /** * 生成指定长度的随机字节 * @param {number} length 需要生成的字节数(单次最大1024,超过自动分批次) * @returns {Promise<ArrayBuffer>} 生成的随机字节 */ async generate(length) { if (length > 1024) { const chunks = []; let remaining = length; while (remaining > 0) { const chunkSize = Math.min(remaining, 1024); chunks.push(await this._generateChunk(chunkSize)); remaining -= chunkSize; } return this._concatArrayBuffers(chunks); } return this._generateChunk(length); } async _generateChunk(length) { const { K, V } = this.state; const hashLength = this.hashAlgorithm === 'SHA-256' ? 32 : 64; const outputChunks = []; let totalBytes = 0; // 生成足够的输出字节 while (totalBytes < length) { // V = HMAC(K, V) const newV = await crypto.subtle.sign('HMAC', await this._getHMACKey(K), V); outputChunks.push(newV); totalBytes += hashLength; this.state.V = newV; } // 截取需要的长度 const fullOutput = this._concatArrayBuffers(outputChunks); const truncatedOutput = fullOutput.slice(0, length); // 更新状态:K = HMAC(K, V || 0x01) const temp = new Uint8Array([...new Uint8Array(this.state.V), 0x01]); const newK = await crypto.subtle.sign('HMAC', await this._getHMACKey(K), temp); this.state.K = newK; this.reseedCounter += 1; return truncatedOutput; } async _getHMACKey(keyData) { return crypto.subtle.importKey( 'raw', keyData, { name: 'HMAC', hash: this.hashAlgorithm }, false, ['sign'] ); } _concatArrayBuffers(buffers) { const totalLength = buffers.reduce((sum, buf) => sum + buf.byteLength, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const buf of buffers) { result.set(new Uint8Array(buf), offset); offset += buf.byteLength; } return result.buffer; } } // 使用示例1:直接生成共享AES密钥 async function generateSharedAESKey(sharedSeed) { // 初始化DRBG(种子需安全共享,至少32字节) const drbg = new HMACDRBG(sharedSeed); // 生成AES-256所需的32字节密钥材料 const keyMaterial = await drbg.generate(32); // 导入为可用于加密解密的AES密钥 const aesKey = await crypto.subtle.importKey( 'raw', keyMaterial, { name: 'AES-GCM' }, true, ['encrypt', 'decrypt'] ); return aesKey; } // 使用示例2:结合deriveKey派生AES密钥 async function deriveAESKeyFromDRBG(sharedSeed, salt) { const drbg = new HMACDRBG(sharedSeed); const derivedInput = await drbg.generate(32); const aesKey = await crypto.subtle.deriveKey( { name: 'PBKDF2', salt: salt, iterations: 100000, hash: 'SHA-256' }, await crypto.subtle.importKey('raw', derivedInput, { name: 'PBKDF2' }, false, ['deriveKey']), { name: 'AES-GCM', length: 256 }, true, ['encrypt', 'decrypt'] ); return aesKey; }
关键注意事项
- 种子安全性:必须通过安全信道(如TLS加密API、端到端加密)传输共享种子,种子长度至少32字节,避免使用弱种子。
- 算法选择:示例中使用SHA-256,若需更高安全性可改用SHA-512,只需调整代码中的哈希长度参数。
- 兼容性:所有主流浏览器(Chrome、Firefox、Safari)和Node.js(需启用Web Crypto)都支持该实现,无需额外依赖。
- 合规性:HMAC_DRBG符合NIST SP 800-90A标准,满足密码学安全要求,适合生成密钥材料。
内容的提问来源于stack exchange,提问作者Randusr
相关产品推荐
相关产品推荐

