Java 加密比较:SunJCE 是否使用本机代码?
Java crypto comparisons: Does SunJCE use native code?
我编写了一个测试来比较一些 Java 加密提供程序的性能(粘贴在下面)。我惊讶地发现 SunJCE 实现最终成为最快的,因为其他(至少 Apache Commons Crypto)依赖于本机 openssl 实现。
- SunJCE 是否也使用本机实现?
- 我的 incorrect/misleading 结果是这个测试有问题吗?
// Nss installed on mac via "brew install nss"
fun getProviders(): List<Provider> {
return listOf(
Security.getProvider("SunJCE"),
sun.security.pkcs11.SunPKCS11(
"--name=CryptoBenchmark\n"
+ "nssDbMode=noDb\n"
+ "nssLibraryDirectory=/usr/local/opt/nss/lib/\n"
+ "attributes=compatibility"),
BouncyCastleProvider()
)
}
fun blockCipherTests(providers: List<Provider>) {
val ciphers = providers.map {
try {
Cipher.getInstance("AES/CTR/NoPadding", it)
} catch (t: Throwable) {
println("Error getting cipher from provider $it: $t")
throw t
}
}
val key = SecretKeySpec(getUTF8Bytes("1234567890123456"),"AES");
val iv = IvParameterSpec(getUTF8Bytes("1234567890123456"));
val random = Random()
ciphers.forEach {
it.init(Cipher.ENCRYPT_MODE, key)
}
// Crypto commons doesn't implement the provider interface(?) so instantiate that cipher separately
val properties = Properties().apply {
setProperty(CryptoCipherFactory.CLASSES_KEY, CryptoCipherFactory.CipherProvider.OPENSSL.getClassName());
}
val apacheCommonsCipher = Utils.getCipherInstance("AES/CTR/NoPadding", properties)
apacheCommonsCipher.init(Cipher.ENCRYPT_MODE, key, iv)
val data = ByteArray(1500)
val out = ByteArray(1500)
random.nextBytes(data)
repeat (10) {
ciphers.forEach { cipher ->
val time = measureNanoTime {
repeat(100) {
cipher.doFinal(data)
}
}
println("Cipher ${cipher.provider} took ${time / 100} nanos/run")
}
// Run the apache test separately
val time = measureNanoTime {
repeat(100) {
apacheCommonsCipher.doFinal(data, 0, 1000, out, 0)
}
}
println("Cipher apache took ${time / 100} nanos/run")
println("====================================")
}
}
fun main() {
val providers = getProviders()
println(providers)
blockCipherTests(providers)
}
是的,确实如此。不,它没有。
AES-NI 通过 Java 内在函数 使用,它用 AES 的本机实现替换字节码。因此,尽管您找不到对 AES-NI 指令的直接调用,但 Java 代码中的 doFinal
调用有时会使用硬件加速。所以JCE的代码就是Java,但是JIT还是可以加速的。漂亮吧?
要真正测试您的代码,您可能需要为 JIT 使用预热时间(同时启用 AES-NI)。您应该能够使用缓冲区而不是每次为密文生成一个新的数组对象。
更重要的是,可能想要捕获该缓冲区的输出,例如将其异或到最终缓冲区并打印出来。这将使编译器几乎不可能完全跳过代码。如果您对结果本身并不真正感兴趣,那么编译器优化就很难处理;毕竟,编译器或 JIT 可能只是完全跳过加密以获得相同的结果。
您可能还需要在单个循环中进行更多 AES 运算。您可以实施 AES 竞赛所需的 Monte Carlo 测试作为基础。
我编写了一个测试来比较一些 Java 加密提供程序的性能(粘贴在下面)。我惊讶地发现 SunJCE 实现最终成为最快的,因为其他(至少 Apache Commons Crypto)依赖于本机 openssl 实现。
- SunJCE 是否也使用本机实现?
- 我的 incorrect/misleading 结果是这个测试有问题吗?
// Nss installed on mac via "brew install nss"
fun getProviders(): List<Provider> {
return listOf(
Security.getProvider("SunJCE"),
sun.security.pkcs11.SunPKCS11(
"--name=CryptoBenchmark\n"
+ "nssDbMode=noDb\n"
+ "nssLibraryDirectory=/usr/local/opt/nss/lib/\n"
+ "attributes=compatibility"),
BouncyCastleProvider()
)
}
fun blockCipherTests(providers: List<Provider>) {
val ciphers = providers.map {
try {
Cipher.getInstance("AES/CTR/NoPadding", it)
} catch (t: Throwable) {
println("Error getting cipher from provider $it: $t")
throw t
}
}
val key = SecretKeySpec(getUTF8Bytes("1234567890123456"),"AES");
val iv = IvParameterSpec(getUTF8Bytes("1234567890123456"));
val random = Random()
ciphers.forEach {
it.init(Cipher.ENCRYPT_MODE, key)
}
// Crypto commons doesn't implement the provider interface(?) so instantiate that cipher separately
val properties = Properties().apply {
setProperty(CryptoCipherFactory.CLASSES_KEY, CryptoCipherFactory.CipherProvider.OPENSSL.getClassName());
}
val apacheCommonsCipher = Utils.getCipherInstance("AES/CTR/NoPadding", properties)
apacheCommonsCipher.init(Cipher.ENCRYPT_MODE, key, iv)
val data = ByteArray(1500)
val out = ByteArray(1500)
random.nextBytes(data)
repeat (10) {
ciphers.forEach { cipher ->
val time = measureNanoTime {
repeat(100) {
cipher.doFinal(data)
}
}
println("Cipher ${cipher.provider} took ${time / 100} nanos/run")
}
// Run the apache test separately
val time = measureNanoTime {
repeat(100) {
apacheCommonsCipher.doFinal(data, 0, 1000, out, 0)
}
}
println("Cipher apache took ${time / 100} nanos/run")
println("====================================")
}
}
fun main() {
val providers = getProviders()
println(providers)
blockCipherTests(providers)
}
是的,确实如此。不,它没有。
AES-NI 通过 Java 内在函数 使用,它用 AES 的本机实现替换字节码。因此,尽管您找不到对 AES-NI 指令的直接调用,但 Java 代码中的 doFinal
调用有时会使用硬件加速。所以JCE的代码就是Java,但是JIT还是可以加速的。漂亮吧?
要真正测试您的代码,您可能需要为 JIT 使用预热时间(同时启用 AES-NI)。您应该能够使用缓冲区而不是每次为密文生成一个新的数组对象。
更重要的是,可能想要捕获该缓冲区的输出,例如将其异或到最终缓冲区并打印出来。这将使编译器几乎不可能完全跳过代码。如果您对结果本身并不真正感兴趣,那么编译器优化就很难处理;毕竟,编译器或 JIT 可能只是完全跳过加密以获得相同的结果。
您可能还需要在单个循环中进行更多 AES 运算。您可以实施 AES 竞赛所需的 Monte Carlo 测试作为基础。