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

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");
}

问题根源

  1. 加解密逻辑不符合RSA标准:RSA的正确用法是:
    • 加密传输:公钥加密,私钥解密;
    • 身份验证/防篡改:私钥签名,公钥验签。
      当前服务端用私钥加密、客户端用公钥解密的逻辑属于误用,且跨语言下填充模式默认不匹配。
  2. 填充模式不兼容: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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.05 12:25:22