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

React中基于C#传递的RSA公钥参数重构并加密失败求助

解决C# RSA公钥在React中用JSEncrypt加密失败的问题

问题根源

你直接将C#导出的Modulus和Exponent的Base64字符串拼接后传给setPublicKey,这不符合JSEncrypt要求的公钥格式。JSEncrypt需要的是PEM格式的RSA公钥(即包含-----BEGIN PUBLIC KEY-----和-----END PUBLIC KEY-----头部尾部,中间是Base64编码的ASN.1结构),而非原始参数的简单拼接。

解决方案

方案1:后端直接导出PEM格式公钥(推荐)

修改C#代码,直接生成标准PEM格式的公钥传给前端,避免前端手动组装的复杂度。

方法A:使用BouncyCastle库(简单可靠)

安装BouncyCastle NuGet包后,通过以下代码生成PEM公钥:

using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Parameters;
using Org.BouncyCastle.OpenSsl;
using System.IO;

using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
{
    // 转换为BouncyCastle兼容的RSA参数
    var bcParams = new RsaKeyParameters(false, 
        new Org.BouncyCastle.Math.BigInteger(1, rsa.ExportParameters(false).Modulus),
        new Org.BouncyCastle.Math.BigInteger(1, rsa.ExportParameters(false).Exponent));
    
    // 导出为PEM格式字符串
    using (var sw = new StringWriter())
    {
        var pemWriter = new PemWriter(sw);
        pemWriter.WriteObject(bcParams);
        string pemPublicKey = sw.ToString();
        // 将pemPublicKey传给前端即可
    }
}

方法B:手动构造PEM(无需第三方库)

如果不想依赖第三方库,可手动将Modulus和Exponent组装成符合ASN.1规范的结构,再转换为PEM:

using System;
using System.Text;

