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.
相关产品推荐
相关产品推荐

