如何用Node.js crypto库实现与openssl passwd -6等效的SHA256密码哈希?
实现与
openssl passwd -6等效的Node.js加密函数 先逐个纠正你的假设:
- 应使用
pbkdf2函数:错误。openssl passwd -6采用的是SHA-512crypt算法,而非PBKDF2,这是结果不一致的核心原因。 - 输出采用base64编码:错误。它使用OpenSSL自定义的Base64变种,字符集为
./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz,且不使用填充符=,编码逻辑也和标准Base64有差异。 - 迭代次数为10000:正确。
openssl passwd -6默认迭代次数就是10000。 - 密钥长度为64:正确。SHA-512crypt生成的哈希为64字节(512位),但编码方式和标准Base64不同。
正确实现代码
Node.js原生crypto库没有直接提供SHA-512crypt的API,需要手动模拟该算法的流程,以下是完整的TypeScript实现:
import * as crypto from "node:crypto"; // SHA-512crypt专属的Base64字符集 const CRYPT_BASE64_CHARS = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; // 自定义Base64编码函数,适配SHA-512crypt格式 function cryptBase64Encode(buffer: Buffer): string { let result = ""; let remaining = buffer.length; let i = 0; while (remaining > 0) { // 每次处理3字节,拆解为4个6位分组 let chunk = 0; chunk |= (buffer[i] & 0xff) << 16; if (remaining > 1) chunk |= (buffer[i+1] & 0xff) << 8; if (remaining > 2) chunk |= buffer[i+2] & 0xff; // 映射到自定义字符集 result += CRYPT_BASE64_CHARS[(chunk >> 18) & 0x3f]; result += CRYPT_BASE64_CHARS[(chunk >> 12) & 0x3f]; if (remaining > 1) result += CRYPT_BASE64_CHARS[(chunk >> 6) & 0x3f]; if (remaining > 2) result += CRYPT_BASE64_CHARS[chunk & 0x3f]; i += 3; remaining -= 3; } // 移除因字节数不足产生的多余字符 if (buffer.length % 3 === 1) { result = result.slice(0, -2); } else if (buffer.length % 3 === 2) { result = result.slice(0, -1); } return result; } export async function sha512Crypt(password: string, salt: string, rounds: number = 10000): Promise<string> { const passwordBuf = Buffer.from(password, "utf8"); const saltBuf = Buffer.from(salt, "utf8"); // 步骤1:计算初始哈希 let h = crypto.createHash("sha512"); h.update(passwordBuf); h.update(saltBuf); h.update(passwordBuf); const H0 = h.digest(); // 步骤2:迭代计算核心哈希 let currentHash = H0; for (let i = 0; i < rounds; i++) { const hash = crypto.createHash("sha512"); // 奇数轮与偶数轮的输入顺序不同 if (i % 2 === 1) { hash.update(passwordBuf); hash.update(currentHash); } else { hash.update(currentHash); } hash.update(saltBuf); // 根据当前迭代次数的二进制中1的个数,重复追加密码 const oneCount = i.toString(2).split("1").length - 1; for (let j = 0; j < oneCount; j++) { hash.update(passwordBuf); } currentHash = hash.digest(); } // 步骤3:混合初始哈希与迭代哈希,生成最终结果 const finalHash = Buffer.alloc(64); for (let i = 0; i < 64; i++) { finalHash[i] = H0[i] ^ currentHash[i]; } // 生成符合openssl格式的字符串 const prefix = rounds === 10000 ? "$6$" : `$6$rounds=${rounds}$`; return `${prefix}${salt}$${cryptBase64Encode(finalHash)}`; }
测试验证
当密码为my-password、盐为saltytest时,调用await sha512Crypt("my-password", "saltytest")生成的结果,会与执行openssl passwd -6 -salt saltytest my-password得到的输出完全一致。
内容的提问来源于stack exchange,提问作者d_inevitable
相关产品推荐
相关产品推荐

