非对称加密实现及私钥注册表存储问题排查
非对称加密解密密钥不一致导致解密失败问题排查
我正在实现一个非对称加密功能:用户在第一个窗体加密数据,关闭后打开第二个窗体查看解密结果。为此写了一个供两个窗体调用的Encryption类,但现在遇到问题:解密时用的私钥和加密时的私钥不一致,导致byte[] decryptedBytes = rsa.Decrypt(encrypteddata, false);抛出“参数不正确”错误。请帮忙排查代码问题,确认实现方式是否有误。
private RSACryptoServiceProvider rsa; private RSAParameters _privateKey; private RSAParameters _publicKey; private static bool keysGenerated; public Encryption() { rsa = new RSACryptoServiceProvider(); } public bool RegistryKeyVerification(string RegistyKeyName, string RegistryKeyValueName) { try { RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistyKeyName); if (key == null) { keysGenerated = false; } else { keysGenerated = true; } } catch (Exception ex) { MessageBox.Show(ex.Message); } return keysGenerated; } public void EncryptionKeysGeneration(string RegistyKeyName, string RegistryKeyValueName) { bool regkeyvalver = RegistryKeyVerification(RegistyKeyName, RegistryKeyValueName); if (regkeyvalver == true) { try { RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistyKeyName); object keyvalue = key.GetValue(RegistryKeyValueName); if (keyvalue == null || keyvalue is byte[]) { GenerateandSaveEncryptionKeys(RegistyKeyName, RegistryKeyValueName); } } catch (Exception ex) { MessageBox.Show(ex.Message); } } } public void GenerateandSaveEncryptionKeys(string RegistyKeyName, string RegistryKeyValueName) { try { _publicKey = rsa.ExportParameters(false); _privateKey = rsa.ExportParameters(true); RSAParameters privatekey = _privateKey; // Saving the private key into the registry: SavePrivateKeytoRegistry(privatekey, RegistyKeyName, RegistryKeyValueName); } catch (Exception ex) { MessageBox.Show(ex.Message); } } public string Encrypt(string data, string RegistyKeyName, string RegistryKeyValueName) { try { RSAParameters publickey = _publicKey; string privateKeyXmlString = LoadPrivateKeyfromRegistry(RegistyKeyName, RegistryKeyValueName); RSAParameters privatekey = DecryptPrivateKey(privateKeyXmlString); // Assign the decrypted private key to the RSA instance rsa.ImportParameters(privatekey); byte[] databytes = Encoding.UTF8.GetBytes(data); byte[] encryptedData = rsa.Encrypt(databytes, false); return Convert.ToBase64String(encryptedData); } catch (Exception ex) { MessageBox.Show(ex.Message); } throw new InvalidOperationException("Data was not able to be encrypted"); } public string Decrypt(string data, string RegistyKeyName, string RegistryKeyValueName) { try { string privateKeyXmlString = LoadPrivateKeyfromRegistry(RegistyKeyName, RegistryKeyValueName); RSAParameters privatekey = DecryptPrivateKey(privateKeyXmlString); var encrypteddata = Convert.FromBase64String(data); rsa.ImportParameters(privatekey); byte[] decryptedBytes = rsa.Decrypt(encrypteddata, false); string decryptedText = Encoding.UTF8.GetString(decryptedBytes); return decryptedText; } catch (Exception ex) { MessageBox.Show(ex.Message); } throw new InvalidOperationException("Data was not able to be decrypted"); } public void SavePrivateKeytoRegistry(RSAParameters privatekey, string RegistyKeyName, string RegistryKeyValueName) { try { RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistyKeyName, true); string privateKeyXml = PrivateKeyToXmlString(privatekey); byte[] privateKeyBytes = Encoding.UTF8.GetBytes(privateKeyXml); byte[] encryptedPrivateKeyBytes = ProtectedData.Protect(privateKeyBytes, null, DataProtectionScope.CurrentUser); key.SetValue(RegistryKeyValueName, encryptedPrivateKeyBytes, RegistryValueKind.Binary); key.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message); } } private string PrivateKeyToXmlString(RSAParameters privateKey) { rsa.ImportParameters(privateKey); return rsa.ToXmlString(true); } public string LoadPrivateKeyfromRegistry(string RegistyKeyName, string RegistryKeyValueName) { RegistryKey key = Registry.CurrentUser.OpenSubKey(RegistyKeyName, true); if (key != null) { RegistryValueKind valueKind = key.GetValueKind(RegistryKeyValueName); if (valueKind == RegistryValueKind.Binary) { byte[] encryptedPrivateKeyBytes = (byte[])key.GetValue(RegistryKeyValueName); byte[] decryptedPrivateKeyBytes = ProtectedData.Unprotect(encryptedPrivateKeyBytes, null, DataProtectionScope.CurrentUser); string privateKeyXmlString = Encoding.UTF8.GetString(decryptedPrivateKeyBytes); return privateKeyXmlString; } else if (valueKind == RegistryValueKind.String) { string privateKeyString = (string)key.GetValue(RegistryKeyValueName); byte[] privateKeyBytes = Encoding.UTF8.GetBytes(privateKeyString); byte[] decryptedPrivateKeyBytes = ProtectedData.Unprotect(privateKeyBytes, null, DataProtectionScope.CurrentUser); string privateKeyXmlString = Encoding.UTF8.GetString(decryptedPrivateKeyBytes); return privateKeyXmlString; } } key.Close(); throw new InvalidOperationException("Invalid registry value kind."); } public RSAParameters DecryptPrivateKey(string privateKeyXmlString) { var privateKeyParams = new RSAParameters(); var xmlDoc = new XmlDocument(); xmlDoc.LoadXml(privateKeyXmlString); if (xmlDoc.DocumentElement != null && xmlDoc.DocumentElement.Name.Equals("RSAKeyValue")) { foreach (XmlNode node in xmlDoc.DocumentElement.ChildNodes) { switch (node.Name) { case "Modulus": privateKeyParams.Modulus = Convert.FromBase64String(node.InnerText); break; case "Exponent": privateKeyParams.Exponent = Convert.FromBase64String(node.InnerText); break; case "P": privateKeyParams.P = Convert.FromBase64String(node.InnerText); break; case "Q": privateKeyParams.Q = Convert.FromBase64String(node.InnerText); break; case "DP": privateKeyParams.DP = Convert.FromBase64String(node.InnerText); break; case "DQ": privateKeyParams.DQ = Convert.FromBase64String(node.InnerText); break; case "InverseQ": privateKeyParams.InverseQ = Convert.FromBase64String(node.InnerText); break; case "D": privateKeyParams.D = Convert.FromBase64String(node.InnerText); break; } } } return privateKeyParams; }
问题根源梳理
- 加密逻辑完全错误:非对称加密的核心是公钥加密、私钥解密,但当前
Encrypt方法却加载私钥并用于加密操作,不仅违背基本逻辑,还会因Encryption类每次实例化都生成新密钥对,导致加密与解密密钥不匹配。 - 密钥存储加载冗余:手动实现XML密钥解析容易出错,
RSACryptoServiceProvider本身提供FromXmlString方法可直接加载XML格式密钥。 - 密钥生成逻辑反向:当前代码在注册表项存在时才生成密钥,实际应该是注册表无密钥时才生成存储。
- 静态变量状态混乱:
keysGenerated作为静态变量,多窗体实例共享时会导致状态判断错误。 - RSA实例重复初始化:每次实例化
Encryption都会生成新RSA实例,密钥状态无法跨窗体保持一致。
修复后的代码实现
核心修正点
- 严格遵循公钥加密、私钥解密逻辑
- 简化密钥存储加载,使用原生XML密钥方法
- 修正密钥生成触发条件:注册表无密钥时才生成
- 移除冗余手动XML解析
- 避免静态变量状态问题
using System; using System.Security.Cryptography; using System.Text; using Microsoft.Win32; using System.Windows.Forms; public class Encryption { private RSACryptoServiceProvider _rsa; private const string PublicKeyXmlKey = "PublicKeyXml"; private const string PrivateKeyXmlKey = "PrivateKeyXml"; public Encryption() { _rsa = new RSACryptoServiceProvider(); } // 检查注册表中是否已存在密钥对 private bool HasStoredKeys(string registrySubKey) { try { using (RegistryKey key = Registry.CurrentUser.OpenSubKey(registrySubKey)) { if (key == null) return false; return key.GetValue(PublicKeyXmlKey) != null && key.GetValue(PrivateKeyXmlKey) != null; } } catch (Exception ex) { MessageBox.Show($"检查密钥存储失败: {ex.Message}"); return false; } } // 生成并保存密钥对到注册表 public void GenerateAndSaveKeys(string registrySubKey) { if (HasStoredKeys(registrySubKey)) return; try { // 创建注册表项(如果不存在) using (RegistryKey key = Registry.CurrentUser.CreateSubKey(registrySubKey)) { if (key == null) throw new InvalidOperationException("无法创建注册表项"); // 导出XML格式的密钥 string publicKeyXml = _rsa.ToXmlString(false); string privateKeyXml = _rsa.ToXmlString(true); // 加密私钥后存储 byte[] encryptedPrivateKey = ProtectedData.Protect(Encoding.UTF8.GetBytes(privateKeyXml), null, DataProtectionScope.CurrentUser); // 保存公钥(明文)和加密后的私钥 key.SetValue(PublicKeyXmlKey, publicKeyXml, RegistryValueKind.String); key.SetValue(PrivateKeyXmlKey, encryptedPrivateKey, RegistryValueKind.Binary); } } catch (Exception ex) { MessageBox.Show($"生成并保存密钥失败: {ex.Message}"); throw; } } // 从注册表加载公钥 private string LoadPublicKey(string registrySubKey) { try { using (RegistryKey key = Registry.CurrentUser.OpenSubKey(registrySubKey)) { if (key == null) throw new InvalidOperationException("注册表项不存在"); string publicKeyXml = key.GetValue(PublicKeyXmlKey) as string; if (string.IsNullOrEmpty(publicKeyXml)) throw new InvalidOperationException("公钥不存在"); return publicKeyXml; } } catch (Exception ex) { MessageBox.Show($"加载公钥失败: {ex.Message}"); throw; } } // 从注册表加载并解密私钥 private string LoadPrivateKey(string registrySubKey) { try { using (RegistryKey key = Registry.CurrentUser.OpenSubKey(registrySubKey)) { if (key == null) throw new InvalidOperationException("注册表项不存在"); byte[] encryptedPrivateKey = key.GetValue(PrivateKeyXmlKey) as byte[]; if (encryptedPrivateKey == null || encryptedPrivateKey.Length == 0) throw new InvalidOperationException("私钥不存在"); // 解密私钥 byte[] privateKeyBytes = ProtectedData.Unprotect(encryptedPrivateKey, null, DataProtectionScope.CurrentUser); return Encoding.UTF8.GetString(privateKeyBytes); } } catch (Exception ex) { MessageBox.Show($"加载私钥失败: {ex.Message}"); throw; } } // 加密方法:使用公钥加密 public string Encrypt(string plainText, string registrySubKey) { try { string publicKeyXml = LoadPublicKey(registrySubKey); _rsa.FromXmlString(publicKeyXml); byte[] plainBytes = Encoding.UTF8.GetBytes(plainText); byte[] encryptedBytes = _rsa.Encrypt(plainBytes, false); return Convert.ToBase64String(encryptedBytes); } catch (Exception ex) { MessageBox.Show($"加密失败: {ex.Message}"); throw new InvalidOperationException("数据加密失败"); } } // 解密方法:使用私钥解密 public string Decrypt(string encryptedText, string registrySubKey) { try { string privateKeyXml = LoadPrivateKey(registrySubKey); _rsa.FromXmlString(privateKeyXml); byte[] encryptedBytes = Convert.FromBase64String(encryptedText); byte[] decryptedBytes = _rsa.Decrypt(encryptedBytes, false); return Encoding.UTF8.GetString(decryptedBytes); } catch (Exception ex) { MessageBox.Show($"解密失败: {ex.Message}"); throw new InvalidOperationException("数据解密失败"); } } }
使用说明
- 第一个窗体初始化时确保密钥生成:
var encryption = new Encryption(); encryption.GenerateAndSaveKeys(@"Software\MyApp\EncryptionKeys");
- 加密数据:
string encryptedData = encryption.Encrypt("要加密的内容", @"Software\MyApp\EncryptionKeys"); // 保存encryptedData到文件或窗体间传递
- 第二个窗体解密:
var encryption = new Encryption(); string decryptedData = encryption.Decrypt(encryptedData, @"Software\MyApp\EncryptionKeys");
额外注意事项
- 使用
CurrentUser注册表 hive,普通用户即可读写,无需管理员权限 ProtectedData.Protect基于当前用户上下文加密私钥,仅当前用户可解密,保障私钥安全- 避免多线程同时使用同一个
Encryption实例,或改用线程安全的RSA实现
内容的提问来源于stack exchange,提问作者WDpad159
相关产品推荐
相关产品推荐

