使用RSA加密/解密时会间歇性地发生CryptographicException

时间:2010-06-30 19:27:07

标签: c# security encryption rsa rsacryptoserviceprovider

我正在尝试使用C#中的RSA加密和解密数据。我有以下MSTest单元测试:

const string rawPassword = "mypass";

// Encrypt
string publicKey, privateKey;
string encryptedPassword = RSAUtils.Encrypt(rawPassword, out publicKey, out privateKey);
Assert.AreNotEqual(rawPassword, encryptedPassword,
    "Raw password and encrypted password should not be equal");

// Decrypt
string decryptedPassword = RSAUtils.Decrypt(encryptedPassword, privateKey);
Assert.AreEqual(rawPassword, decryptedPassword,
    "Did not get expected decrypted password");

在解密过程中失败,但有时只会失败。似乎每当我设置断点并逐步完成测试时,它就会通过。这让我觉得也许有些事情没有及时完成解密才能成功发生,而且我在调试过程中放慢了脚步,让它有足够的时间来完成。当它失败时,在以下方法中它似乎失败的行为decryptedBytes = rsa.Decrypt(bytesToDecrypt, false);

public static string Decrypt(string textToDecrypt, string privateKeyXml)
{
    if (string.IsNullOrEmpty(textToDecrypt))
    {
        throw new ArgumentException(
            "Cannot decrypt null or blank string"
        );
    }
    if (string.IsNullOrEmpty(privateKeyXml))
    {
        throw new ArgumentException("Invalid private key XML given");
    }
    byte[] bytesToDecrypt = ByteConverter.GetBytes(textToDecrypt);
    byte[] decryptedBytes;
    using (var rsa = new RSACryptoServiceProvider())
    {
        rsa.FromXmlString(privateKeyXml);
        decryptedBytes = rsa.Decrypt(bytesToDecrypt, false); // fail here
    }
    return ByteConverter.GetString(decryptedBytes);
}

它失败并出现此异常:

  

System.Security.Cryptography.CryptographicException:错误数据

我的Encrypt方法如下:

public static string Encrypt(string textToEncrypt, out string publicKey,
    out string privateKey)
{
    byte[] bytesToEncrypt = ByteConverter.GetBytes(textToEncrypt);
    byte[] encryptedBytes;
    using (var rsa = new RSACryptoServiceProvider())
    {
        encryptedBytes = rsa.Encrypt(bytesToEncrypt, false);
        publicKey = rsa.ToXmlString(false);
        privateKey = rsa.ToXmlString(true);
    }
    return ByteConverter.GetString(encryptedBytes);
}

全程使用的ByteConverter如下:

public static readonly UnicodeEncoding ByteConverter = new UnicodeEncoding();

我在StackOverflow上看到了一些关于RSA加密和.NET解密的问题。 This one是由于使用私钥加密并尝试使用公钥解密,但我不认为我这样做。 This question与我有相同的例外,但选择的答案是使用OpenSSL.NET,我不愿意这样做。

我做错了什么?

3 个答案:

答案 0 :(得分:8)

您可以将ByteConverter.GetBytes替换为Convert.FromBase64String并将ByteConverter.GetString替换为Convert.ToBase64String,看看是否有帮助。 Bad Data异常通常表示数据中包含无效字符或者长度不是正确的解密长度。我认为使用转换功能可能会解决您的问题。

  public static readonly UnicodeEncoding ByteConverter = new UnicodeEncoding();

  public static string Encrypt(string textToEncrypt, out string publicKey,
    out string privateKey)
  {
     byte[] bytesToEncrypt = ByteConverter.GetBytes(textToEncrypt);
     byte[] encryptedBytes;
     using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
     {
        encryptedBytes = rsa.Encrypt(bytesToEncrypt, false);
        publicKey = rsa.ToXmlString(false);
        privateKey = rsa.ToXmlString(true);
     }
     return Convert.ToBase64String(encryptedBytes);
  }

  public static string Decrypt(string textToDecrypt, string privateKeyXml)
  {
     if (string.IsNullOrEmpty(textToDecrypt))
     {
        throw new ArgumentException(
            "Cannot decrypt null or blank string"
        );
     }
     if (string.IsNullOrEmpty(privateKeyXml))
     {
        throw new ArgumentException("Invalid private key XML given");
     }
     byte[] bytesToDecrypt = Convert.FromBase64String(textToDecrypt);
     byte[] decryptedBytes;
     using (RSACryptoServiceProvider rsa = new RSACryptoServiceProvider())
     {
        rsa.FromXmlString(privateKeyXml);
        decryptedBytes = rsa.Decrypt(bytesToDecrypt, false); // fail here
     }
     return ByteConverter.GetString(decryptedBytes);
  }

