Skip to content

Commit

Permalink
Lazy release of EVP context
Browse files Browse the repository at this point in the history
  • Loading branch information
amirhosv committed Sep 15, 2023
1 parent 5de55c4 commit 833ee8a
Show file tree
Hide file tree
Showing 9 changed files with 369 additions and 196 deletions.
30 changes: 29 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -667,6 +667,15 @@ add_custom_target(check-junit-SecurityManager

DEPENDS accp-jar tests-jar)

add_custom_target(check-junit-AesGcmLazy
COMMAND ${TEST_JAVA_EXECUTABLE}
-Dcom.amazon.corretto.crypto.provider.freeNativeContextEagerly=false
${TEST_RUNNER_ARGUMENTS}
--select-class=com.amazon.corretto.crypto.provider.test.AesTest
--select-class=com.amazon.corretto.crypto.provider.test.AesGcmKatTest

DEPENDS accp-jar tests-jar)

add_custom_target(check-junit-extra-checks
COMMAND ${TEST_JAVA_EXECUTABLE}
-Dcom.amazon.corretto.crypto.provider.extrachecks=ALL
Expand Down Expand Up @@ -759,7 +768,15 @@ add_custom_target(check-install-via-properties-with-debug
DEPENDS accp-jar tests-jar)

add_custom_target(check
DEPENDS check-recursive-init check-install-via-properties check-install-via-properties-with-debug check-junit check-junit-SecurityManager check-external-lib check-libaccp-rpath)
DEPENDS check-recursive-init
check-install-via-properties
check-install-via-properties-with-debug
check-junit
check-junit-SecurityManager
check-external-lib
check-libaccp-rpath
check-junit-AesGcmLazy
check-integration-lazy)

