Dot-Net

在 .NET 中使用 AES 加密 - CryptographicException 表示填充無效且無法刪除

  • November 27, 2013

我用 C# 編寫了一些 AES 加密程式碼,但無法正確加密和解密。如果我輸入“test”作為密碼並且“這個數據必須對所有人保密!” 我收到以下異常:

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

如果我輸入的內容少於 16 個字元,我將不會得到任何輸出。

我相信我需要在加密中進行一些特殊處理,因為 AES 是一種分組密碼,但我不確定那是什麼,而且我無法在網上找到任何範例來說明如何操作。這是我的程式碼:

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

在需要填充的模式(如 CBC)中使用像 AES 這樣的分組密碼時,您必須注意輸出將始終是分組大小的倍數。為了實現這一點,像 PKCS7 這樣的填充模式將在加密過程結束時向密碼添加一些字節。但是你必須讓加密器知道何時結束。為此,您所要做的就是插入語句

cryptoStream.FlushFinalBlock();  

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

PS:

也許它只是為了調試,但你的鹽生成方法每次都會生成完全相同的鹽。

引用自:https://stackoverflow.com/questions/4545387