-
Notifications
You must be signed in to change notification settings - Fork 56
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow users to control the release of EVP context for AES-GCM #329
Conversation
cd1a95a
to
833ee8a
Compare
833ee8a
to
b22bd39
Compare
README.md
Outdated
|
||
* `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` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This documentation is misleading. We are naming this property very generically, but it only impacts AES-GCM. It doesn't makes sense to expand the scope of this work to allow re-use for all algorithms. I suggest leaving the naming as-is, but calling out that this only applies to AES-GCM more clearly (This leaves the door open to more context reuse in the future).
benchmarks/README.md
Outdated
@@ -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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: typo
benchmarks/lib/build.gradle.kts
Outdated
@@ -1,6 +1,7 @@ | |||
val accpVersion: String? by project | |||
val accpLocalJar: String by project | |||
val fips: Boolean by project | |||
val ib: String by project |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: The name is ambiguous. Maybe give it a long name instead? I think Gradle is smart enough to shorten a long name automatically so we can have it both ways.
byte[] encrypt(Cipher cipher) throws Exception { | ||
random.nextBytes(iv); | ||
keyb[0]++; | ||
final String name = random.nextBoolean() ? "AES" : "aes"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems unecessary for this benchmark.
2dc467b
to
9ae3a60
Compare
6ea55e1
to
680e57c
Compare
|
||
byte[] encodedKey = null; | ||
if (jceKey != key) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOT A CONTRIBUTION
By removing the straight object equality check you are incurring additional allocation and computation even when the key doesn't change.
NOT A CONTRIBUTION
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check only saves time if the Cipher object is reused AND the same AES key is use AND the key is the same Java object; in all other cases, keeping jceKey
around not beneficial. From security perspective, jceKey
is a cryptographic material and it's not a good practice to keep a reference to a secret object for longer than it is needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOT A CONTRIBUTION
Consider the common case of a single Cipher object being used to encrypt/decrypt a lot of data with the same key but different parameters (iv or gcm). In this case you can absolutely expect to see the exact same Java key object provided each time. What's more, I would expect that the majority of the time that a cipher object is reused, it is reused with the same Java key object.
You're already keeping the cryptographic materials around in private byte[] key
, so minimizing it here doesn't help. What's more, as you're calling getEncoded()
each time for the comparison, you're resulting in more copies of the cryptographic material spilling to memory than before this change. (In other words, your change will result in more copies of the key in memory rather than fewer.)
NOT A CONTRIBUTION
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Despite not having any impact on performance, I brought back the logic. I'm not sure about how "common" this pattern is, since we have many internal customers that prefer not reuse a Cipher object so that they don't have to deal with thread-safety issues.
* The number a times a key must be reused prior to keeping it in native memory rather than | ||
* freeing it each time. | ||
*/ | ||
private static final int KEY_REUSE_THRESHOLD = 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOT A CONTRIBUTION
By removing KEY_REUSE_THRESHOLD
you are removing all context reuse even when the key doesn't change. This is likely to degrade performance for the majority of uses (unless they explicitly opt into your new logic). Have you checked for regression under existing benchmarks?
NOT A CONTRIBUTION
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The HYBRID
mode, which is the default, preserves the existing behavior and our benchmarks show that there is no regression. When threshold is one, the same effect can be achieved by using a single boolean variable (sameKey
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NOT A CONTRIBUTION
My apologies. I missed the introduction of HYBRID mode.
However, have you checked existing benchmarks or only the new ones that you introduced in this change?
NOT A CONTRIBUTION
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I did the run the existing benchmarks and as expected, there is no regression. The existing benchmarks utilize the same object so other patterns of usage are not measured. Here are the results of the existing benchmarks:
./gradlew -PaccpVersion="2.3.2" -PincludeBenchmark="AesGcm" lib:jmh
Benchmark (chunkSize) (keyBits) Mode Cnt Score Error Units
AesGcmOneShot.oneShot1MiBDecrypt N/A 128 thrpt 5 2600.963 ± 88.948 ops/s
AesGcmOneShot.oneShot1MiBDecrypt N/A 256 thrpt 5 2179.269 ± 31.994 ops/s
AesGcmOneShot.oneShot1MiBEncrypt N/A 128 thrpt 5 2606.161 ± 75.525 ops/s
AesGcmOneShot.oneShot1MiBEncrypt N/A 256 thrpt 5 2190.634 ± 68.482 ops/s
AesGcmUpdate.updateDecrypt 16 128 thrpt 5 734.160 ± 26.745 ops/s
AesGcmUpdate.updateDecrypt 16 256 thrpt 5 745.288 ± 24.561 ops/s
AesGcmUpdate.updateDecrypt 256 128 thrpt 5 1550.954 ± 47.719 ops/s
AesGcmUpdate.updateDecrypt 256 256 thrpt 5 1704.490 ± 80.432 ops/s
AesGcmUpdate.updateEncrypt 16 128 thrpt 5 67.282 ± 5.458 ops/s
AesGcmUpdate.updateEncrypt 16 256 thrpt 5 66.709 ± 2.305 ops/s
AesGcmUpdate.updateEncrypt 256 128 thrpt 5 789.663 ± 19.540 ops/s
AesGcmUpdate.updateEncrypt 256 256 thrpt 5 737.160 ± 31.125 ops/s
./gradlew -PaccpLocalJar="../../build/cmake/AmazonCorrettoCryptoProvider.jar" -PnativeContextReleaseStrategy="HYBRID" -PincludeBenchmark="AesGcm" lib:jmh
Benchmark (chunkSize) (keyBits) Mode Cnt Score Error Units
AesGcmOneShot.oneShot1MiBDecrypt N/A 128 thrpt 5 2611.790 ± 141.192 ops/s
AesGcmOneShot.oneShot1MiBDecrypt N/A 256 thrpt 5 2175.562 ± 68.551 ops/s
AesGcmOneShot.oneShot1MiBEncrypt N/A 128 thrpt 5 2639.222 ± 63.212 ops/s
AesGcmOneShot.oneShot1MiBEncrypt N/A 256 thrpt 5 2196.239 ± 317.460 ops/s
AesGcmUpdate.updateDecrypt 16 128 thrpt 5 787.723 ± 28.777 ops/s
AesGcmUpdate.updateDecrypt 16 256 thrpt 5 675.053 ± 28.231 ops/s
AesGcmUpdate.updateDecrypt 256 128 thrpt 5 1887.667 ± 64.505 ops/s
AesGcmUpdate.updateDecrypt 256 256 thrpt 5 1652.982 ± 95.543 ops/s
AesGcmUpdate.updateEncrypt 16 128 thrpt 5 67.716 ± 1.974 ops/s
AesGcmUpdate.updateEncrypt 16 256 thrpt 5 66.032 ± 4.465 ops/s
AesGcmUpdate.updateEncrypt 256 128 thrpt 5 789.979 ± 38.398 ops/s
AesGcmUpdate.updateEncrypt 256 256 thrpt 5 738.422 ± 28.252 ops/s
680e57c
to
6275528
Compare
d6df464
to
5438240
Compare
blackhole.consume(encrypt(shared)); | ||
} | ||
|
||
byte[] encrypt(Cipher cipher) throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pedantic: the method name says encrypt, but you do both directions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to encryptDecrypt
} | ||
|
||
static NativeContextReleaseStrategy getNativeContextReleaseStrategyProperty( | ||
final String propertyName) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This interface doesn't make sense. When would you ever need to call this with a different property name? Supporting that seems error-prone. Suggest referencing a constant within this method and removing the indirection inside AmazonCorrettoCryptoProvider.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
super nit: I'd inline this function at this point.
5438240
to
3819c95
Compare
private Cipher getAesGcmCipherFromAccp() { | ||
try { | ||
return Cipher.getInstance(AES_GCM, AmazonCorrettoCryptoProvider.INSTANCE); | ||
} catch (final Exception e) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
very minor nit: both callers of this function are themselves in functions that throw checked exceptions, so you don't need to wrap e
in an unchecked exception here.
c72699c
3819c95
to
c72699c
Compare
Description of changes:
This change introduces a system property that allows one to control when an EVP_CIPHER_CTX (native context for encryption) is released. For use cases when the same Cipher object is used for multiple encryption/decryption operations, this can result in improved performance since it avoids allocation and release of the native context while the Cipher object is not garbage collected.
There are three strategies for releasing the context: 1)
Hybrid
, 2)Eager
, 3)Lazy
.Hybrid
: this is the existing strategy and it's introduced so that existing customers would not be affected. In this strategy, the context is released eagerly, unless it is used with the same key. BenchmarkCipherReuse.reuse
whennewKey
isfalse
shows that the existing optimizations are preserved for customers that use the default setting.Eager
: in this strategy, the context is released as soon as possible. Based onCipherReuse.newInstance
benchmarks, customers that use a fresh instance ofCipher
class for their operations, could experience 30% performance improvement whenEager
strategy is used.Lazy
: the context object is preserved while theCipher
object is not garbage collected. Customers caching Cipher objects. Compared to defaultHybrid
strategy, could experience 16% performance improvement even if they use different keys for with the sameCipher
class; however, using the same key, this strategy andHybrid
perform the same.Here is benchmark results:
ACCP 2.3.2 (the latest release of ACCP on Maven)
HYBRID
EAGER
LAZY
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.