2010-12-28 11 views
6

Napisałem kod szyfrowania AES w języku C# i mam problem z uzyskaniem go do szyfrowania i odszyfrowywania poprawnie. Jeśli wprowadzę "test" jako hasło i "Te dane muszą być trzymane w tajemnicy przed wszystkimi!" Otrzymuję następujący wyjątek:Użycie szyfrowania AES w .NET - CryptographicException mówiąc, że dopełnienie jest nieprawidłowe i nie można go usunąć

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() 
    ... 

Jeśli wprowadzę coś mniej niż 16 znaków, nie otrzymam wyniku.

Wydaje mi się, że potrzebuję specjalnej obsługi w szyfrowaniu, ponieważ AES jest szyfrem blokowym, ale nie jestem do końca pewien, co to jest i nie mogłem znaleźć żadnych przykładów w Internecie, które pokazują, w jaki sposób. Tu jest mój kodu:

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

Odpowiedz

13

Podczas korzystania szyfr blokowy AES jak w trybie, który wymaga dopełnienia, jak CBC, trzeba mieć świadomość, że wynik zawsze będzie wielokrotnością rozmiaru bloku. Aby to osiągnąć, tryby wypełniania, takie jak PKCS7, dodadzą niektóre bajty do szyfru na końcu procesu szyfrowania. Ale musisz znać szyfrowanie, kiedy nastąpi koniec. Aby to zrobić, wszystko co musisz zrobić, to włożyć rachunku

cryptoStream.FlushFinalBlock(); 

po

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

PS:

Być może jest to tylko do debugowania, ale metoda generacji sól generuje dokładnie to samo sól za każdym razem.

+0

Wow, proste, co? Myślałem, że to mogło mieć coś wspólnego z FlushFinalBlock, ale nie byłem pewien. Myślę, że dodałem go wcześniej, ale być może właśnie wprowadziłem złe hasło. Powinienem wygenerować losowe testy jednostkowe. :) Wiem również, że sól jest taka sama - nie byłem pewien, jak wygenerować losowy, ale znalazłem 'RNGCryptoServiceProvider' po napisaniu tego kodu. Dzięki za wskazanie. –

+0

Tak wiele przykładów w msdn i stackoverflow nie są tak zdrowe, a ta odpowiedź uratowała mój dzień. FlushFinalBlock(). Łał. – ZZZ

+0

Zawijanie strumienia CryptoStream w StreamWriter ... 'cryptoStream.FlushFinalBlock();' musi być wywoływany niezależnie. Dzięki - to była ogromna oszczędność czasu! – trousyt

Powiązane problemy