Skip to content

Commit

Permalink
8253821: Improve ByteBuffer performance with GCM
Browse files Browse the repository at this point in the history
Reviewed-by: xuelei, valeriep
  • Loading branch information
Anthony Scarpino committed Dec 2, 2020
1 parent 3da30e9 commit cc1915b
Show file tree
Hide file tree
Showing 15 changed files with 2,137 additions and 160 deletions.
41 changes: 36 additions & 5 deletions src/java.base/share/classes/com/sun/crypto/provider/AESCipher.java
Expand Up @@ -25,12 +25,21 @@

package com.sun.crypto.provider;

import java.security.*;
import java.security.spec.*;
import javax.crypto.*;
import javax.crypto.spec.*;
import javax.crypto.BadPaddingException;
import javax.crypto.CipherSpi;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.NoSuchAlgorithmException;
import java.security.ProviderException;
import java.security.SecureRandom;
import java.security.spec.AlgorithmParameterSpec;

/**
* This class implements the AES algorithm in its various modes
Expand Down Expand Up @@ -411,6 +420,7 @@ protected int engineUpdate(byte[] input, int inputOffset, int inputLen,
outputOffset);
}


/**
* Encrypts or decrypts data in a single-part operation,
* or finishes a multiple-part operation.
Expand Down Expand Up @@ -641,5 +651,26 @@ protected void engineUpdateAAD(ByteBuffer src) {
}
}
}
}

/**
* Finalize crypto operation with ByteBuffers
*
* @param input the input ByteBuffer
* @param output the output ByteBuffer
*
* @return output length
* @throws ShortBufferException
* @throws IllegalBlockSizeException
* @throws BadPaddingException
*/
@Override
protected int engineDoFinal(ByteBuffer input, ByteBuffer output)
throws ShortBufferException, IllegalBlockSizeException,
BadPaddingException {
if (core.getMode() == CipherCore.GCM_MODE && !input.hasArray()) {
return core.gcmDoFinal(input, output);
} else {
return super.engineDoFinal(input, output);
}
}
}
59 changes: 43 additions & 16 deletions src/java.base/share/classes/com/sun/crypto/provider/CipherCore.java
Expand Up @@ -25,6 +25,7 @@

package com.sun.crypto.provider;

import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Locale;

Expand Down Expand Up @@ -722,8 +723,7 @@ int update(byte[] input, int inputOffset, int inputLen, byte[] output,
len = (len > 0 ? (len - (len % unitBytes)) : 0);

// check output buffer capacity
if ((output == null) ||
((output.length - outputOffset) < len)) {
if (output == null || (output.length - outputOffset) < len) {
throw new ShortBufferException("Output buffer must be "
+ "(at least) " + len
+ " bytes long");
Expand Down Expand Up @@ -917,10 +917,10 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
int estOutSize = getOutputSizeByOperation(inputLen, true);
int outputCapacity = checkOutputCapacity(output, outputOffset,
estOutSize);
int offset = decrypting ? 0 : outputOffset; // 0 for decrypting
int offset = outputOffset;
byte[] finalBuf = prepareInputBuffer(input, inputOffset,
inputLen, output, outputOffset);
byte[] outWithPadding = null; // for decrypting only
byte[] internalOutput = null; // for decrypting only

int finalOffset = (finalBuf == input) ? inputOffset : 0;
int finalBufLen = (finalBuf == input) ? inputLen : finalBuf.length;
Expand All @@ -934,11 +934,14 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
if (outputCapacity < estOutSize) {
cipher.save();
}
// create temporary output buffer so that only "real"
// data bytes are passed to user's output buffer.
outWithPadding = new byte[estOutSize];
if (getMode() != GCM_MODE || outputCapacity < estOutSize) {
// create temporary output buffer if the estimated size is larger
// than the user-provided buffer.
internalOutput = new byte[estOutSize];
offset = 0;
}
}
byte[] outBuffer = decrypting ? outWithPadding : output;
byte[] outBuffer = (internalOutput != null) ? internalOutput : output;

int outLen = fillOutputBuffer(finalBuf, finalOffset, outBuffer,
offset, finalBufLen, input);
Expand All @@ -954,9 +957,11 @@ int doFinal(byte[] input, int inputOffset, int inputLen, byte[] output,
+ " bytes needed");
}
// copy the result into user-supplied output buffer
System.arraycopy(outWithPadding, 0, output, outputOffset, outLen);
// decrypt mode. Zero out output data that's not required
Arrays.fill(outWithPadding, (byte) 0x00);
if (internalOutput != null) {
System.arraycopy(internalOutput, 0, output, outputOffset, outLen);
// decrypt mode. Zero out output data that's not required
Arrays.fill(internalOutput, (byte) 0x00);
}
}
endDoFinal();
return outLen;
Expand All @@ -970,16 +975,15 @@ private void endDoFinal() {
}
}

