我正在尝试使用AES/GCM/NoPadding加密和解密数据。我安装了JCE Unlimited Strength Policy Files,并运行了下面的(头脑简单的)基准测试。我使用OpenSSL也做了同样的操作,并且能够在我的电脑上实现超过1 GB/s的加密和解密。
使用下面的基准测试,我只能在同一台电脑上使用Java 8获得3MB/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兆字节的速率排列的。
Micro基准测试除外,JDK8中GCM实现的性能(至少高达1.8.0_25)受到了影响。
我可以用更成熟的微型基准持续复制3MB/s(在Haswell i7笔记本电脑上)。
从代码的角度来看,这似乎是由于简单的乘法器实现和GCM计算没有硬件加速。
相比之下,JDK8中的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.0_45中实现。JDK9(至少因为EA b72)也有GCM内部-b72上的AES/GCM速度在没有启用内部的情况下为18MB/s,在启用内部的条件下为25MB/s,两者都令人失望-相比之下,最快(非恒定时间)的BC实现约为60MB/s,最慢(恒定时间,未完全优化)约为26MB/s。
2016年1月更新-1.8.0_72:
JDK1.8.0_60中进行了一些性能修复,现在同一基准测试的性能为18MB/s,比原来提高了6倍,但仍然比BC实现慢得多。
这一问题现在已经在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
OpenSSL实现通过使用pclmulqdq指令的汇编例程(x86平台)进行优化。由于采用了并行算法,所以速度非常快。
java的实现很慢。但它也在Hotspot中使用汇编例程(非并行)进行了优化。您必须预热jvm才能使用Hotspot内部。-XX:CompileThreshold的默认值为10000。
//伪码
warmUp_GCM_cipher_loop10000_times();
do_benchmark();