Encryption

關於使用密碼加密(AES)文件的一些問題

  • April 11, 2021

我對加密比較陌生,我有一些問題。我打算編寫一個獨立的 C# 程序,該程序將使用使用者在程序啟動時輸入的密碼寫入和讀取包含敏感數據的加密文件。程序和文件都將一起儲存在一個私密的地方,但人們永遠不知道它們會落入壞人之手。由於程序是用 C# 編寫的,因此可以反編譯,但我一直聽說這不是問題,因為即使知道算法,加密也應該是安全的。

下面是我寫的測試程式碼,後面會有一些問題:

private string TestEncryption(string password, string input, string filename)
{
   // Encryption
   byte[] inputBytes = AddHash(Encoding.UTF8.GetBytes(input));
   int iterations = 10000;
   int saltSize = 64;
   using (Rfc2898DeriveBytes rbg = new Rfc2898DeriveBytes(password, saltSize, iterations))
   {
       if (rbg.Salt.Length != saltSize)
           throw new Exception("Invalid salt size");

       using (AesManaged aes = new AesManaged())
       {
           byte[] key = rbg.GetBytes(aes.KeySize >> 3);
           byte[] iv = rbg.GetBytes(aes.BlockSize >> 3);
           using (ICryptoTransform cryptoTransform = aes.CreateEncryptor(key, iv))
           using (MemoryStream memoryStream = new MemoryStream())
           {
               using (CryptoStream cryptoStream = new CryptoStream(memoryStream, cryptoTransform, CryptoStreamMode.Write))
                   cryptoStream.Write(inputBytes, 0, inputBytes.Length);

               byte[] encryptedBytes = AddHash(ConcatBytes(memoryStream.ToArray(), rbg.Salt));
               using (FileStream file = new FileStream(filename, FileMode.Create, FileAccess.Write))
                   file.Write(encryptedBytes, 0, encryptedBytes.Length);
           }
       }
   }

   // Decryption
   using (FileStream file = new FileStream(filename, FileMode.Open, FileAccess.Read))
   {
       inputBytes = new byte[file.Length];
       file.Read(inputBytes, 0, inputBytes.Length);
   }
   int length;
   if (!VerifyHash(inputBytes, out length))
       throw new Exception("File corrupted");

   byte[] salt = new byte[saltSize];
   length -= saltSize;
   Buffer.BlockCopy(inputBytes, length, salt, 0, saltSize);
   using (Rfc2898DeriveBytes rbg = new Rfc2898DeriveBytes(password, salt, iterations))
   using (AesManaged aes = new AesManaged())
   {
       byte[] key = rbg.GetBytes(aes.KeySize >> 3);
       byte[] iv = rbg.GetBytes(aes.BlockSize >> 3);
       try
       {
           using (ICryptoTransform cryptoTransform = aes.CreateDecryptor(key, iv))
           using (MemoryStream sourceStream = new MemoryStream(inputBytes, 0, length))
           using (CryptoStream cryptoStream = new CryptoStream(sourceStream, cryptoTransform, CryptoStreamMode.Read))
           using (MemoryStream decryptedStream = new MemoryStream())
           {
               cryptoStream.CopyTo(decryptedStream);
               byte[] decryptedBytes = decryptedStream.ToArray();
               if (!VerifyHash(decryptedBytes, out length))
                   throw new Exception("Invalid password");
               return Encoding.UTF8.GetString(decryptedBytes, 0, length);
           }
       }
       catch (CryptographicException exception)
       { throw new Exception("Probable invalid password", exception); }
   }

   return null;
}

private byte[] AddHash(byte[] bytes)
{
   using (MD5 md5 = MD5.Create())
   {
       byte[] hash = md5.ComputeHash(bytes);
       if (hash.Length != (md5.HashSize >> 3))
           throw new Exception("Invalid hash size");
       return ConcatBytes(bytes, hash);
   }
}

private bool VerifyHash(byte[] bytes, out int length)
{
   using (MD5 md5 = MD5.Create())
   {
       int hashSize = md5.HashSize >> 3;
       length = bytes.Length - hashSize;
       if (bytes.Length > hashSize)
       {
           byte[] hash = new byte[hashSize];
           Buffer.BlockCopy(bytes, length, hash, 0, hashSize);
           if (hash.SequenceEqual(md5.ComputeHash(bytes, 0, length)))
               return true;
       }
   }
   return false;
}

private byte[] ConcatBytes(params byte[][] buffers)
{
   int length = buffers.Sum(b => b.Length);
   byte[] result = new byte[length];
   int offset = 0;
   foreach (byte[] buffer in buffers)
   {
       Buffer.BlockCopy(buffer, 0, result, offset, buffer.Length);
       offset += buffer.Length;
   }
   return result;
}

現在問一些問題:

  1. 您可能已經註意到,我將用於生成密鑰和 IV 的鹽保存在文件中。這是否會損害加密?我想它不是,因為當我們儲存散列密碼時,它們也包括它們的鹽。
  2. 我正在使用 64 和 10000 次迭代的鹽大小,這些是正確的值嗎?
  3. 我還保存了一些雜湊值(MD5)來檢測錯誤。我在文件末尾保存一個,根據加密字節(包括鹽)計算,以測試文件是否未損壞。我不認為這應該是任何問題,因為它不會損害加密。在加密之前將另一個雜湊碼添加到未加密的數據中,以測試數據是否被正確解密。我想知道這是否是個好主意,因為我認為它可以幫助暴力解密攻擊?如果沒有,有什麼更好的選擇?
  4. AES (Rijndael) 仍然是最好的對稱加密算法,還是有更好的算法?
  5. 我應該強制執行密碼的最小大小還是不需要,因為密鑰和 IV 是根據密碼和隨機鹽隨機生成的?
  6. 使用錯誤密碼時能否避免 CryptographicException?

(我在stackoverflow上發布了這個問題,但有人建議我將其移至更合適的站點)

馬克

編輯:添加了第六個問題。

  1. Salt和IV不需要保密,但應該是唯一的。如果我理解正確,Rfc2898DeriveBytes 類會生成隨機鹽嗎?在這種情況下,你很好。
  2. 64 字節(超過)對於隨機鹽來說已經足夠了。迭代次數取決於您的需要。您基本上應該選擇不太慢的最大數字。您可以以某種方式將其擴展到可用的處理能力,而不是一個常數。
  3. 請參閱我們應該 MAC-then-encrypt 還是 encrypt-then-MAC?您可能應該選擇比 MD5 更安全的東西。
  4. 主觀的,但它沒有被打破。
  5. 密碼仍然可以被暴力破解:PBKDF2 只會讓它變慢,而 salt 只會阻止對多個密碼的並行攻擊。糟糕的密碼仍然很糟糕。

引用自:https://crypto.stackexchange.com/questions/16410