Skip to content

Commit

Permalink
Optimize AES/GCM cipher and IV init and improve array cleanup code
Browse files Browse the repository at this point in the history
The cipher initialization cost has been found to be computationally
expensive, especially in the OpenSSL 3.x API.

In order to optimize the cost of using OpenSSL to perform consecutive
AES/GCM encryption and decryption operations, two flags are set and
passed as parameters.

The first flag indicates whether a different cipher is required for the
upcoming operations (i.e., the key size has changed). If that is the
case, a cipher is initialized and set to the provided context. If not,
those steps are omitted, thus reducing the time required for the
operation.

The second flag indicates whether the IV length has changed and is used
in a similar way as the first flag.

The rest of the steps required for the operation are performed
regardless of the flags.

The handling of freeing arrays that were part of that functionality is,
also, updated to comply with the newer approach to cleanup code.

Signed-off by: Kostas Tsiounis <kostas.tsiounis@ibm.com>
  • Loading branch information
KostasTsiounis committed Sep 12, 2023
1 parent 27c0f0f commit 20affef
Show file tree
Hide file tree
Showing 3 changed files with 265 additions and 304 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,26 @@
*/
/*
* ===========================================================================
* (c) Copyright IBM Corp. 2018, 2021 All Rights Reserved
* (c) Copyright IBM Corp. 2018, 2023 All Rights Reserved
* ===========================================================================
*/

package com.sun.crypto.provider;

import java.util.Arrays;
import java.io.*;
import java.security.*;
import javax.crypto.*;
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;
import com.sun.crypto.provider.AESCrypt;
import static com.sun.crypto.provider.AESConstants.AES_BLOCK_SIZE;

import java.io.ByteArrayOutputStream;
import java.lang.ref.Cleaner;
import java.security.InvalidKeyException;
import java.security.ProviderException;

import javax.crypto.AEADBadTagException;
import javax.crypto.ShortBufferException;
import javax.crypto.IllegalBlockSizeException;

import jdk.crypto.jniprovider.NativeCrypto;
import jdk.internal.ref.CleanerFactory;

/**
* This class represents ciphers in GaloisCounter (GCM) mode.
Expand All @@ -54,9 +60,11 @@
*/
final class NativeGaloisCounterMode extends FeedbackCipher {

private static final byte[] EMPTY_BUF = new byte[0];

private byte[] key;
private boolean decrypting;
private static final byte[] emptyAAD = new byte[0];
private final long context;

static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE;
static int DEFAULT_IV_LEN = 12; // in bytes
Expand Down Expand Up @@ -92,10 +100,34 @@ final class NativeGaloisCounterMode extends FeedbackCipher {
private byte[] ibufferSave = null;
private byte[] ibufferSave_enc = null;

private static NativeCrypto nativeCrypto;
private byte[] lastKey = EMPTY_BUF;
private byte[] lastIv = EMPTY_BUF;

private boolean newIVLen;
private boolean newKeyLen;

private static final NativeCrypto nativeCrypto = NativeCrypto.getNativeCrypto();
private static final Cleaner contextCleaner = CleanerFactory.cleaner();

private static final class GCMCleanerRunnable implements Runnable {
private final long nativeContext;

public GCMCleanerRunnable(long nativeContext) {
this.nativeContext = nativeContext;
}

static {
nativeCrypto = NativeCrypto.getNativeCrypto();
@Override
public void run() {
/*
* Release the GCM context.
*/
synchronized (NativeGaloisCounterMode.class) {
long ret = nativeCrypto.DestroyContext(nativeContext);
if (ret == -1) {
throw new ProviderException("Error in destroying context in NativeGaloisCounterMode.");
}
}
}
}

private static void checkDataLength(int processed, int len) {
Expand All @@ -111,6 +143,12 @@ private static void checkDataLength(int processed, int len) {
NativeGaloisCounterMode(SymmetricCipher embeddedCipher) {
super(embeddedCipher);
aadBuffer = new ByteArrayOutputStream();

context = nativeCrypto.CreateContext();
if (context == -1) {
throw new ProviderException("Error in creating context for NativeGaloisCounterMode.");
}
contextCleaner.register(this, new GCMCleanerRunnable(context));
}

/**
Expand Down Expand Up @@ -244,6 +282,22 @@ void init(boolean decrypting, String algorithm, byte[] keyValue,
} else {
ibuffer_enc = new ByteArrayOutputStream();
}

/*
* Check whether cipher and IV need to be set,
* whether because something changed here or
* a call to set them in context hasn't been
* made yet.
*/
if (lastIv.length != this.iv.length) {
newIVLen = true;
}
if (lastKey.length != this.key.length) {
newKeyLen = true;
}

lastKey = keyValue;
lastIv = iv;
}
}

Expand Down Expand Up @@ -354,18 +408,26 @@ int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs)
len = in.length;
ibuffer_enc.reset();

byte[] aad = (((aadBuffer == null) || (aadBuffer.size() == 0)) ? emptyAAD : aadBuffer.toByteArray());
byte[] aad = (((aadBuffer == null) || (aadBuffer.size() == 0)) ? EMPTY_BUF : aadBuffer.toByteArray());

ret = nativeCrypto.GCMEncrypt(key, key.length,
ret = nativeCrypto.GCMEncrypt(context,
key, key.length,
iv, iv.length,
in, inOfs, len,
out, outOfs,
aad, aad.length, localTagLenBytes);
aad, aad.length,
localTagLenBytes,
newIVLen,
newKeyLen);
}
if (ret == -1) {
throw new ProviderException("Error in Native GaloisCounterMode");
}

/* Cipher and IV length were set, since call to GCMEncrypt succeeded. */
newKeyLen = false;
newIVLen = false;

return (len + localTagLenBytes);
}

