C#解密Node.js Crypto RSA加密数据时出现OAEP Padding错误
RSA跨语言加解密兼容问题(C#客户端 + Node.js服务端)
问题现象
搭建了WebSocket通信的C#客户端与Node.js服务端,采用RSA加密传输消息:
- 客户端用公钥加密的数据,服务端可正常解密;
- 服务端用私钥加密的数据,客户端解密时抛出异常:
System.Security.Cryptography.CryptographicException: Error occurred while decoding OAEP padding.
尝试更换编码格式后问题仍未解决。
相关代码
C#客户端代码(Form1.cs)
using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Security; using System; using System.IO; using System.Net.WebSockets; using System.Security.Cryptography; using System.Text; using System.Threading; using System.Windows.Forms; namespace SomethingNamespace { public partial class Form1: Form { private ClientWebSocket ws = new ClientWebSocket(); public Login(string[] args) { InitializeComponent(); } private async void Login_Load(object sender, EventArgs e) { RSACryptoServiceProvider RSA = ImportPublicKey("Something Public Key"); string MessageEncrypted = Convert.ToBase64String(RSA.Encrypt(Encoding.UTF8.GetBytes("Hi I am Client"), true)); MessageBox.Show(MessageEncrypted); Uri serverUri = new Uri("ws://localhost:3000/"); await ws.ConnectAsync(serverUri, CancellationToken.None); ArraySegment<byte> bytesToSend = new ArraySegment<byte>(Encoding.UTF8.GetBytes(MessageEncrypted)); await ws.SendAsync(bytesToSend, WebSocketMessageType.Text, true, CancellationToken.None); while (ws.State == WebSocketState.Open) { ArraySegment<byte> bytesReceived = new ArraySegment<byte>(new byte[2048]); WebSocketReceiveResult result = await ws.ReceiveAsync(bytesReceived, CancellationToken.None); string response = Encoding.UTF8.GetString(bytesReceived.Array, 0, result.Count); MessageBox.Show(response); if (response != "Hi I am Client") { RSA.Decrypt(Convert.FromBase64String(response), true); } } } public static RSACryptoServiceProvider ImportPublicKey(string pem) { PemReader pr = new PemReader(new StringReader(pem)); AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject(); RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey); RSACryptoServiceProvider csp = new RSACryptoServiceProvider(); csp.ImportParameters(rsaParams); return csp; } } }
Node.js服务端代码
index.js
require('dotenv').config(); const express = require('express'); const app = express(); async function initialize() { var server = await require('./server')(app); var ws = await require('./socket')(server); } initialize();
server.js
module.exports = async (app) => { await loadMiddlewares(); await loadRoutes(); await checkDatabase(); return await app.listen(app.get('port'), () => { logger.info(`${process.env.SERVER_NAME} server started on port ${app.get('port')}`); }); };
socket.js
const webSocket = require('ws'); const rsa = require('./modules/rsa') module.exports = (server) => { const ws = new webSocket.Server({ server }); ws.on('connection', (client, req) => { client.id = req.headers['sec-websocket-key']; ws.clients[client.id] = client; client.on('message', (message) => { switch (req.url) { default: console.log('received: %s', message) var received = rsa.privateDecrypt(String(message, 'utf8'), process.env.RSA_PRIVATE_KEY) client.send(received); var sent = rsa.privateEncrypt(received, process.env.RSA_PRIVATE_KEY) client.send(sent); console.log('sent: %s', sent); break; } }); client.on('close', (code, reason) => { delete ws.clients[client.id]; }); }); return ws; };
rsa.js
const crypto = require('crypto'); exports.privateEncrypt = (data, key) => { const buffer = Buffer.from(data); const encrypted = crypto.privateEncrypt(key, buffer); return encrypted.toString("base64"); } exports.privateDecrypt = (data, key) => { const buffer = Buffer.from(data, "base64"); const decrypted = crypto.privateDecrypt(key, buffer); return decrypted.toString("utf8"); } exports.publicEncrypt = (data, key) => { const buffer = Buffer.from(data); const encrypted = crypto.publicEncrypt(key, buffer); return encrypted.toString("base64"); } exports.publicDecrypt = (data, key) => { const buffer = Buffer.from(data, "base64"); const decrypted = crypto.publicDecrypt(key, buffer); return decrypted.toString("utf8"); }
问题根源
- 加解密逻辑不符合RSA标准:RSA的正确用法是:
- 加密传输:公钥加密,私钥解密;
- 身份验证/防篡改:私钥签名,公钥验签。
当前服务端用私钥加密、客户端用公钥解密的逻辑属于误用,且跨语言下填充模式默认不匹配。
- 填充模式不兼容:C#中
RSACryptoServiceProvider.Encrypt(true)使用OAEP填充,而Node.js的crypto.privateEncrypt默认使用PKCS#1 v1.5填充,两者不匹配导致解密时出现OAEP填充解码错误。
修复方案
方案1:修正加解密逻辑(推荐)
遵循RSA标准流程:
- 客户端→服务端:客户端用服务端公钥加密消息,服务端用自身私钥解密(当前已正常工作);
- 服务端→客户端:服务端用客户端公钥加密消息,客户端用自身私钥解密;若用于身份验证,服务端用私钥签名,客户端用公钥验签。
方案2:统一填充模式(仅用于兼容现有错误逻辑)
若必须保持服务端私钥加密、客户端公钥解密的逻辑,需两边统一使用OAEP填充:
修改Node.js服务端rsa.js
const crypto = require('crypto'); exports.privateEncrypt = (data, key) => { const buffer = Buffer.from(data); // 指定OAEP填充,与C#的Encrypt(true)匹配 const encrypted = crypto.privateEncrypt( { key: key, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, buffer ); return encrypted.toString("base64"); } exports.privateDecrypt = (data, key) => { const buffer = Buffer.from(data, "base64"); const decrypted = crypto.privateDecrypt(key, buffer); return decrypted.toString("utf8"); } exports.publicEncrypt = (data, key) => { const buffer = Buffer.from(data); const encrypted = crypto.publicEncrypt(key, buffer); return encrypted.toString("base64"); } exports.publicDecrypt = (data, key) => { const buffer = Buffer.from(data, "base64"); // 指定OAEP填充,用于解密服务端私钥加密的数据 const decrypted = crypto.publicDecrypt( { key: key, padding: crypto.constants.RSA_PKCS1_OAEP_PADDING }, buffer ); return decrypted.toString("utf8"); }
C#客户端代码确认
客户端RSA.Decrypt(Convert.FromBase64String(response), true)已使用OAEP填充,无需修改,确保导入的是服务端对应的公钥即可。
额外注意事项
- RSA仅适合加密小数据(如对称密钥),大消息建议先用AES等对称加密算法加密,再用RSA加密对称密钥;
- 确保两边使用的密钥格式一致(标准PEM格式,无多余换行或空格);
- C#中推荐使用
RSA.Create()替代旧的RSACryptoServiceProvider,兼容性更好。
内容的提问来源于stack exchange,提问作者Qwerty
相关产品推荐
相关产品推荐

