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

C# RSA签名时抛出Key does not exist错误如何排查解决

错误原因排查

1. 直接触发错误的核心原因

客户端使用同一个RSA实例同时存储自身密钥对和服务端公钥:
客户端执行RSA.Create()时会生成自身的公私钥对,后续调用rsa.ImportRSAPublicKey()导入服务端公钥时,会直接覆盖该实例内的原有密钥,导致实例丢失私钥。后续调用rsa.SignData()签名时需要私钥参与,系统找不到私钥就会抛出Key does not exist错误。
服务端也存在同款问题:导入客户端公钥时用了自身原有密钥对的RSA实例,会覆盖自身私钥,后续如果需要签名/解密也会触发相同错误。

2. 其他关联错误

  • 公钥编码不匹配:服务端发送自身公钥时做了Base64编码处理,但客户端收到后直接将Base64字节当做原始公钥导入,必然导入失败,进一步破坏RSA实例状态。
  • 服务端公钥导入逻辑错误:客户端发送的自身公钥是明文未加密状态,服务端错误调用rsa.Decrypt()解密明文公钥,得到乱码后导入公钥必然失败。
  • 网络流读取不可靠:NetworkStream.Read()不保证一次性读取到指定长度的所有数据,你当前的写法大概率会出现数据读取不全的情况,导致公钥解析、长度转换失败。
解决方法

1. 拆分RSA实例

客户端和服务端都分别创建独立的RSA实例,不要共用:

  • 自身密钥实例:用于生成自身密钥对、签名、解密操作,永远不要导入外部公钥覆盖
  • 对方公钥实例:仅用于存储对方公钥,做验签、加密操作

2. 修复公钥编解码逻辑

统一公钥传输编码,接收端做对应解码:如果服务端发送Base64编码的公钥,客户端接收后需要先转UTF8字符串,再做Base64解码得到原始公钥字节再导入。

3. 移除无意义的解密操作

服务端导入客户端公钥时,直接用收到的明文公钥字节导入即可,不要调用Decrypt方法。

4. 封装可靠的网络流读取方法

保证每次读取到指定长度的完整数据,示例如下:

public static byte[] ReadExact(NetworkStream stream, int length)
{
    byte[] buffer = new byte[length];
    int totalRead = 0;
    while (totalRead < length)
    {
        int read = stream.Read(buffer, totalRead, length - totalRead);
        if (read == 0) throw new IOException("连接已断开");
        totalRead += read;
    }
    return buffer;
}

修改后的核心代码示例

客户端代码

// 客户端自身密钥对实例,用于签名
RSA clientRsa = RSA.Create();
// 存储服务端公钥的实例,单独创建
RSA serverPubRsa = RSA.Create();

IPEndPoint end = new IPEndPoint(IPAddress.Loopback, 500);
Socket sock = new Socket(end.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
sock.Connect(end);
NetworkStream ns = new NetworkStream(sock);

// 读服务端公钥长度
byte[] lenBuffer = ReadExact(ns, 1024); // 假设长度字符串不超过1024字节,也可以固定长度传输长度
int pubKeyLen = int.Parse(Encoding.UTF8.GetString(lenBuffer).Trim('\0'));
// 读服务端Base64编码的公钥
byte[] pubKeyBase64Bytes = ReadExact(ns, pubKeyLen);
string pubKeyBase64 = Encoding.UTF8.GetString(pubKeyBase64Bytes);
byte[] serverPubKey = Convert.FromBase64String(pubKeyBase64);
// 导入到单独的服务端公钥实例
int a;
serverPubRsa.ImportRSAPublicKey(serverPubKey, out a);

// 发送客户端自身公钥(明文,无需加密)
byte[] clientPubKey = clientRsa.ExportRSAPublicKey();
ns.Write(Encoding.UTF8.GetBytes(clientPubKey.Length.ToString()));
ns.Write(clientPubKey);

// 用客户端自身的RSA实例签名,不会丢失私钥
byte[] signature = clientRsa.SignData(Encoding.UTF8.GetBytes("h"), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
ns.Write(Encoding.UTF8.GetBytes(signature.Length.ToString()));
ns.Write(signature);

服务端代码

// 服务端自身密钥对实例,不要导入外部公钥
RSA serverRsa = RSA.Create();
// 存储客户端公钥的实例
RSA clientPubRsa = RSA.Create();

byte[] bytesFrom = new byte[8192];
NetworkStream networkStream = clientSocket.GetStream();
// 发送服务端公钥,Base64编码
string pubKeyBase64 = Convert.ToBase64String(serverRsa.ExportRSAPublicKey());
networkStream.Write(Encoding.UTF8.GetBytes(pubKeyBase64.Length.ToString()));
networkStream.Write(Encoding.UTF8.GetBytes(pubKeyBase64));

// 读客户端公钥长度
byte[] lenBuffer = ReadExact(networkStream, 1024);
int clientPubLen = int.Parse(Encoding.UTF8.GetString(lenBuffer).Trim('\0'));
// 读客户端公钥明文
byte[] clientPubKey = ReadExact(networkStream, clientPubLen);
// 直接导入到客户端公钥实例,不要解密
int a;
clientPubRsa.ImportRSAPublicKey(clientPubKey, out a);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 15:18:04