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

