使用Java 8u20进行慢速AES GCM加密和解密

时间:2014-09-23 09:56:54

标签: cryptography benchmarking java-8 aes-gcm

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

在下面的基准测试中,我只能在同一台PC上使用Java 8进行 3 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兆字节。

3 个答案:

答案 0 :(得分:20)

除了微观基准测试之外,JDK 8中GCM实现的性能(至少高达1.8.0_25)已经瘫痪。

我可以通过更成熟的微基准来持续重现3MB / s(在Haswell i7笔记本电脑上)。

code dive开始,这似乎是由于天真的乘数实现而没有用于GCM计算的硬件加速。

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

plans to implement hardware accelerationthere have been third party submissions to improve the performance with,但这些尚无法发布。

需要注意的是,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_60中,同一基准测试的性能现在是18MB / s - 比原来的提升了6倍,但仍然比BC实现慢得多。

答案 1 :(得分:4)

现在已在Java 8u60中使用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来启用覆盖安全属性(实际路径可能因操作系统而异)并添加:

policy.allowSystemProperty=true

答案 2 :(得分:0)

OpenSSL实现由assembly routine使用pclmulqdq指令(x86平台)进行优化。由于并行算法,它非常快。

java实现很慢。但它也使用装配程序(不是并行)在Hotspot中进行了优化。你必须热身jvm才能使用Hotspot内在。 -XX:CompileThreshold的默认值为10000.

//伪代码

warmUp_GCM_cipher_loop10000_times();

do_benchmark();