web-dev-qa-db-ja.com

ファイルごとのAES暗号化ランダムIV

以下の更新にジャンプします<リンクの作成方法がわからない:(

次のコードにいくつかの改良を加えました: Csharp-AES-bits-Encryption-Library-with-Salt

  • saltBytesがパスワードのSHA512になりました。
  • 各暗号化呼び出しのランダムIV。 (IV長さ16が暗号化されたファイルに追加され、復号化前にファイルから削除されます)

最適化が必要な欠陥はありますか?特に、私の質問は次のとおりです。

  • 以下の「暗号化コード」にある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;
    }

更新:

これはコメント/推奨事項/アドバイスに基づいて更新されたコードです

  • ランダムソルトTrue
  • 反復100000

暗号化:

        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;
    }
3
xhxx

セキュリティの観点からは、generateIV()メソッドは問題ありません。NETとして冗長なビットが提供されない場合とまったく同じ方法でIVを生成します。

3つのより大きな問題は、100回の反復がway PBKDF2には少なすぎること、認証の欠如です。これは、暗号文が制御不能になっている場合の問題です(別の方法としてremoveTagAndIV()ですが、そもそもタグは実装されていません)およびソルトとしてのパスワードハッシュの使用。ソルトのポイントは、世界的にユニークであることです。生のパスワードのハッシュはそうではありません。これでPBKFDが大幅に弱まり、実用的なキースペースの潜在的なサイズが大幅に制限され、レインボーテーブルがキーの識別に使用される可能性が生まれました。 salt mustはグローバルに一意である必要があります。これを実現する最も簡単な方法は、既存のgenerateIV()メソッドを使用してソルトを生成し、IVと暗号文とともに保存することです。

6
Xander
  1. 私は.NETプログラマーではないため、ここのコードの一部を完全には理解していません。

  2. passwordBytesにはどのくらいのエントロピーがありますか?エントロピーが72ビットを下回っている場合は、 (Slow)Password Hash 、またはより具体的にはキー導出関数を使用する必要があります。

  3. generateIVは、ランダムである限り問題ありません。グローバルに一意である限り、暗号的に安全な乱数である必要はありません。

  4. CBCが適切な連鎖モードであることを確認したい場合は、別の質問をすることができます。それぞれの連鎖モードの長所と短所について回答します。

  5. セキュリティのためにdecryptionコードを精査するように依頼するべきではありません。単にテストするだけで、自分で調べることができます。

  6. ソルトは、確定的ではなく、ランダムである必要があります。ペッパーも追加します。

  7. #5を参照して、encryptionコードをできるだけ短く簡潔にします。あなたが好きな個々の点について質問してください、しかし私たちに全体をレビューさせることはここでは少し外れたトピックです。

  8. AESは良い選択です。

3
Bryan Field