web-dev-qa-db-ja.com

Java 8u20を使用した低速のAES GCM暗号化および復号化

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

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

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呼び出しを削除してウォームアップを導入しましたが、予想どおり、ベンチマークの結果に改善はありませんでした。 3 メガバイト /秒で一直線に並んでいます。

22
Christo

マイクロベンチマークはさておき、JDK 8のGCM実装のパフォーマンス(少なくとも1.8.0_25まで)は損なわれています。

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

code dive から、これは単純な乗数の実装によるもので、GCM計算のためのハードウェアアクセラレーションがないためと思われます。

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

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

JDK GCMの実装では、暗号文の最後にある認証タグが検証されるまで、復号化時にプレーンテキスト全体もバッファーに入れられます。これにより、大きなメッセージで使用できないようになります。

Bouncy Castleは(執筆時点で)より高速なGCM実装を備えています(ソフトウェア特許法に拘束されないオープンソースソフトウェアを作成している場合はOCB)。


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

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


2016年1月更新-1.8.0_72:

いくつかのパフォーマンス修正がJDKに導入されました 1.8.0_6 と同じベンチマークでのパフォーマンスは18MB/sになりました-オリジナルから6倍向上しましたが、それでもBC実装よりはるかに遅いです。

20
archie

これは現在、Java 8u60 with JDK-8069072 )で部分的に対処されています。この修正がないと、2.5M/sになります。この修正では、25M/sになります。 GCMは完全に私に60M/sを与えます。

GCMを無効にするには、次の行を含むJava.securityという名前のファイルを作成します。

jdk.tls.disabledAlgorithms=SSLv3,GCM

次に、Javaプロセスで開始します。

Java -Djava.security.properties=/path/to/my/Java.security ...

これが機能しない場合は、/usr/Java/default/jre/lib/security/Java.securityを編集し(実際のパスはOSによって異なる場合があります)、以下を追加して、セキュリティプロパティの上書きを有効にする必要がある場合があります。

policy.allowSystemProperty=true
4
kichik

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

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

//疑似コード

warmUp_GCM_cipher_loop10000_times();

do_benchmark();

0
Xianda Ke