CryptographicException периодически возникает при шифровании/дешифровании с помощью RSA

Я пытаюсь зашифровать и расшифровать данные с помощью 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. Это было связано с шифрованием с помощью закрытого ключа и попыткой расшифровать с помощью открытый ключ, но я не думаю, что делаю это. Этот вопрос имеет то же исключение, что и я , но выбранный ответ заключался в использовании OpenSSL.NET, чего я бы предпочел не делать.

Что я делаю неправильно?


person Sarah Vessels    schedule 30.06.2010    source источник


Ответы (3)


Не могли бы вы заменить ByteConverter.GetBytes на Convert.FromBase64String и заменить ByteConverter.GetString на Convert.ToBase64String и посмотреть, поможет ли это. Исключение Bad Data обычно означает, что у вас есть недопустимый символ в данных или что длина не соответствует длине для расшифровки. Я думаю, что использование функций Convert может решить ваши проблемы.

  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);
  }
person SwDevMan81    schedule 30.06.2010
comment
Хм, попытка это дает мне другое исключение: System.FormatException: неверная длина для массива символов Base-64. Это произошло в первой строке Encrypt: byte[] bytesToEncrypt = Convert.FromBase64String(textToEncrypt);. - person Sarah Vessels; 30.06.2010
comment
@Sarah - Хорошо, я обновил твой пример. Я протестировал его, и похоже, что он работает. - person SwDevMan81; 01.07.2010
comment
Это работает! Спасибо. Никогда бы не подумал использовать эту смесь Convert/UnicodeEncoding. - person Sarah Vessels; 01.07.2010

Ваша проблема связана с преобразованием байтов в строку. Не все последовательности байтов являются допустимой кодировкой UTF-16, и вы используете кодировку UnicodeEncoding, которая автоматически игнорирует недопустимые байты. Если вы использовали

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

вместо этого ваш код потерпит неудачу при попытке преобразовать байты вместо того, чтобы молча заменить недопустимые пары байтов на 0xFFFD.

То, что тест работал во время отладки, было совпадением. Вы используете случайную пару ключей RSA, поэтому иногда вы получаете шифрование, которое является допустимой кодировкой UTF-16.

Исправление, как предлагает SwDevMan81, заключается в использовании кодировки, которая может преобразовывать все возможные массивы байтов. F.x. Base64-кодировка.

person Rasmus Faber    schedule 01.07.2010

Я бы порекомендовал использовать этот класс, к сожалению, я не помню оригинального автора.

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, чтобы убедиться, что данные поступают оттуда, где указано.

person Ben    schedule 30.06.2010