private int unpad(int outLen, byte[] outWithPadding)
private int unpad(int outLen, int off, byte[] outWithPadding)
throws BadPaddingException {
int padStart = padding.unpad(outWithPadding, 0, outLen);
int padStart = padding.unpad(outWithPadding, off, outLen);
if (padStart < 0) {
throw new BadPaddingException("Given final block not " +
"properly padded. Such issues can arise if a bad key " +
"is used during decryption.");
}
outLen = padStart;
return outLen;
return padStart - off;
}

private byte[] prepareInputBuffer(byte[] input, int inputOffset,
Expand Down Expand Up @@ -1055,7 +1059,7 @@ private int fillOutputBuffer(byte[] finalBuf, int finalOffset,
len = finalNoPadding(finalBuf, finalOffset, output,
outOfs, finalBufLen);
if (decrypting && padding != null) {
len = unpad(len, output);
len = unpad(len, outOfs, output);
}
return len;
} finally {
Expand Down Expand Up @@ -1225,4 +1229,27 @@ void updateAAD(byte[] src, int offset, int len) {
checkReinit();
cipher.updateAAD(src, offset, len);
}

// This must only be used with GCM.
// If some data has been buffered from an update call, operate on the buffer
// then run doFinal.
int gcmDoFinal(ByteBuffer src, ByteBuffer dst) throws ShortBufferException,
IllegalBlockSizeException, BadPaddingException {
int estOutSize = getOutputSizeByOperation(src.remaining(), true);
if (estOutSize > dst.remaining()) {
throw new ShortBufferException("output buffer too small");
}

if (decrypting) {
if (buffered > 0) {
cipher.decrypt(buffer, 0, buffered, new byte[0], 0);
}
return cipher.decryptFinal(src, dst);
} else {
if (buffered > 0) {
((GaloisCounterMode)cipher).encrypt(buffer, 0, buffered);
}
return cipher.encryptFinal(src, dst);
}
}
}
Expand Up @@ -25,6 +25,7 @@

package com.sun.crypto.provider;

import java.nio.ByteBuffer;
import java.security.InvalidKeyException;
import java.security.InvalidAlgorithmParameterException;
import javax.crypto.*;
Expand Down Expand Up @@ -242,4 +243,27 @@ int getBufferedLength() {
// internally during decryption mode
return 0;
}

/*
* ByteBuffer methods should not be accessed as CipherCore and AESCipher
* copy the data to byte arrays. These methods are to satisfy the compiler.
*/
int encrypt(ByteBuffer src, ByteBuffer dst) {
throw new UnsupportedOperationException("ByteBuffer not supported");
};

int decrypt(ByteBuffer src, ByteBuffer dst) {
throw new UnsupportedOperationException("ByteBuffer not supported");
};

int encryptFinal(ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException, ShortBufferException {
throw new UnsupportedOperationException("ByteBuffer not supported");
};

int decryptFinal(ByteBuffer src, ByteBuffer dst)
throws IllegalBlockSizeException, AEADBadTagException,
ShortBufferException {
throw new UnsupportedOperationException("ByteBuffer not supported");
}
}
114 changes: 109 additions & 5 deletions src/java.base/share/classes/com/sun/crypto/provider/GCTR.java
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
Expand Down Expand Up @@ -54,11 +54,15 @@
*/
final class GCTR extends CounterMode {

// Maximum buffer size rotating ByteBuffer->byte[] intrinsic copy
private static final int MAX_LEN = 1024;

GCTR(SymmetricCipher cipher, byte[] initialCounterBlk) {
super(cipher);
if (initialCounterBlk.length != AES_BLOCK_SIZE) {
throw new RuntimeException("length of initial counter block (" + initialCounterBlk.length +
") not equal to AES_BLOCK_SIZE (" + AES_BLOCK_SIZE + ")");
throw new RuntimeException("length of initial counter block (" +
initialCounterBlk.length + ") not equal to AES_BLOCK_SIZE (" +
AES_BLOCK_SIZE + ")");
}

iv = initialCounterBlk;
Expand Down Expand Up @@ -112,9 +116,89 @@ int update(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) {
}
}

// input must be multiples of AES blocks, 128-bit, when calling update
int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) {
if (inLen - inOfs > in.length) {
throw new RuntimeException("input length out of bound");
}
if (inLen < 0 || inLen % AES_BLOCK_SIZE != 0) {
throw new RuntimeException("input length unsupported");
}
// See GaloisCounterMode. decryptFinal(bytebuffer, bytebuffer) for
// details on the check for 'dst' having enough space for the result.

long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = inLen / AES_BLOCK_SIZE;
if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) {
int index = (i * AES_BLOCK_SIZE + n);
dst.put((byte) ((in[inOfs + index] ^ encryptedCntr[n])));
}
GaloisCounterMode.increment32(counter);
}
return inLen;
} else {
int len = inLen - inLen % AES_BLOCK_SIZE;
int processed = len;
byte[] out = new byte[Math.min(MAX_LEN, len)];
int offset = inOfs;
while (processed > MAX_LEN) {
encrypt(in, offset, MAX_LEN, out, 0);
dst.put(out, 0, MAX_LEN);
processed -= MAX_LEN;
offset += MAX_LEN;
}
encrypt(in, offset, processed, out, 0);
// If dst is less than blocksize, insert only what it can. Extra
// bytes would cause buffers with enough size to fail with a
// short buffer
dst.put(out, 0, Math.min(dst.remaining(), processed));
return len;
}
}

