2010-12-28 33 views
6

Escribí un código de cifrado AES en C# y tengo problemas para cifrarlo y descifrarlo correctamente. Si ingreso "prueba" como frase de contraseña y "¡esta información debe mantenerse en secreto de todos!" Recibo la siguiente excepción:Uso del cifrado AES en .NET - CryptographicException que indica que el relleno no es válido y no se puede eliminar

System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed. 
    at System.Security.Cryptography.RijndaelManagedTransform.DecryptData(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount, Byte[]& outputBuffer, Int32 outputOffset, PaddingMode paddingMode, Boolean fLast) 
    at System.Security.Cryptography.RijndaelManagedTransform.TransformFinalBlock(Byte[] inputBuffer, Int32 inputOffset, Int32 inputCount) 
    at System.Security.Cryptography.CryptoStream.FlushFinalBlock() 
    at System.Security.Cryptography.CryptoStream.Dispose(Boolean disposing) 
    at System.IO.Stream.Close() 
    at System.IO.Stream.Dispose() 
    ... 

Y si introduzco algo menos de 16 caracteres, no obtengo salida.

Creo que necesito un manejo especial en el cifrado ya que AES es un cifrado de bloques, pero no estoy seguro de qué es eso exactamente, y no pude encontrar ningún ejemplo en la web que muestre cómo hacerlo. Aquí está mi código:

using System; 
using System.IO; 
using System.Security.Cryptography; 
using System.Text; 

public static class DatabaseCrypto 
{ 
    public static EncryptedData Encrypt(string password, string data) 
    { 
     return DatabaseCrypto.Transform(true, password, data, null, null) as EncryptedData; 
    } 

    public static string Decrypt(string password, EncryptedData data) 
    { 
     return DatabaseCrypto.Transform(false, password, data.DataString, data.SaltString, data.MACString) as string; 
    } 

    private static object Transform(bool encrypt, string password, string data, string saltString, string macString) 
    { 
     using (AesManaged aes = new AesManaged()) 
     { 
      aes.Mode = CipherMode.CBC; 
      aes.Padding = PaddingMode.PKCS7; 
      int key_len = aes.KeySize/8; 
      int iv_len = aes.BlockSize/8; 
      const int salt_size = 8; 
      const int iterations = 8192; 

      byte[] salt = encrypt ? new byte[salt_size] : Convert.FromBase64String(saltString); 
      if (encrypt) 
      { 
       new RNGCryptoServiceProvider().GetBytes(salt); 
      } 

      byte[] bc_key = new Rfc2898DeriveBytes("BLK" + password, salt, iterations).GetBytes(key_len); 
      byte[] iv = new Rfc2898DeriveBytes("IV" + password, salt, iterations).GetBytes(iv_len); 
      byte[] mac_key = new Rfc2898DeriveBytes("MAC" + password, salt, iterations).GetBytes(16); 

      aes.Key = bc_key; 
      aes.IV = iv; 

      byte[] rawData = encrypt ? Encoding.UTF8.GetBytes(data) : Convert.FromBase64String(data); 

      using (ICryptoTransform transform = encrypt ? aes.CreateEncryptor() : aes.CreateDecryptor()) 
      using (MemoryStream memoryStream = encrypt ? new MemoryStream() : new MemoryStream(rawData)) 
      using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, encrypt ? CryptoStreamMode.Write : CryptoStreamMode.Read)) 
      { 
       if (encrypt) 
       { 
        cryptoStream.Write(rawData, 0, rawData.Length); 

        return new EncryptedData(salt, mac_key, memoryStream.ToArray()); 
       } 
       else 
       { 
        byte[] originalData = new byte[rawData.Length]; 
        int count = cryptoStream.Read(originalData, 0, originalData.Length); 

        return Encoding.UTF8.GetString(originalData, 0, count); 
       } 
      } 
     } 
    } 
} 

