crypto加密-实战篇之非对称加密

生成密钥对(node服务端)

我们使用 node 内置的 crypto 模块即可,其说明如下

crypto.generateKeyPairSync(algorithm,options);

第一个参数algorithm:指定要生成的密钥对的算法类型。

第二个参数options:一个配置对象,用于指定生成密钥对时的具体参数。对于 RSA 密钥对,常见的配置选项包括:
modulusLength:必填,指定 RSA 密钥的模长(以位为单位)。常见的值有 1024、2048 和 4096。模长越大,安全性越高,但计算成本也越高。
publicExponent:非必填,指定公钥的指数。通常使用 65537(即十六进制的 0x10001),这是一个常用的公钥指数,因为它在计算上相对高效。
hash:非必填,指定哈希算法。对于 RSA-PSS,可以指定哈希算法,如 'sha256'。

通常情况下,我们会将产出的密钥对 对象转为字符串 并保存到文件,以便于持久化储存和传输!

const genKeyPair = () => {
  // 生成密钥对
  const keyPair = crypto.generateKeyPairSync("rsa", {
    modulusLength: 1024,
  });

  // 将密钥对转换为PEM格式的字符串
  const publicKeyStr = keyPair.publicKey
    .export({ type: "spki", format: "pem" })
    .toString();
  const privateKeyStr = keyPair.privateKey
    .export({ type: "pkcs8", format: "pem" })
    .toString();
  
  // 将密钥对字符串保存到文件中
  fs.outputFileSync("key-pair/publicKey.pem", publicKeyStr);
  fs.outputFileSync("key-pair/privateKey.pem", privateKeyStr);
};
genKeyPair();

关于导出一些参数解释:
type :参数指定了密钥的编码类型,即密钥的具体结构和内容。如spki/pkcs8 用于表示一种标准公钥和私钥信息格式,包含了密钥的算法标识符和密钥本身,其他type有的则不包含密钥算法标识 或 其他特性等等。
format:指定了密钥的输出方式。如字符串的 pem 和二进制的der等等!
注意导出参数是必填项!

使用公钥加密数据(web客户端)

转换为前端可用的密钥格式

前端在在拿到生成 pem 公钥文件(字符串)的时候,需要将其转换为前端的 公钥格式,即CryptoKey对象!

// 从(生成的)PEM内容(字符串)生成前端标准RSA公钥(CryptoKey对象)
const genPublicKeyFromPemStr = async (pemText) => {
    // 移除PEM头尾和换行符
    const pemHeader = "-----BEGIN PUBLIC KEY-----";
    const pemFooter = "-----END PUBLIC KEY-----";
    const pemContents = pemText
    .replace(pemHeader, "")
    .replace(pemFooter, "")
    .replace(/\s/g, "");

    // 将公钥字符串Base64解码
    const binaryDer = Uint8Array.from(atob(pemContents), (c) =>
    c.charCodeAt(0)
    );

    // 导入编码后的公钥注意,要和生成以及后端解密的算法参数保持一致)
    return await crypto.subtle.importKey(
    "spki",
    binaryDer,
    {
        name: "RSA-OAEP",
        hash: "SHA-256", // 指定内容的哈希算法
    },
    false,
    ["encrypt"]
    );
};


// 封装一个通用方法,加载文件
const loadFile = (filePath) => {
    const response = fetch(filePath).then((res) => res.text());
    return response;
};

// 这里就拿到了前端标准的密钥(此处是公钥)对象
 const publicKey = await genPublicKeyFromPemStr(pemStr);

这里解释下 crypto.subtle.importKey 的用法,注意 一定要和生成方以及解密方 ,三者保持一致

crypto.subtle.importKey(
    format, // 密钥的格式
    keyData, // 密钥数据
    algorithm, // 密钥的算法信息
    extractable, // 密钥是否可导出
    keyUsages // 密钥的用途
);

加密方法封装

在 Web Crypto API 中,加密和解密操作通常需要使用 ArrayBuffer 或其视图(如 Uint8Array)作为输入和输出,而不是直接使用字符串。这是因为加密算法在底层处理的是二进制数据,而不是文本数据。字符串需要先被转换为二进制格式,才能进行加密操作。

// 使用公钥加密
const encrypt = async (data, publicKey)=> {
    // 将内容字符串转换为ArrayBuffer,因为不支持字符串直接加密
    const encoder = new TextEncoder();
    const dataBuffer = encoder.encode(data);

    // 加密(注意,要和生成以及后端解密的算法参数保持一致)
    const encrypted = await crypto.subtle.encrypt(
        { name: "RSA-OAEP" },
        publicKey,
        dataBuffer
    );

    // 为了便于加密后数据的传输,我们还需将ArrayBuffer转换为Base64字符串
    const encryptedArray = new Uint8Array(encrypted);
    const base64String = btoa(String.fromCharCode(...encryptedArray));
    return base64String;
}

加密个数据试试

<input type="text" />
    <button>加密数据</button>

    <div class="encrypted-data">
      <p>加密后的数据:</p>
      <p class="encrypted-data-content"></p>
    </div>

    <script>
      // ...省略前几个方法

      const input = document.querySelector("input");
      const button = document.querySelector("button");
      const encryptedDataContent = document.querySelector(
        ".encrypted-data-content"
      );

      button.addEventListener("click", async () => {
        const pemStr = await loadFile("./key-pair/publicKey.pem");
        const publicKey = await genPublicKeyFromPemStr(pemStr);
        const content = input.value;
        const encryptedData = await encrypt(content, publicKey);
        encryptedDataContent.innerText = encryptedData;
      });
    </script>

400

使用私钥解密数据(node服务端)

// 使用私钥解密
export const decrypt = (encryptedData) => {
  let privateKey = fs.readFileSync("key-pair/privateKey.pem", "utf8");
  // 将PEM格式的私钥字符串转换为KeyObject
  privateKey = crypto.createPrivateKey(privateKey);
 // 把前端传过来的 base64 字符串还原为buffer(加解密只能操作文件,而非字符串)
  const buffer = Buffer.from(encryptedData, "base64");
  // 开始解密 - 指定与前端相同的填充方式和哈希算法
  const decrypted = crypto.privateDecrypt(
    {
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: "sha256", // // 指定内容的哈希算法(与前端的保持一致)
    },
    buffer
  );
  return decrypted.toString("utf8");
};

600

posted @ 2025-08-24 18:21  丁少华  阅读(11)  评论(0)    收藏  举报