答案 1 :(得分:3)

您的问题是从字节到字符串的转换。并非所有字节序列都是有效的UTF-16编码,并且您正在使用无提示忽略无效字节的UnicodeEncoding。如果您使用

public static readonly UnicodeEncoding ByteConverter = new UnicodeEncoding(false, false, true);

相反,尝试转换字节时代码会失败,而不是用0xFFFD静默替换无效字节对。

在调试时测试工作的事实是巧合。您正在使用随机RSA密钥对,因此有时您将获得有效的UTF-16编码加密。

正如SwDevMan81建议的那样,修复程序使用可以转换所有可能的字节数组的编码。 F.X.编码的base64。

答案 2 :(得分:1)

我建议使用这个课,遗憾的是我不记得原作者了..

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;

namespace Encryption
{

class AsymmetricED
{
    private static RSAParameters param = new RSAParameters();
    /// <summary>
    /// Get Parameters
    /// </summary>
    /// <param name="pp">Export private parameters?</param>
    /// <returns></returns>
    public static RSAParameters GenerateKeys(bool pp)
    {
        RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
        if (param.Equals(new RSAParameters()))
        {
            param = RSA.ExportParameters(true);
        }
        RSA.ImportParameters(param);
        return RSA.ExportParameters(pp);
    }
    static public byte[] RSAEncrypt(byte[] DataToEncrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
    {
        try
        {
            //Create a new instance of RSACryptoServiceProvider.
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();

            //Import the RSA Key information. This only needs
            //toinclude the public key information.
            RSA.ImportParameters(RSAKeyInfo);

            //Encrypt the passed byte array and specify OAEP padding.  
            //OAEP padding is only available on Microsoft Windows XP or
            //later.  
            return RSA.Encrypt(DataToEncrypt, DoOAEPPadding);
        }
        //Catch and display a CryptographicException  
        //to the console.
        catch (CryptographicException e)
        {
            Console.WriteLine(e.Message);

            return null;
        }

    }

    static public byte[] RSADecrypt(byte[] DataToDecrypt, RSAParameters RSAKeyInfo, bool DoOAEPPadding)
    {
        try
        {
            //Create a new instance of RSACryptoServiceProvider.
            RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();

            //Import the RSA Key information. This needs
            //to include the private key information.
            RSA.ImportParameters(RSAKeyInfo);

            //Decrypt the passed byte array and specify OAEP padding.  
            //OAEP padding is only available on Microsoft Windows XP or
            //later.  
            return RSA.Decrypt(DataToDecrypt, DoOAEPPadding);
        }
        //Catch and display a CryptographicException  
        //to the console.
        catch (CryptographicException e)
        {
            ConsoleColor col = Console.BackgroundColor;
            Console.BackgroundColor = ConsoleColor.Red;
            Console.WriteLine(e.ToString());
            Console.BackgroundColor = col;
            return null;
        }

    }
}
}

用作:

Encryption.AsymmetricED.RSAEncrypt(Data, GenerateKeys(false), false);

Encryption.AsymmetricED.RSADecrypt(Data, GenerateKeys(true), false);

编辑: 我还建议您不要将其用于大数据加密。通常,您将使用对称算法(AES等)加密实际数据,然后使用RSA算法加密对称密钥(随机生成),然后发送rsa加密对称密钥和对称密钥数据。 您还应该查看RSA签名,以确保数据来自它所说的位置。