You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:03:15