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

非对称加密实现及私钥注册表存储问题排查

非对称加密解密密钥不一致导致解密失败问题排查

我正在实现一个非对称加密功能:用户在第一个窗体加密数据,关闭后打开第二个窗体查看解密结果。为此写了一个供两个窗体调用的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("数据解密失败");
        }
    }
}

使用说明

  1. 第一个窗体初始化时确保密钥生成:
var encryption = new Encryption();
encryption.GenerateAndSaveKeys(@"Software\MyApp\EncryptionKeys");
  1. 加密数据:
string encryptedData = encryption.Encrypt("要加密的内容", @"Software\MyApp\EncryptionKeys");
// 保存encryptedData到文件或窗体间传递
  1. 第二个窗体解密:
var encryption = new Encryption();
string decryptedData = encryption.Decrypt(encryptedData, @"Software\MyApp\EncryptionKeys");

额外注意事项

  • 使用CurrentUser注册表 hive,普通用户即可读写,无需管理员权限
  • ProtectedData.Protect基于当前用户上下文加密私钥,仅当前用户可解密,保障私钥安全
  • 避免多线程同时使用同一个Encryption实例,或改用线程安全的RSA实现

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:07:35