22

AES/GCM/NoPadding を使用してデータを暗号化および復号化しようとしています。JCE Unlimited Strength Policy Files をインストールし、以下の (単純な) ベンチマークを実行しました。OpenSSL を使用して同じことを行い、PC で1 GB/秒を超える暗号化と復号化を実現できました。

以下のベンチマークでは、同じ PC で Java 8 を使用して3 MB/秒の暗号化と復号化しか取得できません。私が間違っていることは何ですか?

public static void main(String[] args) throws Exception {
    final byte[] data = new byte[64 * 1024];
    final byte[] encrypted = new byte[64 * 1024];
    final byte[] key = new byte[32];
    final byte[] iv = new byte[12];
    final Random random = new Random(1);
    random.nextBytes(data);
    random.nextBytes(key);
    random.nextBytes(iv);

    System.out.println("Benchmarking AES-256 GCM encryption for 10 seconds");
    long javaEncryptInputBytes = 0;
    long javaEncryptStartTime = System.currentTimeMillis();
    final Cipher javaAES256 = Cipher.getInstance("AES/GCM/NoPadding");
    byte[] tag = new byte[16];
    long encryptInitTime = 0L;
    long encryptUpdate1Time = 0L;
    long encryptDoFinalTime = 0L;
    while (System.currentTimeMillis() - javaEncryptStartTime < 10000) {
        random.nextBytes(iv);
        long n1 = System.nanoTime();
        javaAES256.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(key, "AES"), new GCMParameterSpec(16 * Byte.SIZE, iv));
        long n2 = System.nanoTime();
        javaAES256.update(data, 0, data.length, encrypted, 0);
        long n3 = System.nanoTime();
        javaAES256.doFinal(tag, 0);
        long n4 = System.nanoTime();
        javaEncryptInputBytes += data.length;

        encryptInitTime = n2 - n1;
        encryptUpdate1Time = n3 - n2;
        encryptDoFinalTime = n4 - n3;
    }
    long javaEncryptEndTime = System.currentTimeMillis();
    System.out.println("Time init (ns): "     + encryptInitTime);
    System.out.println("Time update (ns): "   + encryptUpdate1Time);
    System.out.println("Time do final (ns): " + encryptDoFinalTime);
    System.out.println("Java calculated at " + (javaEncryptInputBytes / 1024 / 1024 / ((javaEncryptEndTime - javaEncryptStartTime) / 1000)) + " MB/s");

    System.out.println("Benchmarking AES-256 GCM decryption for 10 seconds");
    long javaDecryptInputBytes = 0;
    long javaDecryptStartTime = System.currentTimeMillis();
    final GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(16 * Byte.SIZE, iv);
    final SecretKeySpec keySpec = new SecretKeySpec(key, "AES");
    long decryptInitTime = 0L;
    long decryptUpdate1Time = 0L;
    long decryptUpdate2Time = 0L;
    long decryptDoFinalTime = 0L;
    while (System.currentTimeMillis() - javaDecryptStartTime < 10000) {
        long n1 = System.nanoTime();
        javaAES256.init(Cipher.DECRYPT_MODE, keySpec, gcmParameterSpec);
        long n2 = System.nanoTime();
        int offset = javaAES256.update(encrypted, 0, encrypted.length, data, 0);
        long n3 = System.nanoTime();
        javaAES256.update(tag, 0, tag.length, data, offset);
        long n4 = System.nanoTime();
        javaAES256.doFinal(data, offset);
        long n5 = System.nanoTime();
        javaDecryptInputBytes += data.length;

        decryptInitTime += n2 - n1;
        decryptUpdate1Time += n3 - n2;
        decryptUpdate2Time += n4 - n3;
        decryptDoFinalTime += n5 - n4;
    }
    long javaDecryptEndTime = System.currentTimeMillis();
    System.out.println("Time init (ns): " + decryptInitTime);
    System.out.println("Time update 1 (ns): " + decryptUpdate1Time);
    System.out.println("Time update 2 (ns): " + decryptUpdate2Time);
    System.out.println("Time do final (ns): " + decryptDoFinalTime);
    System.out.println("Total bytes processed: " + javaDecryptInputBytes);
    System.out.println("Java calculated at " + (javaDecryptInputBytes / 1024 / 1024 / ((javaDecryptEndTime - javaDecryptStartTime) / 1000)) + " MB/s");
}

編集: この単純なベンチマークを改善するための楽しい演習として残します。

ServerVM を使用してさらにテストし、nanoTime 呼び出しを削除し、ウォームアップを導入しましたが、予想どおり、ベンチマークの結果が改善されることはありませんでした。1 秒あたり 3メガバイトで横ばいです。

4

3 に答える 3

20

マイクロベンチマークは別として、JDK 8 (少なくとも 1.8.0_25 まで) での GCM 実装のパフォーマンスは機能しません。

より成熟したマイクロベンチマークを使用して、(Haswell i7 ラップトップで) 3MB/s を一貫して再現できます。

コード ダイブから、これはナイーブな乗算器の実装と、GCM 計算のハードウェア アクセラレーションがないことが原因であると思われます。

比較すると、JDK 8 の AES (ECB または CBC モード) は AES-NI 高速組み込みを使用し、(少なくとも Java の場合) 非常に高速です (同じハードウェアで 1GB/秒のオーダー)。パフォーマンスは、破損した GCM パフォーマンスによって完全に支配されます。

ハードウェア アクセラレーションを実装する計画があり、 でパフォーマンスを改善するサード パーティの提案がありましたが、これらはまだリリースされていません。

他に注意すべき点は、JDK GCM 実装は、暗号文の末尾にある認証タグが検証されるまで、復号化時に平文全体をバッファリングすることです。これにより、大きなメッセージで使用するために暗号文が機能しなくなります。

Bouncy Castle には (執筆時点で) より高速な GCM 実装 (および、ソフトウェア特許法によって妨げられていないオープン ソース ソフトウェアを作成している場合は OCB) があります。


2015 年 7 月更新 - 1.8.0_45 および JDK 9

JDK 8+ は、改善された (そして一定時間の) Java 実装 (RedHat の Florian Weimer による寄稿) を取得します。JDK9 (少なくとも EA b72 以降) には GCM 組み込み関数もあります。b72 での AES/GCM 速度は、組み込み関数を有効にしない場合は 18MB/秒、組み込み関数を有効にすると 25MB/秒です。どちらもがっかりです - 比較のために、最速の (一定時間ではない) BC実装は ~60MB/s で、最も遅い (一定時間、完全に最適化されていない) は ~26MB/s です。


2016 年 1 月更新 - 1.8.0_72:

一部のパフォーマンス修正は JDK 1.8.0_60に組み込まれ、同じベンチマークでのパフォーマンスは現在 18MB/秒です。これは元の 6 倍の改善ですが、それでも BC 実装よりもはるかに遅いです。

于 2014-11-19T22:13:26.203 に答える
0

OpenSSL の実装は、pclmulqdq 命令 (x86 プラットフォーム) を使用したアセンブリ ルーチンによって最適化されます。並列アルゴリズムにより非常に高速です。

Java の実装は遅いです。ただし、アセンブリ ルーチンを使用して Hotspot でも最適化されています (並列化されていません)。Hotspot 組み込みを使用するには、jvm をウォームアップする必要があります。-XX:CompileThreshold のデフォルト値は 10000 です。

// 疑似コード

warmUp_GCM_cipher_loop10000_times();

do_benchmark();

于 2016-01-27T07:13:30.223 に答える