// input operates on multiples of AES blocks, 128-bit, when calling update.
// The remainder is left in the src buffer.
int update(ByteBuffer src, ByteBuffer dst) {
long blocksLeft = blocksUntilRollover();
int numOfCompleteBlocks = src.remaining() / AES_BLOCK_SIZE;
if (numOfCompleteBlocks >= blocksLeft) {
// Counter Mode encryption cannot be used because counter will
// roll over incorrectly. Use GCM-specific code instead.
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
for (int i = 0; i < numOfCompleteBlocks; i++) {
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
for (int n = 0; n < AES_BLOCK_SIZE; n++) {
dst.put((byte) (src.get() ^ encryptedCntr[n]));
}
GaloisCounterMode.increment32(counter);
}
return numOfCompleteBlocks * AES_BLOCK_SIZE;
}

int len = src.remaining() - (src.remaining() % AES_BLOCK_SIZE);
int processed = len;
byte[] in = new byte[Math.min(MAX_LEN, len)];
while (processed > MAX_LEN) {
src.get(in, 0, MAX_LEN);
encrypt(in, 0, MAX_LEN, in, 0);
dst.put(in, 0, MAX_LEN);
processed -= MAX_LEN;
}
src.get(in, 0, processed);
encrypt(in, 0, processed, in, 0);
dst.put(in, 0, processed);
return len;
}

// input can be arbitrary size when calling doFinal
int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
int outOfs) throws IllegalBlockSizeException {
int outOfs) throws IllegalBlockSizeException {
try {
if (inLen < 0) {
throw new IllegalBlockSizeException("Negative input size!");
Expand All @@ -130,7 +214,7 @@ int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
for (int n = 0; n < lastBlockSize; n++) {
out[outOfs + completeBlkLen + n] =
(byte) ((in[inOfs + completeBlkLen + n] ^
encryptedCntr[n]));
encryptedCntr[n]));
}
}
}
Expand All @@ -139,4 +223,24 @@ int doFinal(byte[] in, int inOfs, int inLen, byte[] out,
}
return inLen;
}

// src can be arbitrary size when calling doFinal
int doFinal(ByteBuffer src, ByteBuffer dst) {
int len = src.remaining();
int lastBlockSize = len % AES_BLOCK_SIZE;
try {
update(src, dst);
if (lastBlockSize != 0) {
// do the last partial block
byte[] encryptedCntr = new byte[AES_BLOCK_SIZE];
embeddedCipher.encryptBlock(counter, 0, encryptedCntr, 0);
for (int n = 0; n < lastBlockSize; n++) {
dst.put((byte) (src.get() ^ encryptedCntr[n]));
}
}
} finally {
reset();
}
return len;
}
}

1 comment on commit cc1915b

@openjdk-notifier
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.