以下の更新にジャンプします<リンクの作成方法がわからない:(
次のコードにいくつかの改良を加えました: Csharp-AES-bits-Encryption-Library-with-Salt
最適化が必要な欠陥はありますか?特に、私の質問は次のとおりです。
以下の「暗号化コード」にあるgenerateIV()
メソッドは安全ですか?唯一の依存関係は.NETにあることに注意してください- RNGCryptoServiceProvider Class
。
パスワードハッシュをソルトとして使用しても安全ですか?または、IVのようにランダムにして、暗号文と一緒に保存する必要がありますか?
参考までに、これが私のコードです。
次のコードが機能しています。2つのテキストファイルを暗号化して、それぞれのテキストがまったく同じであるようにテストしました。結果は両方とも異なるデータを持ち、復号化は成功します。
また、ランダムIVの前に確認したところ、両方のファイルに同じ暗号化テキストが含まれており、結果は同じデータになります。
暗号化コード:
private static int IV_LENGTH = 16;
public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] encryptedBytesAndIV = null;
byte[] saltBytes = SHA512.Create().ComputeHash(passwordBytes);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
//AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = generateIV();
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
encryptedBytesAndIV = new byte[encryptedBytes.Length + AES.IV.Length];
AES.IV.CopyTo(encryptedBytesAndIV, 0);
encryptedBytes.CopyTo(encryptedBytesAndIV, IV_LENGTH);
}
}
return encryptedBytesAndIV;
}
private static byte[] generateIV()
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] nonce = new byte[IV_LENGTH];
rng.GetBytes(nonce);
return nonce;
}
}
復号化コード:
public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
byte[] saltBytes = SHA512.Create().ComputeHash(passwordBytes);
using (MemoryStream ms = new MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
//AES.BlockSize = 128;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = getIV(bytesToBeDecrypted);
bytesToBeDecrypted = removeTagAndIV(bytesToBeDecrypted);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
private static byte[] removeTagAndIV(byte[] arr)
{
byte[] enc = new byte[arr.Length - IV_LENGTH];
Array.Copy(arr, IV_LENGTH, enc, 0, arr.Length - IV_LENGTH);
return enc;
}
private static byte[] getIV(byte[] arr)
{
byte[] IV = new byte[IV_LENGTH];
Array.Copy(arr, 0, IV, 0, IV_LENGTH);
return IV;
}
更新:
これはコメント/推奨事項/アドバイスに基づいて更新されたコードです
暗号化:
public static byte[] AES_Encrypt(byte[] bytesToBeEncrypted, byte[] passwordBytes)
{
byte[] encryptedBytes = null;
byte[] encryptedBytesFinal = null;
byte[] saltBytes = generateIVandSalt(16);
using (System.IO.MemoryStream ms = new System.IO.MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = generateIVandSalt(16);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateEncryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeEncrypted, 0, bytesToBeEncrypted.Length);
cs.Close();
}
encryptedBytes = ms.ToArray();
encryptedBytesFinal = new byte[encryptedBytes.Length + AES.IV.Length + saltBytes.Length];
AES.IV.CopyTo(encryptedBytesFinal, 0);
saltBytes.CopyTo(encryptedBytesFinal, 16);
encryptedBytes.CopyTo(encryptedBytesFinal, 16 + 16);
}
}
return encryptedBytesFinal;
}
private static byte[] generateIVandSalt(int len)
{
using (RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider())
{
byte[] nonce = new byte[len];
rng.GetBytes(nonce);
return nonce;
}
}
解読:
public static byte[] AES_Decrypt(byte[] bytesToBeDecrypted, byte[] passwordBytes)
{
byte[] decryptedBytes = null;
byte[] saltBytes = getSalt(bytesToBeDecrypted);
using (MemoryStream ms = new MemoryStream())
{
using (AesCryptoServiceProvider AES = new AesCryptoServiceProvider())
{
AES.KeySize = 256;
var key = new Rfc2898DeriveBytes(passwordBytes, saltBytes, 100000);
AES.Key = key.GetBytes(AES.KeySize / 8);
AES.IV = getIV(bytesToBeDecrypted);
bytesToBeDecrypted = removeIVandSalt(bytesToBeDecrypted);
AES.Mode = CipherMode.CBC;
using (var cs = new CryptoStream(ms, AES.CreateDecryptor(), CryptoStreamMode.Write))
{
cs.Write(bytesToBeDecrypted, 0, bytesToBeDecrypted.Length);
cs.Close();
}
decryptedBytes = ms.ToArray();
}
}
return decryptedBytes;
}
private static byte[] removeIVandSalt(byte[] arr)
{
byte[] enc = new byte[arr.Length - 16 - IV_LENGTH];
Array.Copy(arr, IV_LENGTH + 16, enc, 0, arr.Length - IV_LENGTH - 16);
return enc;
}
private static byte[] getIV(byte[] arr)
{
byte[] IV = new byte[IV_LENGTH];
Array.Copy(arr, 0, IV, 0, IV_LENGTH);
return IV;
}
private static byte[] getSalt(byte[] arr)
{
byte[] salt = new byte[16];
Array.Copy(arr, IV_LENGTH, salt, 0, 16);
return salt;
}
セキュリティの観点からは、generateIV()
メソッドは問題ありません。NETとして冗長なビットが提供されない場合とまったく同じ方法でIVを生成します。
3つのより大きな問題は、100回の反復がway PBKDF2には少なすぎること、認証の欠如です。これは、暗号文が制御不能になっている場合の問題です(別の方法としてremoveTagAndIV()
ですが、そもそもタグは実装されていません)およびソルトとしてのパスワードハッシュの使用。ソルトのポイントは、世界的にユニークであることです。生のパスワードのハッシュはそうではありません。これでPBKFDが大幅に弱まり、実用的なキースペースの潜在的なサイズが大幅に制限され、レインボーテーブルがキーの識別に使用される可能性が生まれました。 salt mustはグローバルに一意である必要があります。これを実現する最も簡単な方法は、既存のgenerateIV()
メソッドを使用してソルトを生成し、IVと暗号文とともに保存することです。
私は.NETプログラマーではないため、ここのコードの一部を完全には理解していません。
passwordBytes
にはどのくらいのエントロピーがありますか?エントロピーが72ビットを下回っている場合は、 (Slow)Password Hash 、またはより具体的にはキー導出関数を使用する必要があります。
generateIV
は、ランダムである限り問題ありません。グローバルに一意である限り、暗号的に安全な乱数である必要はありません。
CBCが適切な連鎖モードであることを確認したい場合は、別の質問をすることができます。それぞれの連鎖モードの長所と短所について回答します。
セキュリティのためにdecryptionコードを精査するように依頼するべきではありません。単にテストするだけで、自分で調べることができます。
ソルトは、確定的ではなく、ランダムである必要があります。ペッパーも追加します。
#5を参照して、encryptionコードをできるだけ短く簡潔にします。あなたが好きな個々の点について質問してください、しかし私たちに全体をレビューさせることはここでは少し外れたトピックです。
AESは良い選択です。