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

RSA客户端与服务端公钥交换实现及加密解密报错排查

问题1 解密失败与CryptographicException异常修复

根因分析

  • RsaEncryption类中RSACryptoServiceProvider csp被声明为静态变量,所有实例共享同一个csp对象。Encrypt方法中执行csp = new RSACryptoServiceProvider()会直接覆盖全局静态csp,导致原本存储的私钥参数失效,解密时导入私钥就会抛出「Key not valid for use in specified state」异常。
  • 服务端HelloController每次接收请求都会实例化新的RsaEncryption对象,等于每次请求都会生成全新的公私钥对,客户端的RsaEncryption实例持有的是自己生成的完全不同的密钥对,解密必然失败。
  • 加密逻辑逻辑不符合非对称加密规则:如果要让服务端加密的内容只有客户端能解密,应该用客户端的公钥加密,而不是服务端自己的公钥加密。用服务端自己的公钥加密的内容,只有服务端自己的私钥能解密,客户端当然解不开。

代码修复

首先修改Rsa加解密类,移除静态csp,支持导入外部公钥:

namespace HttpClientSample
{
    public class RsaEncryption
    {
        // 移除静态csp,每个实例持有自己的密钥参数
        private readonly RSAParameters _privateKey;
        private readonly RSAParameters _publicKey;

        // 无参构造:生成新的公私钥对(生成密钥对时用)
        public RsaEncryption()
        {
            using var csp = new RSACryptoServiceProvider(2048);
            _privateKey = csp.ExportParameters(true);
            _publicKey = csp.ExportParameters(false);
        }

        // 带参构造:导入外部公钥(导入对方公钥时用)
        public RsaEncryption(string publicKeyXml)
        {
            using var sr = new StringReader(publicKeyXml);
            var xs = new XmlSerializer(typeof(RSAParameters));
            _publicKey = (RSAParameters)xs.Deserialize(sr);
        }

        public string GetPublicKeyXml()
        {
            using var sw = new StringWriter();
            var xs = new XmlSerializer(typeof(RSAParameters));
            xs.Serialize(sw, _publicKey);
            return sw.ToString();
        }

        // 用当前实例持有的公钥加密
        public string Encrypt(string plainText)
        {
            using var csp = new RSACryptoServiceProvider();
            csp.ImportParameters(_publicKey);
            var data = Encoding.UTF8.GetBytes(plainText); // 统一用UTF8避免编码问题
            var cipher = csp.Encrypt(data, true); // OAEP填充安全性更高,填false对应PKCS#1 v1.5
            return Convert.ToBase64String(cipher);
        }

        // 用当前实例持有的私钥解密
        public string Decrypt(string cipherText)
        {
            using var csp = new RSACryptoServiceProvider();
            csp.ImportParameters(_privateKey);
            var dataBytes = Convert.FromBase64String(cipherText);
            var plainText = csp.Decrypt(dataBytes, true);
            return Encoding.UTF8.GetString(plainText);
        }
    }
}

服务端修改,全局单例持有Rsa实例,不要每次请求生成新密钥,新增公钥获取接口:

public class HelloController : ApiController
{
    // 全局单例,服务端启动时生成一次密钥对即可
    private static readonly RsaEncryption _serverRsa = new RsaEncryption();

    [HttpGet]
    [Route("api/publickey")]
    public string GetPublicKey()
    {
        return _serverRsa.GetPublicKeyXml();
    }

    public string Get()
    {
        string ret = "Hello World";
        // 加密需要用客户端公钥,验签需要用服务端私钥,根据业务场景选择
        return ret;
    }
}

客户端修改,启动时先初始化公钥:

// 客户端全局Rsa实例,启动时初始化
private static RsaEncryption _clientRsa;

// 客户端启动时先调用该方法获取服务端公钥
static async Task InitRsaAsync()
{
    var publicKey = await client.GetStringAsync("api/publickey");
    _clientRsa = new RsaEncryption(publicKey);
}

static async Task<string> GetProductAsync(string path)
{
    string product = null;
    HttpResponseMessage response = await client.GetAsync(path);
    if (response.IsSuccessStatusCode)
    {
        product = await response.Content.ReadAsStringAsync();
        // 解密业务内容逻辑根据加密规则调整即可
    }
    return product;
}

问题2 RSA公钥交换原理与落地方案

核心原理

RSA是非对称加密算法,密钥成对出现:

  • 公钥:可以公开分发,任何人都能拿到,用于加密内容或者验证私钥生成的签名
  • 私钥:必须严格保密,只有持有者知道,用于解密对应公钥加密的内容或者生成签名

实现服务端向客户端传输加密内容,两种常用场景的逻辑如下:

场景加密逻辑密钥持有逻辑
内容只有指定客户端能解密服务端用客户端的公钥加密内容客户端自己生成公私钥,提前把自己的公钥同步给服务端
内容要保证是官方服务端发出(防篡改、防伪造)服务端用自己的私钥签名内容,客户端用服务端公钥验签,内容本身用对称加密(AES)加密服务端生成公私钥,客户端提前持有服务端公钥

注意:RSA加密有长度限制,2048位RSA最多加密245字节左右的内容,不要直接用RSA加密业务内容,推荐用混合加密方案:生成随机AES密钥,用AES加密业务内容,再用对方的公钥加密AES密钥,最后把加密后的AES密钥和密文一起发给对方,对方用自己的私钥解密得到AES密钥,再用AES密钥解密密文。

公钥交换落地方案

方案1:预置公钥(最安全,推荐)

服务端提前生成好固定的公私钥对,把公钥XML/PEM格式直接预置到客户端的安装包/代码里,不需要线上传输公钥,完全避免中间人攻击。

方案2:首次请求拉取公钥(灵活,适用非敏感场景)

客户端首次启动时请求服务端的公钥接口拉取公钥,存在本地,后续请求直接用本地存储的公钥。该方案如果没有额外校验,可能被中间人攻击替换公钥,敏感业务不要使用。

方案3:直接使用HTTPS

如果是公网服务,直接用CA颁发的HTTPS证书即可,HTTPS底层已经实现了完整的非对称加密、公钥校验、混合加密逻辑,不需要自行实现RSA加解密。


内容的提问来源于stack exchange,提问作者sshh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.28 20:45:05