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

Node.js(TypeScript)中如何实现单密钥加密、多密钥解密?

需求说明

需要实现单密钥加密、多把独立密钥均可解密同一份密文的功能,当前基于Node.js内置crypto库开发,使用aes-256-ctr算法,原有代码仅支持加解密使用相同密钥,无法满足多密钥解密的业务要求,开发环境为Node.js + TypeScript,原有代码如下:

const crypto = require("crypto");
const algorithm = "aes-256-ctr";
const secretKey = "vOVH6sdmpNWjRRIqCc7rdxs01lwHzfr3";

encrypt = (text: string) => {

let iv = crypto.randomBytes(16);
let cipher = crypto.createCipheriv(algorithm, secretKey, iv);
let encrypted: any = Buffer.concat([cipher.update(text), 
cipher.final()]);
let hash: any = {
iv: iv.toString("hex"),
content: encrypted.toString("hex"),
 };
  return hash;
  };

  decrypt = (hash: any) => {
  if (hash.iv) {
  const decipher =     
  crypto.createDecipheriv(algorithm,
  secretKey,Buffer.from(hash?.iv, 
  "hex"));

  const decrpyted = Buffer.concat([
  decipher.update(Buffer.from(hash.content, 
  "hex")),decipher.final(),]);
   return decrpyted.toString();
   }
   };
实现方案

对称加密的原生逻辑就是加解密必须使用相同密钥,不存在直接配置多把解密密钥的AES模式,要实现需求最稳妥、性能最好的方案是采用数字信封结构,核心逻辑如下:

  • 每次加密新数据时,首先随机生成一个一次性的32字节AES数据密钥,用这个数据密钥走原有aes-256-ctr逻辑加密明文,得到密文和对应iv
  • 遍历所有允许解密该密文的业务密钥,每把业务密钥单独加密这个随机生成的数据密钥,生成对应的数据密钥密文副本,和明文密文、iv存在一起
  • 解密时,拿当前持有的业务密钥,逐个尝试解密存储的数据密钥副本,只要能成功解密出数据密钥,就可以用这个数据密钥解出原始明文

这个方案的优势是原有AES加解密逻辑几乎不需要改动,额外开销只有32字节长度的数据密钥的多次加密,性能损耗可以忽略;同时每段密文使用独立的随机数据密钥,符合密码学安全规范,不会出现一钥泄露全量密文崩溃的问题。

改造后代码
import crypto from "crypto";
const algorithm = "aes-256-ctr";
const KEY_LENGTH = 32; // aes-256要求密钥长度为32字节

interface EncryptResult {
  iv: string;
  content: string;
  encryptedDataKeys: Array<{
    keyIv: string;
    encryptedKey: string;
  }>;
}

const encrypt = (text: string, allowedDecryptKeys: string[]): EncryptResult => {
  // 生成本次加密专用的一次性数据密钥
  const dataKey = crypto.randomBytes(KEY_LENGTH);
  // 用数据密钥加密明文,和原有逻辑完全一致
  const iv = crypto.randomBytes(16);
  const cipher = crypto.createCipheriv(algorithm, dataKey, iv);
  const encryptedContent = Buffer.concat([cipher.update(text), cipher.final()]);
  // 用所有授权的业务密钥分别加密数据密钥,生成可解密的密钥副本
  const encryptedDataKeys = allowedDecryptKeys.map((secretKey) => {
    if (Buffer.byteLength(secretKey) !== KEY_LENGTH) {
      throw new Error("密钥长度必须为32字节,适配aes-256算法要求");
    }
    const keyIv = crypto.randomBytes(16);
    const keyCipher = crypto.createCipheriv(algorithm, Buffer.from(secretKey), keyIv);
    const encryptedKey = Buffer.concat([keyCipher.update(dataKey), keyCipher.final()]);
    return {
      keyIv: keyIv.toString("hex"),
      encryptedKey: encryptedKey.toString("hex")
    };
  });

  return {
    iv: iv.toString("hex"),
    content: encryptedContent.toString("hex"),
    encryptedDataKeys
  };
};

const decrypt = (hash: EncryptResult, currentKey: string): string | null => {
  if (!hash.iv || !hash.encryptedDataKeys?.length) return null;
  if (Buffer.byteLength(currentKey) !== KEY_LENGTH) {
    throw new Error("密钥长度必须为32字节,适配aes-256算法要求");
  }
  // 尝试用当前密钥解密数据密钥
  let dataKey: Buffer | null = null;
  for (const encKeyItem of hash.encryptedDataKeys) {
    try {
      const keyDecipher = crypto.createDecipheriv(
        algorithm,
        Buffer.from(currentKey),
        Buffer.from(encKeyItem.keyIv, "hex")
      );
      dataKey = Buffer.concat([
        keyDecipher.update(Buffer.from(encKeyItem.encryptedKey, "hex")),
        keyDecipher.final()
      ]);
      break;
    } catch (e) {
      // 当前密钥不匹配该副本,继续尝试下一个
      continue;
    }
  }
  // 所有副本都解密失败,说明当前密钥无解密权限
  if (!dataKey) return null;
  // 用解密得到的数据密钥解明文,和原有逻辑一致
  const decipher = crypto.createDecipheriv(algorithm, dataKey, Buffer.from(hash.iv, "hex"));
  const decrypted = Buffer.concat([
    decipher.update(Buffer.from(hash.content, "hex")),
    decipher.final()
  ]);
  return decrypted.toString();
};

// 用法示例
const key1 = "vOVH6sdmpNWjRRIqCc7rdxs01lwHzfr3";
const key2 = "xY83kP9sW2qA5dF7gH0jL3zX1cV6bN4m";
const key3 = "pO2iU8yT7rE5wQ9aS4dF6gH3jK1lZ0xC";
// 加密时指定key1、key2为授权解密密钥
const cipherText = encrypt("测试密文内容", [key1, key2]);
console.log(decrypt(cipherText, key1)); // 正常输出明文
console.log(decrypt(cipherText, key2)); // 正常输出明文
console.log(decrypt(cipherText, key3)); // 返回null,无权限
其他可选方案说明
  • 非对称数字信封:如果需要加密后动态新增解密方,可以使用非对称公钥加密数据密钥,持有对应私钥的主体均可解密,但非对称运算性能远低于对称运算,长文本加密场景下效率差,且如果需要不同解密方持有完全独立的密钥,嵌套逻辑复杂度更高
  • Shamir秘密共享:如果需要实现N个密钥中凑够M个即可解密的阈值场景,可以把数据密钥拆成N个分片分发给各解密方,凑够阈值数量的分片即可重组数据密钥解密,该方案实现复杂度高,适合有特殊阈值要求的场景,普通多密钥解密需求不需要用到

内容的提问来源于stack exchange,提问作者fk me

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:33:34