2010-06-30 59 views
6

Estoy intentando cifrar y descifrar datos utilizando RSA en C#. Tengo la siguiente prueba de unidad MSTest:CryptographicException se produce de forma intermitente al cifrar/descifrar con RSA

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"); 

Se produce un error durante el descifrado, pero solo algunas veces. Parece que cada vez que establezco puntos de interrupción y paso por la prueba, pasa. Esto me hizo pensar que tal vez algo no estaba terminando a tiempo para que la descifración ocurriera con éxito, y que me ralentizé mientras la depuración le daba tiempo suficiente para completarla. Cuando falla, la línea parece fallar en es decryptedBytes = rsa.Decrypt(bytesToDecrypt, false); en el siguiente método:

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); 
} 

falla con esta excepción:

System.Security.Cryptography.CryptographicException: Datos incorrectos

Mi método Encrypt es el siguiente:

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); 
} 

El ByteConverter utilizado a lo largo es sólo el siguiente:

public static readonly UnicodeEncoding ByteConverter = new UnicodeEncoding(); 

que he visto algunas preguntas sobre stackoverflow sobre cifrado RSA y descifrado con .NET. This one se debió a la encriptación con la clave privada y al intento de descifrado con la clave pública, pero no creo que esté haciendo eso. This question tiene la misma excepción que yo, pero la respuesta seleccionada fue usar OpenSSL.NET, que yo preferiría no hacer.

¿Qué estoy haciendo mal?

Respuesta

8

¿Podría reemplazar ByteConverter.GetBytes con Convert.FromBase64String y reemplazar ByteConverter.GetString con Convert.ToBase64String y ver si eso ayuda. La excepción Bad Data generalmente significa que tiene un carácter no válido en los datos o que la longitud no es la correcta para descifrar. Creo que usar las funciones Convertir podría solucionar tus problemas.

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

Hm, lo que me da una excepción diferente: "System.FormatException: longitud no válida para una matriz de caracteres Base-64". Esto ocurrió en la primera línea de 'Encrypt':' byte [] bytesToEncrypt = Convert.FromBase64String (textToEncrypt); '. –

+1

@Sarah - Ok, actualicé su ejemplo. Lo probé y parece que funciona. – SwDevMan81

+0

¡Eso funciona! Gracias. Nunca hubiera pensado usar esa mezcla de 'Convert' /' UnicodeEncoding'. –

1

Yo recomendaría el uso de esta clase, por desgracia no recuerdo el autor original, aunque ..

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; 
     } 

    } 
} 
} 

Utilizar como:

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

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

EDIT: también recomiendo que no lo usa para encriptación de datos grandes. Por lo general, encriptaría los datos reales con un algoritmo simétrico (AES, etc.), luego cifraría la clave simétrica (generada aleatoriamente) con el algoritmo RSA, luego enviaría la clave simétrica rsa cifrada y los datos de la clave simétrica. También debería Mire la firma RSA, para asegurarse de que los datos provienen de donde dice que es ...

3

Su problema es con la conversión de bytes a cadena. No todas las secuencias de bytes son una codificación UTF-16 válida y está utilizando un UnicodeEncoding que ignora silenciosamente los bytes no válidos.Si utilizó

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

lugar, el código habría fallado al intentar convertir los bytes en lugar de sustituir en silencio los bytes no válidos pares con 0xFFFD.

El hecho de que la prueba funcionó durante la depuración fue una coincidencia. Está utilizando un par de claves RSA al azar, por lo que a veces obtendrá un cifrado que es una codificación UTF-16 válida.

La corrección es, como sugiere SwDevMan81, utilizar una codificación que pueda convertir todas las matrices de bytes posibles. F.x. Codificación Base64.

Cuestiones relacionadas