public static string RsaParametersToPem(RSAParameters parameters)
{
    var modulusBytes = parameters.Modulus;
    var exponentBytes = parameters.Exponent;

    // ASN.1 Integer编码(最高位为1时补前导零)
    var modulusEncoded = EncodeInteger(modulusBytes);
    var exponentEncoded = EncodeInteger(exponentBytes);

    // 组装RSA公钥序列
    var rsaPublicKeySequence = ConcatBytes(
        new byte[] { 0x30 },
        LengthBytes(modulusEncoded.Length + exponentEncoded.Length + 2),
        modulusEncoded,
        exponentEncoded
    );

    // 组装SubjectPublicKeyInfo结构
    var oidSequence = ConcatBytes(
        new byte[] { 0x30, 0x0D },
        new byte[] { 0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, // RSA算法OID
        new byte[] { 0x05, 0x00 }
    );

    var subjectPublicKeyInfo = ConcatBytes(
        new byte[] { 0x30 },
        LengthBytes(oidSequence.Length + rsaPublicKeySequence.Length + 2),
        oidSequence,
        new byte[] { 0x03 },
        LengthBytes(rsaPublicKeySequence.Length + 1),
        new byte[] { 0x00 }, // 未使用位
        rsaPublicKeySequence
    );

    // 转换为PEM格式
    string base64 = Convert.ToBase64String(subjectPublicKeyInfo, Base64FormattingOptions.InsertLineBreaks);
    return $"-----BEGIN PUBLIC KEY-----\n{base64}\n-----END PUBLIC KEY-----";
}

private static byte[] EncodeInteger(byte[] value)
{
    if (value[0] >= 0x80)
    {
        return ConcatBytes(new byte[] { 0x00 }, value);
    }
    return value;
}

private static byte[] LengthBytes(int length)
{
    if (length < 0x80)
    {
        return new byte[] { (byte)length };
    }
    var lengthBytes = BitConverter.GetBytes(length);
    Array.Reverse(lengthBytes);
    int bytesCount = lengthBytes.Length;
    while (bytesCount > 0 && lengthBytes[bytesCount - 1] == 0)
    {
        bytesCount--;
    }
    var result = new byte[bytesCount + 1];
    result[0] = (byte)(0x80 + bytesCount);
    Array.Copy(lengthBytes, lengthBytes.Length - bytesCount, result, 1, bytesCount);
    return result;
}

private static byte[] ConcatBytes(params byte[][] arrays)
{
    int totalLength = 0;
    foreach (var array in arrays) totalLength += array.Length;
    var result = new byte[totalLength];
    int offset = 0;
    foreach (var array in arrays)
    {
        Array.Copy(array, 0, result, offset, array.Length);
        offset += array.Length;
    }
    return result;
}

生成密钥时调用RsaParametersToPem方法,将返回的PEM字符串传给前端即可。

方案2:前端组装符合格式的公钥

如果后端无法修改,前端可将Modulus和Exponent的Base64字符串转换为标准PEM格式:

const encryptMessage = (messageToEncrypt) => {
  if (publicKeyParts?.length === 2) {
    const encrypt = new JSEncrypt();
    // publicKeyParts[0]为Exponent的Base64,publicKeyParts[1]为Modulus的Base64
    const exponent = base64ToArrayBuffer(publicKeyParts[0]);
    const modulus = base64ToArrayBuffer(publicKeyParts[1]);

    const pemPublicKey = buildRsaPemPublicKey(modulus, exponent);
    encrypt.setPublicKey(pemPublicKey);

    const data = { payload: messageToEncrypt };
    const encryptedPayload = encrypt.encrypt(JSON.stringify(data));
    return encryptedPayload;
  }
};

// Base64转ArrayBuffer
function base64ToArrayBuffer(base64) {
  const binaryString = window.atob(base64);
  const bytes = new Uint8Array(binaryString.length);
  for (let i = 0; i < binaryString.length; i++) {
    bytes[i] = binaryString.charCodeAt(i);
  }
  return bytes;
}

// 组装RSA公钥的PEM格式
function buildRsaPemPublicKey(modulus, exponent) {
  function encodeInteger(bytes) {
    if (bytes[0] >= 0x80) {
      const newBytes = new Uint8Array(bytes.length + 1);
      newBytes[0] = 0x00;
      newBytes.set(bytes, 1);
      return newBytes;
    }
    return bytes;
  }

  function getLengthBytes(length) {
    if (length < 0x80) return new Uint8Array([length]);
    const lengthBytes = new Uint8Array(4);
    for (let i = 3; i >= 0; i--) {
      lengthBytes[i] = length & 0xff;
      length >>= 8;
      if (length === 0) {
        return new Uint8Array([0x80 + (4 - i), ...lengthBytes.slice(i)]);
      }
    }
    return new Uint8Array([0x84, ...lengthBytes]);
  }

  const encodedModulus = encodeInteger(modulus);
  const encodedExponent = encodeInteger(exponent);

  // 组装RSA公钥序列
  const rsaSeqLength = encodedModulus.length + encodedExponent.length;
  const rsaSeq = new Uint8Array([
    0x30,
    ...getLengthBytes(rsaSeqLength),
    ...encodedModulus,
    ...encodedExponent
  ]);

  // 组装SubjectPublicKeyInfo结构
  const oidSeq = new Uint8Array([
    0x30, 0x0D,
    0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01,
    0x05, 0x00
  ]);

  const spkiLength = oidSeq.length + rsaSeq.length + 2;
  const spki = new Uint8Array([
    0x30,
    ...getLengthBytes(spkiLength),
    ...oidSeq,
    0x03,
    ...getLengthBytes(rsaSeq.length + 1),
    0x00,
    ...rsaSeq
  ]);

  // 转换为PEM格式
  const base64 = btoa(String.fromCharCode(...spki));
  const formattedBase64 = base64.match(/.{1,64}/g).join('\n');
  return `-----BEGIN PUBLIC KEY-----\n${formattedBase64}\n-----END PUBLIC KEY-----`;
}

验证要点

  • 确保后端传递的Modulus和Exponent是原始字节数组直接转Base64,无额外编码或截断。
  • 前端组装PEM后,可通过RSA格式验证工具确认公钥结构是否正确。
  • 确保使用最新稳定版的JSEncrypt库。

内容的提问来源于stack exchange,提问作者Jorge A.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 18:54:53