if(ENABLE_NATIVE_TEST_HOOKS)
add_custom_target(check-keyutils
Expand Down Expand Up @@ -803,6 +820,17 @@ add_custom_target(check-integration

set_target_properties(check-integration PROPERTIES EXCLUDE_FROM_ALL 1)

add_custom_target(check-integration-lazy
COMMAND ${TEST_JAVA_EXECUTABLE}
-Dcom.amazon.corretto.crypto.provider.freeNativeContextEagerly=false
${TEST_RUNNER_ARGUMENTS}
--reports-dir=integration-tests
--select-package=com.amazon.corretto.crypto.provider.test.integration

DEPENDS accp-jar tests-jar)

set_target_properties(check-integration-lazy PROPERTIES EXCLUDE_FROM_ALL 1)

if (CMAKE_SYSTEM_PROCESSOR STREQUAL "x86_64")

add_custom_target(run-dieharder-libcrypto-rng
Expand Down
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ AmazonCorrettoCryptoProvider.INSTANCE.assertHealthy();

### Other system properties
ACCP can be configured via several system properties.
None of these should be needed for standard deployments and we recommend not touching them.
None of these should be needed for standard deployments, and we recommend not touching them.
They are of most use to developers needing to test ACCP or experiment with benchmarking.
These are all read early in the load process and may be cached so any changes to them made from within Java may not be respected.
Thus, these should all be set on the JVM command line using `-D`.
Expand Down Expand Up @@ -315,7 +315,14 @@ Thus, these should all be set on the JVM command line using `-D`.
This means that there is a slight "pause" before ACCP FIPS's SecureRandom can produce pseudo-random bytes in highly threaded environments.
Because, in extreme cases this could present an availability risk, we do not register LibCryptoRng by default in configurations where this initialization cost is incurred (i.e. FIPS mode).
Non-FIPS AWS-LC does not use CPU jitter for its DRBG seed's entropy, and therefore does not incur this initialization cost, therefore we register LibCryptoRng by default when not in FIPS mode.

* `com.amazon.corretto.crypto.provider.freeNativeContextEagerly`
Takes in `true` or `false` (defaults to `true`). If `true`, the underlying `EVP_CIPHER_CTX`
of a GCM `Cipher` is freed as soon as possible; otherwise, the underlying `EVP_CIPHER_CTX`
is not released util the `Cipher` object is garbage collected. When reusing the same `Cipher`
object, it would be beneficial to set this system property to `false` so that different
encryption/decryption operations would not require allocation and release of `EVP_CIPHER_CTX`
structure. A common use case would be having long-running threads that each would get its
own instance of `Cipher` object.

# License
This library is licensed under the Apache 2.0 license although portions of this
Expand Down
2 changes: 2 additions & 0 deletions benchmarks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ The benchmarks can use locally built ACCP or published ACCP.
The `lib:jmh` Gradle task runs the benchmarks and generates reports in JSON and HTML.
The reports are saved under `lib/build/results/jmh`.

* `-Pib="INLCUDE_BENCHMARK"` would only run specified benchmark.

### Benchmarking published ACCP to Maven

```bash
Expand Down
6 changes: 5 additions & 1 deletion benchmarks/lib/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
val accpVersion: String? by project
val accpLocalJar: String by project
val fips: Boolean by project
val ib: String by project

plugins {
`java-library`
Expand Down Expand Up @@ -46,7 +47,9 @@ java {
}

jmh {
// includes.add("AesXts") // can be used to run a subset of benchmarks
if (project.hasProperty("ib")) {
includes.add(ib)
}
fork.set(1)
benchmarkMode.add("thrpt")
threads.set(1)
Expand All @@ -59,6 +62,7 @@ jmh {
duplicateClassesStrategy.set(DuplicatesStrategy.WARN)
jvmArgs.add("-DversionStr=${accpVersion}")
jvmArgs.add("-Dcom.amazon.corretto.crypto.provider.registerSecureRandom=true")
jvmArgs.add("-Dcom.amazon.corretto.crypto.provider.freeNativeContextEagerly=false")
}

jmhReport {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.amazon.corretto.crypto.provider.benchmarks;

import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
import lombok.SneakyThrows;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@State(Scope.Thread)
public class CipherReuse {
Cipher shared;
SecretKeySpec key;
Random random = new Random();
byte[] iv = new byte[12];
byte[] input = new byte[1000];
byte[] keyb = new byte[32];
byte[] aad = new byte[100];

@Param({"true", "false"})
private boolean newKey;

@Setup
public void setup() throws Exception {
shared = getAesGcmCipherFromAccp();
key = new SecretKeySpec(new byte[]{1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8}, "AES");
random.nextBytes(input);
random.nextBytes(aad);
}

@Benchmark
public void newInstance(Blackhole blackhole) throws Exception {
blackhole.consume(encrypt(getAesGcmCipherFromAccp()));
}

@Benchmark
public void reuse(Blackhole blackhole) throws Exception {
blackhole.consume(encrypt(shared));
}

byte[] encrypt(Cipher cipher) throws Exception {
random.nextBytes(iv);
keyb[0]++;
final String name = random.nextBoolean() ? "AES" : "aes";
final var sk = newKey ? new SecretKeySpec(keyb, name) : new SecretKeySpec(key.getEncoded(), name);
cipher.init(Cipher.ENCRYPT_MODE, sk, new GCMParameterSpec(128, iv));
cipher.updateAAD(aad);
final var cipherText = cipher.doFinal(input);
cipher.init(Cipher.DECRYPT_MODE, sk, new GCMParameterSpec(128, iv));
cipher.updateAAD(aad);
return cipher.doFinal(cipherText);
}

private Cipher getAesGcmCipherFromAccp() {
try {
return Cipher.getInstance("AES/GCM/NoPadding", AmazonCorrettoCryptoProvider.INSTANCE);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (NoSuchPaddingException e) {
throw new RuntimeException(e);
}
}
}
103 changes: 41 additions & 62 deletions csrc/aes_gcm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@

using namespace AmazonCorrettoCryptoProvider;

static void initContext(raii_env& env, raii_cipher_ctx& ctx, jint opMode, java_buffer key, java_buffer iv)
static void initContext(raii_env& env, raii_cipher_ctx& ctx, jint opMode, java_buffer& key, java_buffer& iv)
{
const EVP_CIPHER* cipher;

Expand Down Expand Up @@ -66,6 +66,34 @@ static void initContext(raii_env& env, raii_cipher_ctx& ctx, jint opMode, java_b
}
}

static void initializeContext(raii_env& env,
jlong ctxPtr,
raii_cipher_ctx& ctx,
jboolean sameKey,
jbyteArray keyArray,
jbyteArray ivArray,
int enc)
{
if (ctxPtr != 0) {
ctx.borrow(reinterpret_cast<EVP_CIPHER_CTX*>(ctxPtr));
} else {
ctx.init();
EVP_CIPHER_CTX_init(ctx);
}

java_buffer iv = java_buffer::from_array(env, ivArray);

if (ctxPtr != 0 && sameKey == JNI_TRUE) {
jni_borrow ivBorrow(env, iv, "iv");
if (unlikely(!EVP_CipherInit_ex(ctx, NULL, NULL, NULL, ivBorrow.data(), enc))) {
throw java_ex::from_openssl(EX_RUNTIME_CRYPTO, "Failed to set IV");
}
} else {
java_buffer key = java_buffer::from_array(env, keyArray);
initContext(env, ctx, enc, key, iv);
}
}

static int updateLoop(raii_env& env, java_buffer out, java_buffer in, EVP_CIPHER_CTX* ctx)
{
int total_output = 0;
Expand Down Expand Up @@ -145,6 +173,7 @@ static int cryptFinish(raii_env& env, int opMode, java_buffer resultBuf, unsigne
JNIEXPORT int JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_oneShotEncrypt(JNIEnv* pEnv,
jclass,
jlong ctxPtr,
jboolean sameKey,
jlongArray ctxOut,
jbyteArray inputArray,
jint inoffset,
Expand All @@ -157,25 +186,12 @@ JNIEXPORT int JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_oneShot
{
try {
raii_env env(pEnv);
raii_cipher_ctx ctx;

initializeContext(env, ctxPtr, ctx, sameKey, keyArray, ivArray, NATIVE_MODE_ENCRYPT);

java_buffer input = java_buffer::from_array(env, inputArray, inoffset, inlen);
java_buffer result = java_buffer::from_array(env, resultArray, resultOffset);
java_buffer iv = java_buffer::from_array(env, ivArray);

raii_cipher_ctx ctx;
if (ctxPtr) {
ctx.borrow(reinterpret_cast<EVP_CIPHER_CTX*>(ctxPtr));

jni_borrow ivBorrow(env, iv, "iv");
if (unlikely(!EVP_CipherInit_ex(ctx, NULL, NULL, NULL, ivBorrow.data(), NATIVE_MODE_ENCRYPT))) {
throw java_ex::from_openssl(EX_RUNTIME_CRYPTO, "Failed to set IV");
}
} else {
ctx.init();
EVP_CIPHER_CTX_init(ctx);
java_buffer key = java_buffer::from_array(env, keyArray);
initContext(env, ctx, NATIVE_MODE_ENCRYPT, key, iv);
}

int outoffset = updateLoop(env, result, input, ctx);
if (outoffset < 0)
Expand All @@ -197,41 +213,16 @@ JNIEXPORT int JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_oneShot
}
}

JNIEXPORT void JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_encryptInit__J_3B(
JNIEnv* pEnv, jclass, jlong ctxPtr, jbyteArray ivArray)
{
try {
raii_env env(pEnv);

if (!ctxPtr)
throw java_ex(EX_NPE, "Null context");

EVP_CIPHER_CTX* ctx = reinterpret_cast<EVP_CIPHER_CTX*>(ctxPtr);
java_buffer iv = java_buffer::from_array(env, ivArray);

jni_borrow ivBorrow(env, iv, "iv");
if (unlikely(!EVP_CipherInit_ex(ctx, NULL, NULL, NULL, ivBorrow.data(), NATIVE_MODE_ENCRYPT))) {
throw java_ex::from_openssl(EX_RUNTIME_CRYPTO, "Failed to set IV");
}
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
}
}

JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_encryptInit___3B_3B(
JNIEnv* pEnv, jclass, jbyteArray keyArray, jbyteArray ivArray)
JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_encryptInit(
JNIEnv* pEnv, jclass, jlong ctxPtr, jboolean sameKey, jbyteArray keyArray, jbyteArray ivArray)
{
raii_cipher_ctx ctx;
ctx.init();
EVP_CIPHER_CTX_init(ctx);

try {
raii_env env(pEnv);
raii_cipher_ctx ctx;

java_buffer key = java_buffer::from_array(env, keyArray);
java_buffer iv = java_buffer::from_array(env, ivArray);

initContext(env, ctx, NATIVE_MODE_ENCRYPT, key, iv);
initializeContext(env, ctxPtr, ctx, sameKey, keyArray, ivArray, NATIVE_MODE_ENCRYPT);

return (jlong)ctx.take();
} catch (java_ex& ex) {
Expand Down Expand Up @@ -348,6 +339,7 @@ JNIEXPORT jint JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_encryp
JNIEXPORT jint JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_oneShotDecrypt(JNIEnv* pEnv,
jclass,
jlong ctxPtr,
jboolean sameKey,
jlongArray ctxOut,
jbyteArray inputArray,
jint inoffset,
Expand All @@ -362,25 +354,12 @@ JNIEXPORT jint JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSpi_oneSho
{
try {
raii_env env(pEnv);
raii_cipher_ctx ctx;

initializeContext(env, ctxPtr, ctx, sameKey, keyArray, ivArray, NATIVE_MODE_DECRYPT);

java_buffer input = java_buffer::from_array(env, inputArray, inoffset, inlen);
java_buffer result = java_buffer::from_array(env, resultArray, resultOffset);
java_buffer iv = java_buffer::from_array(env, ivArray);

raii_cipher_ctx ctx;
if (ctxPtr) {
ctx.borrow(reinterpret_cast<EVP_CIPHER_CTX*>(ctxPtr));

jni_borrow ivBorrow(env, iv, "iv");
if (unlikely(!EVP_CipherInit_ex(ctx, NULL, NULL, NULL, ivBorrow.data(), NATIVE_MODE_DECRYPT))) {
throw java_ex::from_openssl(EX_RUNTIME_CRYPTO, "Failed to set IV");
}
} else {
ctx.init();
EVP_CIPHER_CTX_init(ctx);
java_buffer key = java_buffer::from_array(env, keyArray);
initContext(env, ctx, NATIVE_MODE_DECRYPT, key, iv);
}

// Decrypt mode: Set the tag before we decrypt
if (unlikely(tagLen > 16 || tagLen < 0)) {
Expand Down
Loading

0 comments on commit 833ee8a

Please sign in to comment.