使用 Java 8u20 进行慢速 AES GCM 加密和解密 [英] Slow AES GCM encryption and decryption with Java 8u20

查看:26
本文介绍了使用 Java 8u20 进行慢速 AES GCM 加密和解密的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试使用 AES/GCM/NoPadding 加密和解密数据.我安装了 JCE Unlimited Strength Policy Files 并运行了下面的(头脑简单的)基准测试.我使用 OpenSSL 完成了同样的工作,并且能够在我的 PC 上实现超过 1 GB/s 的加密和解密.

I am trying to encrypt and decrypt data using AES/GCM/NoPadding. I installed the JCE Unlimited Strength Policy Files and ran the (simple minded) benchmark below. I've done the same using OpenSSL and was able to achieve more than 1 GB/s encryption and decryption on my PC.

在下面的基准测试中,我只能在同一台 PC 上使用 Java 8 获得 3 MB/s 加密和解密.知道我做错了什么吗?

With the benchmark below I'm only able to get 3 MB/s encryption and decryption using Java 8 on the same PC. Any idea what I am doing wrong?

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");
}

我把它作为一个有趣的练习来改进这个头脑简单的基准.

I leave it as a fun exercise to improve this simple minded benchmark.

我已经使用 ServerVM 进行了更多测试,删除了 nanoTime 调用并引入了预热,但正如我所料,这些都没有对基准测试结果有任何改进.它以每秒 3 兆字节的速度保持平稳.

I've tested some more using the ServerVM, removed nanoTime calls and introduced warmup, but as I expected none of this had any improvement on the benchmark results. It is flat-lined at 3 megabytes per second.

推荐答案

撇开微基准测试不谈,JDK 8(至少到 1.8.0_25)中的 GCM 实现的性能很差.

Micro-benchmarking aside, the performance of the GCM implementation in JDK 8 (at least up to 1.8.0_25) is crippled.

我可以使用更成熟的微基准测试始终如一地重现 3MB/s(在 Haswell i7 笔记本电脑上).

I can consistently reproduce the 3MB/s (on a Haswell i7 laptop) with a more mature micro-benchmark.

来自 代码潜水,这似乎是由于简单的乘法器实现和 GCM 计算没有硬件加速.

From a code dive, this appears to be due to a naive multiplier implementation and no hardware acceleration for the GCM calculations.

相比之下,JDK 8 中的 AES(在 ECB 或 CBC 模式下)使用 AES-NI 加速内在,并且(至少对于 Java)非常快(在相同硬件上大约为 1GB/s),但总体而言AES/GCM 性能完全由破碎的 GCM 性能主导.

By comparison AES (in ECB or CBC mode) in JDK 8 uses an AES-NI accelerated intrinsic and is (for Java at least) very quick (in the order of 1GB/s on the same hardware), but the overall AES/GCM performance is completely dominated by the broken GCM performance.

计划实施硬件加速,以及已经有第三方提交以提高性能,但是这些还没有发布.

There are plans to implement hardware acceleration, and there have been third party submissions to improve the performance with, but these haven't made it to a release yet.

还有一点需要注意的是,JDK GCM 实现还会在解密时缓冲整个明文,直到密文末尾的身份验证标记得到验证,这使其无法用于处理大消息.

Something else to be aware of is that the JDK GCM implementation also buffers the entire plaintext on decryption until the authentication tag at the end of the ciphertext is verified, which cripples it for use with large messages.

Bouncy Castle(在撰写本文时)拥有更快的 GCM 实现(如果您正在编写不受软件专利法阻碍的开源软件,则还有 OCB).

Bouncy Castle has (at the time of writing) faster GCM implementations (and OCB if you're writing open source software of not encumbered by software patent laws).

2015 年 7 月更新 - 1.8.0_45 和 JDK 9

Updated July 2015 - 1.8.0_45 and JDK 9

JDK 8+ 将获得改进的(和恒定时间的)Java 实现(由 RedHat 的 Florian Weimer 提供)——这已经在 J​​DK 9 EA 版本中登陆,但显然还没有在 1.8.0_45 中.JDK9(至少从 EA b72 开始)也有 GCM 内在函数——b72 上的 AES/GCM 速度在未启用内在函数时为 18MB/s,在启用内在函数时为 25MB/s,两者都令人失望——为了比较最快(非恒定时间)BC实现为 ~60MB/s,最慢(恒定时间,未完全优化)为 ~26MB/s.

JDK 8+ will get an improved (and constant time) Java implementation (contributed by Florian Weimer of RedHat) - this has landed in JDK 9 EA builds, but apparently not yet in 1.8.0_45. JDK9 (since EA b72 at least) also has GCM intrinsics - AES/GCM speed on b72 is 18MB/s without intrinsics enabled and 25MB/s with intrinsics enabled, both of which are disappointing - for comparison the fastest (not constant time) BC implementation is ~60MB/s and the slowest (constant time, not fully optimised) is ~26MB/s.

2016 年 1 月更新 - 1.8.0_72:

Updated Jan 2016 - 1.8.0_72:

JDK 1.8.0_60 中的一些性能修复和性能相同基准现在是 18MB/s - 比原来提高了 6 倍,但仍然比 BC 实现慢得多.

Some performance fixes landed in JDK 1.8.0_60 and performance on the same benchmark now is 18MB/s - a 6x improvement from the original, but still much slower than the BC implementations.

这篇关于使用 Java 8u20 进行慢速 AES GCM 加密和解密的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