Expand Down Expand Up @@ -442,7 +504,7 @@ int decryptFinal(byte[] in, int inOfs, int len,
}

byte[] aad = (((aadBuffer == null) || (aadBuffer.size() == 0)) ?
emptyAAD : aadBuffer.toByteArray());
EMPTY_BUF : aadBuffer.toByteArray());

aadBuffer = null;

Expand All @@ -456,18 +518,26 @@ int decryptFinal(byte[] in, int inOfs, int len,
len = in.length;
ibuffer.reset();

ret = nativeCrypto.GCMDecrypt(key, key.length,
ret = nativeCrypto.GCMDecrypt(context,
key, key.length,
iv, iv.length,
in, inOfs, len,
out, outOfs,
aad, aad.length, localTagLenBytes);
aad, aad.length,
localTagLenBytes,
newIVLen,
newKeyLen);
}
if (ret == -2) {
throw new AEADBadTagException("Tag mismatch!");
} else if (ret == -1) {
throw new ProviderException("Error in Native GaloisCounterMode");
}

/* Cipher and IV length were set, since call to GCMDecrypt succeeded. */
newKeyLen = false;
newIVLen = false;

return ret;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ public final native int DigestComputeAndReset(long context,

public final native int DigestReset(long context);

/* Native interfaces shared by CBC and ChaCha20 */
/* Native interfaces shared by CBC, ChaCha20 and GCM. */

public final native long CreateContext();

Expand Down Expand Up @@ -252,7 +252,8 @@ public final native int CBCFinalEncrypt(long context,

/* Native GCM interfaces */

public final native int GCMEncrypt(byte[] key,
public final native int GCMEncrypt(long context,
byte[] key,
int keylen,
byte[] iv,
int ivlen,
Expand All @@ -263,9 +264,12 @@ public final native int GCMEncrypt(byte[] key,
int outOffset,
byte[] aad,
int aadLen,
int tagLen);
int tagLen,
boolean newIVLen,
boolean newKeyLen);

public final native int GCMDecrypt(byte[] key,
public final native int GCMDecrypt(long context,
byte[] key,
int keylen,
byte[] iv,
int ivlen,
Expand All @@ -276,7 +280,9 @@ public final native int GCMDecrypt(byte[] key,
int outOffset,
byte[] aad,
int aadLen,
int tagLen);
int tagLen,
boolean newIVLen,
boolean newKeyLen);

/* Native RSA interfaces */

Expand Down
Loading

0 comments on commit 20affef

Please sign in to comment.