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

Cryptography 使用Java 8u20进行慢速AES GCM加密和解密,cryptography,benchmarking,java-8,aes-gcm,Cryptography,Benchmarking,Java 8,Aes Gcm,我正在尝试使用AES/GCM/NoPadding加密和解密数据。我安装了JCE Unlimited Strength策略文件并运行了下面的(头脑简单的)基准测试。我使用OpenSSL也做了同样的事情,并且能够在我的PC上实现超过1 GB/s的加密和解密 使用下面的基准测试,我只能在同一台PC上使用Java 8进行3 MB/s加密和解密。知道我做错了什么吗 public static void main(String[] args) throws Exception { final byt

我正在尝试使用AES/GCM/NoPadding加密和解密数据。我安装了JCE Unlimited Strength策略文件并运行了下面的(头脑简单的)基准测试。我使用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");
}
publicstaticvoidmain(字符串[]args)引发异常{
最终字节[]数据=新字节[64*1024];
最终字节[]加密=新字节[64*1024];
最终字节[]键=新字节[32];
最终字节[]iv=新字节[12];
最终随机数=新随机数(1);
随机。下个字节(数据);
随机。下个字节(键);
随机。下一字节(iv);
System.out.println(“基准测试AES-256 GCM加密10秒”);
长javaEncryptInputBytes=0;
long javaEncryptStartTime=System.currentTimeMillis();
最终密码javaAES256=Cipher.getInstance(“AES/GCM/NoPadding”);
字节[]标记=新字节[16];
长时间=0L;
长加密更新时间=0L;
long encryptDoFinalTime=0L;
而(System.currentTimeMillis()-javaEncryptStartTime<10000){
随机。下一字节(iv);
long n1=System.nanoTime();
init(Cipher.ENCRYPT_模式,新的SecretKeySpec(key,“AES”),新的GCMParameterSpec(16*Byte.SIZE,iv));
long n2=System.nanoTime();
javaAES256.update(数据,0,data.length,加密,0);
long n3=System.nanoTime();
javaAES256.doFinal(标记,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(“时间更新(ns):”+encryptUpdate1Time);
System.out.println(“最终时间(ns):”+encryptDoFinalTime);
println(“Java在”+(javaEncryptInputBytes/1024/1024/((javaEncryptEndTime-javaEncryptStartTime)/1000)时计算)+“MB/s”);
System.out.println(“基准AES-256 GCM解密10秒”);
长javaDecryptInputBytes=0;
long javaDecryptStartTime=System.currentTimeMillis();
最终GCMPareterSpec GCMPareterSpec=新的GCMPareterSpec(16*Byte.SIZE,iv);
最终SecretKeySpec keySpec=新SecretKeySpec(键,“AES”);
长时间=0升;
长解密更新时间=0L;
长时间=0L;
长时间=0L;
而(System.currentTimeMillis()-javaDecryptStartTime<10000){
long n1=System.nanoTime();
javaAES256.init(Cipher.DECRYPT_模式,keySpec,gcmParameterSpec);
long n2=System.nanoTime();
int offset=javaAES256.update(加密的,0,encrypted.length,数据,0);
long n3=System.nanoTime();
javaAES256.update(tag,0,tag.length,data,offset);
long n4=System.nanoTime();
javaAES256.doFinal(数据,偏移量);
long n5=System.nanoTime();
javaDecryptInputBytes+=data.length;
时间+=n2-n1;
解密更新时间+=n3-n2;
解密Update2Time+=n4-n3;
解密时间+=n5-n4;
}
long javaDecryptEndTime=System.currentTimeMillis();
System.out.println(“Time init(ns):”+decryptInitTime);
System.out.println(“时间更新1(ns):”+decryptUpdate1Time);
System.out.println(“时间更新2(ns):”+decryptUpdate2Time);
System.out.println(“最终完成时间(ns):”+decryptDoFinalTime);
System.out.println(“处理的总字节数:“+javaDecryptInputBytes”);
println(“Java计算在”+(javaDecryptInputBytes/1024/1024/((javaDecryptEndTime-javaDecryptStartTime)/1000))+“MB/s”);
}
编辑: 我把它作为一个有趣的练习来改进这个简单的基准


我已经使用ServerVM测试了更多,删除了nanoTime调用并引入了warmup,但正如我所预期的,这些都没有对基准测试结果产生任何改进。它以每秒3兆字节的速度运行。

撇开微观基准测试不谈,JDK 8中GCM实现的性能(至少高达1.8.0_25)受到了影响

我可以用一个更成熟的微基准持续复制3MB/s(在Haswell i7笔记本电脑上)

从a来看,这似乎是由于简单的乘法器实现和GCM计算没有硬件加速

相比之下,JDK 8中的AES(在ECB或CBC模式下)使用AES-NI加速的内在特性,并且(至少对于Java)非常快(在相同硬件上的速度为1GB/s),但总体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.045中实现。 JDK9(因为至少EA b72)也有GCM内部函数-b72上的AES/GCM速度在未启用内部函数的情况下为18MB/s,在启用内部函数的情况下为25MB/s,两者都令人失望-相比之下,最快(非恒定时间)的BC实现速度约为60MB/s,最慢(恒定时间,而非恒定时间)
jdk.tls.disabledAlgorithms=SSLv3,GCM
java -Djava.security.properties=/path/to/my/java.security ...
policy.allowSystemProperty=true