public class EncryptedData 
{ 
    public EncryptedData() 
    { 
    } 

    public EncryptedData(byte[] salt, byte[] mac, byte[] data) 
    { 
     this.Salt = salt; 
     this.MAC = mac; 
     this.Data = data; 
    } 

    public EncryptedData(string salt, string mac, string data) 
    { 
     this.SaltString = salt; 
     this.MACString = mac; 
     this.DataString = data; 
    } 

    public byte[] Salt 
    { 
     get; 
     set; 
    } 

    public string SaltString 
    { 
     get { return Convert.ToBase64String(this.Salt); } 
     set { this.Salt = Convert.FromBase64String(value); } 
    } 

    public byte[] MAC 
    { 
     get; 
     set; 
    } 

    public string MACString 
    { 
     get { return Convert.ToBase64String(this.MAC); } 
     set { this.MAC = Convert.FromBase64String(value); } 
    } 

    public byte[] Data 
    { 
     get; 
     set; 
    } 

    public string DataString 
    { 
     get { return Convert.ToBase64String(this.Data); } 
     set { this.Data = Convert.FromBase64String(value); } 
    } 
} 

    static void ReadTest() 
    { 
     Console.WriteLine("Enter password: "); 
     string password = Console.ReadLine(); 

     using (StreamReader reader = new StreamReader("aes.cs.txt")) 
     { 
      EncryptedData enc = new EncryptedData(); 
      enc.SaltString = reader.ReadLine(); 
      enc.MACString = reader.ReadLine(); 
      enc.DataString = reader.ReadLine(); 

      Console.WriteLine("The decrypted data was: " + DatabaseCrypto.Decrypt(password, enc)); 
     } 
    } 

    static void WriteTest() 
    { 
     Console.WriteLine("Enter data: "); 
     string data = Console.ReadLine(); 
     Console.WriteLine("Enter password: "); 
     string password = Console.ReadLine(); 

     EncryptedData enc = DatabaseCrypto.Encrypt(password, data); 

     using (StreamWriter stream = new StreamWriter("aes.cs.txt")) 
     { 
      stream.WriteLine(enc.SaltString); 
      stream.WriteLine(enc.MACString); 
      stream.WriteLine(enc.DataString); 

      Console.WriteLine("The encrypted data was: " + enc.DataString); 
     } 
    } 

Respuesta

13

Cuando se utiliza un cifrado en bloque como AES en un modo que requiere el relleno, como CBC, debe ser consciente de que la salida siempre será un múltiplo del tamaño de bloque. Para lograr esto, los modos de relleno como PKCS7 agregarán algunos bytes al cifrado al final del proceso de cifrado. Pero debe dejar que el encriptador sepa cuándo se produce el final. Para ello, todo lo que tiene que hacer es insertar la declaración

cryptoStream.FlushFinalBlock(); 

después

cryptoStream.Write(rawData, 0, rawData.Length); 

PS:

Tal vez es sólo para la depuración, pero su método de generación de sal genera exactamente el mismo sal cada vez.

+0

Guau, tan simple como eso, ¿eh? Pensé que podría haber sido algo relacionado con FlushFinalBlock, pero no estaba seguro. Creo que lo agregué antes, pero tal vez estaba ingresando la contraseña incorrecta. Debería generar algunas pruebas unitarias aleatorias. :) También sé que la sal es la misma, no estaba seguro de cómo generar una aleatoria todavía, pero encontré 'RNGCryptoServiceProvider' después de escribir ese código. Gracias por señalarlo. –

+0

Muchos ejemplos en msdn y stackoverflow no son tan saludables, y esta respuesta me salvó el día. FlushFinalBlock(). Guau. – ZZZ

+0

Estaba envolviendo el CryptoStream en un StreamWriter ... 'cryptoStream.FlushFinalBlock();' necesita ser llamado independientemente. Gracias - ¡esto fue un gran ahorro de tiempo! – trousyt

Cuestiones relacionadas