Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ include_directories(${OPENSSL_INCLUDE_DIR} ${JNI_INCLUDE_DIRS} ${JNI_HEADER_DIR}

set(C_SRC
csrc/aes_gcm.cpp
csrc/aes_gcm_siv.cpp
csrc/aes_xts.cpp
csrc/aes_cbc.cpp
csrc/aes_cfb.cpp
Expand Down
18 changes: 18 additions & 0 deletions DIFFERENCES.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,24 @@ To prevent callers from corrupting their signatures, we forbid them from updatin
# Extensions
Applications are unlikely to directly encounter any of these changes but may choose to take advantage of them.

## AES-GCM-SIV enforces a fixed 12-byte nonce and 128-bit tag length
`AES/GCM-SIV/NoPadding` accepts either
[GCMParameterSpec](https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/GCMParameterSpec.html)
or [IvParameterSpec](https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/IvParameterSpec.html)
to supply the nonce. In both cases the nonce must be exactly 12 bytes, as required by
[RFC 8452](https://www.rfc-editor.org/rfc/rfc8452). When using `GCMParameterSpec`, the tag
length must be 128 bits — any other value throws `InvalidAlgorithmParameterException`. When using
`IvParameterSpec`, the 128-bit tag length is implicit (it is the only tag length supported).
Only 128-bit and 256-bit AES keys are supported; 192-bit keys will throw `InvalidKeyException`.

## AES-GCM-SIV tolerates nonce reuse
`AES/GCM-SIV/NoPadding` does not throw when the same key and nonce are reused across multiple
`Cipher.init()` calls. This is intentional — nonce-misuse resistance is the primary design goal of
AES-GCM-SIV ([RFC 8452](https://www.rfc-editor.org/rfc/rfc8452)). While reuse degrades
confidentiality guarantees, it does not compromise authenticity.
This differs from `AES/GCM/NoPadding`, which throws `InvalidAlgorithmParameterException` on
key+nonce reuse.

## AES-GCM supports IvParameterSpec
ACCP allows use of [IvParameterSpec](https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/IvParameterSpec.html) when calling [Cipher.init()](https://docs.oracle.com/javase/8/docs/api/javax/crypto/Cipher.html#init-int-java.security.Key-java.security.spec.AlgorithmParameterSpec-).
This is equivalent to using a [GCMParameterSpec](https://docs.oracle.com/javase/8/docs/api/javax/crypto/spec/GCMParameterSpec.html) with the same IV value and a tag length of 128 bits.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ Mac algorithms:
Cipher algorithms:
* AES/GCM/NoPadding
* AES_\<n\>/GCM/NoPadding, where n can be 128, or 256
* AES/GCM-SIV/NoPadding
* AES_\<n\>/GCM-SIV/NoPadding, where n can be 128 or 256
* AES/KW/NoPadding
* AES/KWP/NoPadding
* AES/XTS/NoPadding
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
package com.amazon.corretto.crypto.provider.benchmarks;

import com.amazon.corretto.crypto.provider.AmazonCorrettoCryptoProvider;
import java.security.spec.AlgorithmParameterSpec;
import javax.crypto.spec.GCMParameterSpec;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;

/**
* Benchmarks for AES-GCM-SIV (RFC 8452) one-shot encrypt and decrypt. SunJCE does not implement
* this algorithm, so only ACCP (backed by AWS-LC) and BouncyCastle are benchmarked here.
*/
@State(Scope.Benchmark)
public class AesGcmSivOneShot extends AesBase {
@Param({"128", "256"})
public int keyBits;

@Param({AmazonCorrettoCryptoProvider.PROVIDER_NAME, "BC"})
public String provider;

@Param({"NoPadding"})
public String padding;

@Setup
public void setup() throws Exception {
super.setup(keyBits, provider, padding);
}

@Override
protected String getMode() {
return "GCM-SIV";
}

@Override
protected AlgorithmParameterSpec createParameterSpec(byte[] iv) {
return new GCMParameterSpec(128, iv);
}

@Override
protected int getIvSize() {
return 12;
}

@Benchmark
public byte[] encrypt() throws Exception {
return super.oneShot1MiBEncrypt();
}

@Benchmark
public byte[] decrypt() throws Exception {
return super.oneShot1MiBDecrypt();
}
}
255 changes: 255 additions & 0 deletions csrc/aes_gcm_siv.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "buffer.h"
#include "env.h"
#include "generated-headers.h"
#include "util.h"
#include <openssl/aead.h>
#include <openssl/err.h>

#define KEY_LEN_AES128 16
#define KEY_LEN_AES256 32
#define GCM_SIV_NONCE_LENGTH 12
#define GCM_SIV_TAG_LENGTH 16

#define EX_BADTAG "javax/crypto/AEADBadTagException"

using namespace AmazonCorrettoCryptoProvider;

static EVP_AEAD_CTX* createAeadCtx(raii_env& env, java_buffer keyBuf)
{
const EVP_AEAD* aead;
switch (keyBuf.len()) {
case KEY_LEN_AES128:
aead = EVP_aead_aes_128_gcm_siv();
break;
case KEY_LEN_AES256:
aead = EVP_aead_aes_256_gcm_siv();
break;
default:
throw java_ex(EX_RUNTIME_CRYPTO, "Unsupported key length for AES-GCM-SIV");
}

SecureBuffer<uint8_t, KEY_LEN_AES256> keybuf;
keyBuf.get_bytes(env, keybuf.buf, 0, keyBuf.len());

EVP_AEAD_CTX* ctx = EVP_AEAD_CTX_new(aead, keybuf.buf, keyBuf.len(), GCM_SIV_TAG_LENGTH);
if (unlikely(!ctx)) {
throw java_ex::from_openssl(EX_RUNTIME_CRYPTO, "Failed to create AES-GCM-SIV AEAD context");
}
return ctx;
}

// Creates a new EVP_AEAD_CTX for the given key and returns its pointer.
// The caller is responsible for freeing it via releaseAeadCtx.
JNIEXPORT jlong JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSivSpi_nCreateContext(
JNIEnv* pEnv, jclass, jbyteArray keyArray)
{
try {
raii_env env(pEnv);
java_buffer keyBuf = java_buffer::from_array(env, keyArray);
EVP_AEAD_CTX* ctx = createAeadCtx(env, keyBuf);
return reinterpret_cast<jlong>(ctx);
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
return 0;
}
}

// Encrypts plaintext with AES-GCM-SIV. Ciphertext = plaintext || tag (16 bytes).
// Returns: number of bytes written to outputArray, or -1 on error.
JNIEXPORT jint JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSivSpi_nSeal(JNIEnv* pEnv,
jclass,
jlong ctxPtr,
jboolean sameKey,
jlongArray ctxOut,
jbyteArray keyArray,
jbyteArray nonceArray,
jbyteArray inputArray,
jint inputOffset,
jint inputLen,
jbyteArray outputArray,
jint outputOffset,
jbyteArray aadArray,
jint aadLen)
{
try {
raii_env env(pEnv);

// Create all java_buffers before any borrows
java_buffer nonceBuf = java_buffer::from_array(env, nonceArray);
java_buffer inputBuf = java_buffer::from_array(env, inputArray, inputOffset, inputLen);
java_buffer outputBuf = java_buffer::from_array(env, outputArray, outputOffset);
java_buffer aadBuf;
if (aadLen > 0) {
aadBuf = java_buffer::from_array(env, aadArray, 0, aadLen);
}

EVP_AEAD_CTX* ctx;
bool ownsCtx = false;

if (ctxPtr != 0 && sameKey == JNI_TRUE) {
ctx = reinterpret_cast<EVP_AEAD_CTX*>(ctxPtr);
} else {
java_buffer keyBuf = java_buffer::from_array(env, keyArray);
ctx = createAeadCtx(env, keyBuf);
ownsCtx = true;
}

size_t out_len = 0;
bool success;

{
jni_borrow nonce(env, nonceBuf, "nonce");
jni_borrow input(env, inputBuf, "input");
jni_borrow output(env, outputBuf, "output");

if (aadLen > 0) {
jni_borrow aad(env, aadBuf, "aad");
success = EVP_AEAD_CTX_seal(ctx,
output.data(),
&out_len,
outputBuf.len(),
nonce.data(),
GCM_SIV_NONCE_LENGTH,
input.data(),
inputLen,
aad.data(),
aadLen);
} else {
success = EVP_AEAD_CTX_seal(ctx,
output.data(),
&out_len,
outputBuf.len(),
nonce.data(),
GCM_SIV_NONCE_LENGTH,
input.data(),
inputLen,
nullptr,
0);
}
}

if (unlikely(!success)) {
if (ownsCtx) {
EVP_AEAD_CTX_free(ctx);
}
throw java_ex::from_openssl(EX_RUNTIME_CRYPTO, "AES-GCM-SIV seal failed");
}

if (ownsCtx) {
if (ctxOut != nullptr) {
jlong tmpPtr = reinterpret_cast<jlong>(ctx);
pEnv->SetLongArrayRegion(ctxOut, 0, 1, &tmpPtr);
} else {
EVP_AEAD_CTX_free(ctx);
}
}

return (jint)out_len;
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
return -1;
}
}

// Decrypts and authenticates AES-GCM-SIV ciphertext (ciphertext || tag).
// Returns: number of plaintext bytes written to outputArray, or -1 on error.
// Throws AEADBadTagException on authentication failure.
JNIEXPORT jint JNICALL Java_com_amazon_corretto_crypto_provider_AesGcmSivSpi_nOpen(JNIEnv* pEnv,
jclass,
jlong ctxPtr,
jboolean sameKey,
jlongArray ctxOut,
jbyteArray keyArray,
jbyteArray nonceArray,
jbyteArray inputArray,
jint inputOffset,
jint inputLen,
jbyteArray outputArray,
jint outputOffset,
jbyteArray aadArray,
jint aadLen)
{
try {
raii_env env(pEnv);

// Create all java_buffers before any borrows
java_buffer nonceBuf = java_buffer::from_array(env, nonceArray);
java_buffer inputBuf = java_buffer::from_array(env, inputArray, inputOffset, inputLen);
java_buffer outputBuf = java_buffer::from_array(env, outputArray, outputOffset);
java_buffer aadBuf;
if (aadLen > 0) {
aadBuf = java_buffer::from_array(env, aadArray, 0, aadLen);
}

EVP_AEAD_CTX* ctx;
bool ownsCtx = false;

if (ctxPtr != 0 && sameKey == JNI_TRUE) {
ctx = reinterpret_cast<EVP_AEAD_CTX*>(ctxPtr);
} else {
java_buffer keyBuf = java_buffer::from_array(env, keyArray);
ctx = createAeadCtx(env, keyBuf);
ownsCtx = true;
}

size_t out_len = 0;
int rv;

{
jni_borrow nonce(env, nonceBuf, "nonce");
jni_borrow input(env, inputBuf, "input");
jni_borrow output(env, outputBuf, "output");

if (aadLen > 0) {
jni_borrow aad(env, aadBuf, "aad");
rv = EVP_AEAD_CTX_open(ctx,
output.data(),
&out_len,
outputBuf.len(),
nonce.data(),
GCM_SIV_NONCE_LENGTH,
input.data(),
inputLen,
aad.data(),
aadLen);
} else {
rv = EVP_AEAD_CTX_open(ctx,
output.data(),
&out_len,
outputBuf.len(),
nonce.data(),
GCM_SIV_NONCE_LENGTH,
input.data(),
inputLen,
nullptr,
0);
}
}

if (unlikely(!rv)) {
if (ownsCtx) {
EVP_AEAD_CTX_free(ctx);
}
// EVP_AEAD_CTX_open only fails due to authentication failure given validated inputs.
// Drain any OpenSSL error codes and always signal AEADBadTagException.
drainOpensslErrors();
throw java_ex(EX_BADTAG, "Tag mismatch!");
}

if (ownsCtx) {
if (ctxOut != nullptr) {
jlong tmpPtr = reinterpret_cast<jlong>(ctx);
pEnv->SetLongArrayRegion(ctxOut, 0, 1, &tmpPtr);
} else {
EVP_AEAD_CTX_free(ctx);
}
}

return (jint)out_len;
} catch (java_ex& ex) {
ex.throw_to_java(pEnv);
return -1;
}
}
7 changes: 7 additions & 0 deletions csrc/util.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
#include "generated-headers.h"
#include <openssl/aead.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <cassert>
Expand Down Expand Up @@ -48,6 +49,12 @@ extern "C" JNIEXPORT void JNICALL Java_com_amazon_corretto_crypto_provider_Utils
EVP_CIPHER_CTX_free(reinterpret_cast<EVP_CIPHER_CTX*>(ctxPtr));
}

extern "C" JNIEXPORT void JNICALL Java_com_amazon_corretto_crypto_provider_Utils_releaseEvpAeadCtx(
JNIEnv*, jclass, jlong ctxPtr)
{
EVP_AEAD_CTX_free(reinterpret_cast<EVP_AEAD_CTX*>(ctxPtr));
}

EVP_MD const* digest_code_to_EVP_MD(int digestCode)
{
switch (digestCode) {
Expand Down
Loading
Loading