Криптографические сравнения Java: использует ли SunJCE собственный код? - PullRequest
0 голосов
/ 11 апреля 2019

Я написал тест для сравнения производительности некоторых провайдеров шифрования Java (вставлено ниже). Я был удивлен, обнаружив, что реализация SunJCE оказалась самой быстрой, поскольку другие (по крайней мере, Apache Commons Crypto) полагаются на собственную реализацию openssl.

  1. Использует ли SunJCE также нативную реализацию?
  2. Есть ли проблема с этим тестом, которая дает мне неправильные / вводящие в заблуждение результаты?
// 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)
}

1 Ответ

0 голосов
/ 12 апреля 2019

Да, это так. И нет, это не так.

AES-NI используется через встроенные функции Java , которые заменяют байт-код собственной реализацией AES. Поэтому, хотя вы не найдете прямого вызова инструкций AES-NI, вызов doFinal в вашем коде Java в какой-то момент будет использовать аппаратное ускорение. Таким образом, код JCE - это просто Java, но JIT все еще может ускорить его. Отличная, да?

Чтобы действительно протестировать ваш код, вы можете использовать время разогрева для JIT (также для включения AES-NI). Вы должны иметь возможность использовать буфер вместо создания нового объекта массива каждый раз для зашифрованного текста.

Что еще более важно, может потребоваться перехватить вывод этого буфера и, например, XOR в окончательный буфер и распечатать. Это сделает практически невозможным для компилятора вообще пропустить код. С оптимизацией компилятора сложно справиться, если вас не интересуют сами результаты; в конце концов, компилятор или JIT могут просто пропустить шифрование, чтобы получить тот же результат.

Возможно, вам потребуется больше операций AES в одном цикле. Вы можете внедрить тестирование Монте-Карло, необходимое для соревнований AES, в качестве базы.

...