|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
package com.sun.crypto.provider; |
|
|
|
import sun.nio.ch.DirectBuffer; |
|
import sun.security.jca.JCAUtil; |
|
import sun.security.util.ArrayUtil; |
|
|
|
import javax.crypto.AEADBadTagException; |
|
import javax.crypto.BadPaddingException; |
|
import javax.crypto.Cipher; |
|
import javax.crypto.CipherSpi; |
|
import javax.crypto.IllegalBlockSizeException; |
|
import javax.crypto.NoSuchPaddingException; |
|
import javax.crypto.ShortBufferException; |
|
import javax.crypto.spec.GCMParameterSpec; |
|
import java.io.ByteArrayOutputStream; |
|
import java.io.IOException; |
|
import java.lang.invoke.MethodHandles; |
|
import java.lang.invoke.VarHandle; |
|
import java.nio.ByteBuffer; |
|
import java.nio.ByteOrder; |
|
import java.security.AlgorithmParameters; |
|
import java.security.InvalidAlgorithmParameterException; |
|
import java.security.InvalidKeyException; |
|
import java.security.Key; |
|
import java.security.MessageDigest; |
|
import java.security.NoSuchAlgorithmException; |
|
import java.security.ProviderException; |
|
import java.security.SecureRandom; |
|
import java.security.spec.AlgorithmParameterSpec; |
|
import java.security.spec.InvalidParameterSpecException; |
|
import java.util.Arrays; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract class GaloisCounterMode extends CipherSpi { |
|
static int DEFAULT_IV_LEN = 12; |
|
static int DEFAULT_TAG_LEN = 16; |
|
// In NIST SP 800-38D, GCM input size is limited to be no longer |
|
// than (2^36 - 32) bytes. Otherwise, the counter will wrap |
|
// around and lead to a leak of plaintext. |
|
// However, given the current GCM spec requirement that recovered |
|
// text can only be returned after successful tag verification, |
|
// we are bound by limiting the data size to the size limit of |
|
// java byte array, e.g. Integer.MAX_VALUE, since all data |
|
|
|
private static final int MAX_BUF_SIZE = Integer.MAX_VALUE; |
|
// data size when buffer is divided up to aid in intrinsics |
|
private static final int TRIGGERLEN = 65536; |
|
|
|
static final byte[] EMPTY_BUF = new byte[0]; |
|
|
|
private boolean initialized = false; |
|
|
|
SymmetricCipher blockCipher; |
|
|
|
private GCMEngine engine; |
|
private boolean encryption = true; |
|
|
|
|
|
int tagLenBytes = DEFAULT_TAG_LEN; |
|
|
|
int keySize; |
|
|
|
boolean reInit = false; |
|
byte[] lastKey = EMPTY_BUF; |
|
byte[] lastIv = EMPTY_BUF; |
|
byte[] iv = null; |
|
SecureRandom random = null; |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
GaloisCounterMode(int keySize, SymmetricCipher embeddedCipher) { |
|
blockCipher = embeddedCipher; |
|
this.keySize = keySize; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
void init(int opmode, Key key, GCMParameterSpec spec) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
encryption = (opmode == Cipher.ENCRYPT_MODE) || |
|
(opmode == Cipher.WRAP_MODE); |
|
|
|
int tagLen = spec.getTLen(); |
|
if (tagLen < 96 || tagLen > 128 || ((tagLen & 0x07) != 0)) { |
|
throw new InvalidAlgorithmParameterException |
|
("Unsupported TLen value. Must be one of " + |
|
"{128, 120, 112, 104, 96}"); |
|
} |
|
tagLenBytes = tagLen >> 3; |
|
|
|
|
|
if (key == null) { |
|
throw new InvalidKeyException("The key must not be null"); |
|
} |
|
byte[] keyValue = key.getEncoded(); |
|
if (keyValue == null) { |
|
throw new InvalidKeyException("Key encoding must not be null"); |
|
} else if (keySize != -1 && keyValue.length != keySize) { |
|
Arrays.fill(keyValue, (byte) 0); |
|
throw new InvalidKeyException("The key must be " + |
|
keySize + " bytes"); |
|
} |
|
|
|
|
|
if (encryption) { |
|
if (MessageDigest.isEqual(keyValue, lastKey) && |
|
MessageDigest.isEqual(iv, lastIv)) { |
|
Arrays.fill(keyValue, (byte) 0); |
|
throw new InvalidAlgorithmParameterException( |
|
"Cannot reuse iv for GCM encryption"); |
|
} |
|
|
|
|
|
if (lastKey != null) { |
|
Arrays.fill(lastKey, (byte) 0); |
|
} |
|
lastKey = keyValue; |
|
lastIv = iv; |
|
} |
|
|
|
reInit = false; |
|
|
|
|
|
try { |
|
blockCipher.init(false, key.getAlgorithm(), keyValue); |
|
} finally { |
|
if (!encryption) { |
|
Arrays.fill(keyValue, (byte) 0); |
|
} |
|
} |
|
} |
|
|
|
@Override |
|
protected void engineSetMode(String mode) throws NoSuchAlgorithmException { |
|
if (!mode.equalsIgnoreCase("GCM")) { |
|
throw new NoSuchAlgorithmException("Mode must be GCM"); |
|
} |
|
} |
|
|
|
@Override |
|
protected void engineSetPadding(String padding) |
|
throws NoSuchPaddingException { |
|
if (!padding.equalsIgnoreCase("NoPadding")) { |
|
throw new NoSuchPaddingException("Padding must be NoPadding"); |
|
} |
|
} |
|
|
|
@Override |
|
protected int engineGetBlockSize() { |
|
return blockCipher.getBlockSize(); |
|
} |
|
|
|
@Override |
|
protected int engineGetOutputSize(int inputLen) { |
|
checkInit(); |
|
return engine.getOutputSize(inputLen, true); |
|
} |
|
|
|
@Override |
|
protected int engineGetKeySize(Key key) throws InvalidKeyException { |
|
byte[] encoded = key.getEncoded(); |
|
Arrays.fill(encoded, (byte)0); |
|
if (!AESCrypt.isKeySizeValid(encoded.length)) { |
|
throw new InvalidKeyException("Invalid key length: " + |
|
encoded.length + " bytes"); |
|
} |
|
return Math.multiplyExact(encoded.length, 8); |
|
} |
|
|
|
@Override |
|
protected byte[] engineGetIV() { |
|
if (iv == null) { |
|
return null; |
|
} |
|
return iv.clone(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private static byte[] createIv(SecureRandom rand) { |
|
byte[] iv = new byte[DEFAULT_IV_LEN]; |
|
if (rand == null) { |
|
rand = JCAUtil.getDefSecureRandom(); |
|
} |
|
rand.nextBytes(iv); |
|
return iv; |
|
} |
|
|
|
@Override |
|
protected AlgorithmParameters engineGetParameters() { |
|
GCMParameterSpec spec; |
|
spec = new GCMParameterSpec(tagLenBytes * 8, |
|
iv == null ? createIv(random) : iv.clone()); |
|
try { |
|
AlgorithmParameters params = |
|
AlgorithmParameters.getInstance("GCM", |
|
SunJCE.getInstance()); |
|
params.init(spec); |
|
return params; |
|
} catch (NoSuchAlgorithmException | InvalidParameterSpecException e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
@Override |
|
protected void engineInit(int opmode, Key key, SecureRandom random) |
|
throws InvalidKeyException { |
|
|
|
engine = null; |
|
if (opmode == Cipher.DECRYPT_MODE || opmode == Cipher.UNWRAP_MODE) { |
|
throw new InvalidKeyException("No GCMParameterSpec specified"); |
|
} |
|
try { |
|
engineInit(opmode, key, (AlgorithmParameterSpec) null, random); |
|
} catch (InvalidAlgorithmParameterException e) { |
|
// never happen |
|
} |
|
} |
|
|
|
@Override |
|
protected void engineInit(int opmode, Key key, |
|
AlgorithmParameterSpec params, SecureRandom random) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
|
|
GCMParameterSpec spec; |
|
this.random = random; |
|
engine = null; |
|
if (params == null) { |
|
iv = createIv(random); |
|
spec = new GCMParameterSpec(DEFAULT_TAG_LEN * 8, iv); |
|
} else { |
|
if (!(params instanceof GCMParameterSpec)) { |
|
throw new InvalidAlgorithmParameterException( |
|
"AlgorithmParameterSpec not of GCMParameterSpec"); |
|
} |
|
spec = (GCMParameterSpec)params; |
|
iv = spec.getIV(); |
|
if (iv == null) { |
|
throw new InvalidAlgorithmParameterException("IV is null"); |
|
} |
|
if (iv.length == 0) { |
|
throw new InvalidAlgorithmParameterException("IV is empty"); |
|
} |
|
} |
|
init(opmode, key, spec); |
|
initialized = true; |
|
} |
|
|
|
@Override |
|
protected void engineInit(int opmode, Key key, AlgorithmParameters params, |
|
SecureRandom random) throws InvalidKeyException, |
|
InvalidAlgorithmParameterException { |
|
GCMParameterSpec spec = null; |
|
engine = null; |
|
if (params != null) { |
|
try { |
|
spec = params.getParameterSpec(GCMParameterSpec.class); |
|
} catch (InvalidParameterSpecException e) { |
|
throw new InvalidAlgorithmParameterException(e); |
|
} |
|
} |
|
engineInit(opmode, key, spec, random); |
|
} |
|
|
|
void checkInit() { |
|
if (!initialized) { |
|
throw new IllegalStateException("Operation not initialized."); |
|
} |
|
|
|
if (engine == null) { |
|
if (encryption) { |
|
engine = new GCMEncrypt(blockCipher); |
|
} else { |
|
engine = new GCMDecrypt(blockCipher); |
|
} |
|
} |
|
} |
|
|
|
void checkReInit() { |
|
if (reInit) { |
|
throw new IllegalStateException( |
|
"Must use either different key or " + " iv for GCM encryption"); |
|
} |
|
} |
|
|
|
@Override |
|
protected byte[] engineUpdate(byte[] input, int inputOffset, int inputLen) { |
|
checkInit(); |
|
ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); |
|
return engine.doUpdate(input, inputOffset, inputLen); |
|
} |
|
|
|
@Override |
|
protected int engineUpdate(byte[] input, int inputOffset, int inputLen, |
|
byte[] output, int outputOffset) throws ShortBufferException { |
|
checkInit(); |
|
ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); |
|
ArrayUtil.nullAndBoundsCheck(output, outputOffset, |
|
output.length - outputOffset); |
|
int len = engine.getOutputSize(inputLen, false); |
|
if (len > output.length - outputOffset) { |
|
throw new ShortBufferException("Output buffer too small, must be " + |
|
"at least " + len + " bytes long"); |
|
} |
|
return engine.doUpdate(input, inputOffset, inputLen, output, |
|
outputOffset); |
|
} |
|
|
|
@Override |
|
protected int engineUpdate(ByteBuffer src, ByteBuffer dst) |
|
throws ShortBufferException { |
|
checkInit(); |
|
int len = engine.getOutputSize(src.remaining(), false); |
|
if (len > dst.remaining()) { |
|
throw new ShortBufferException( |
|
"Output buffer must be at least " + len + " bytes long"); |
|
} |
|
return engine.doUpdate(src, dst); |
|
} |
|
|
|
@Override |
|
protected void engineUpdateAAD(byte[] src, int offset, int len) { |
|
checkInit(); |
|
engine.updateAAD(src, offset, len); |
|
} |
|
|
|
@Override |
|
protected void engineUpdateAAD(ByteBuffer src) { |
|
checkInit(); |
|
if (src.hasArray()) { |
|
int pos = src.position(); |
|
int len = src.remaining(); |
|
engine.updateAAD(src.array(), src.arrayOffset() + pos, len); |
|
src.position(pos + len); |
|
} else { |
|
byte[] aad = new byte[src.remaining()]; |
|
src.get(aad); |
|
engine.updateAAD(aad, 0, aad.length); |
|
} |
|
} |
|
|
|
@Override |
|
protected byte[] engineDoFinal(byte[] input, int inputOffset, |
|
int inputLen) throws IllegalBlockSizeException, BadPaddingException { |
|
if (input == null) { |
|
input = EMPTY_BUF; |
|
} |
|
try { |
|
ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); |
|
} catch (ArrayIndexOutOfBoundsException e) { |
|
throw new IllegalBlockSizeException("input array invalid"); |
|
} |
|
|
|
checkInit(); |
|
byte[] output = new byte[engine.getOutputSize(inputLen, true)]; |
|
|
|
try { |
|
engine.doFinal(input, inputOffset, inputLen, output, 0); |
|
} catch (ShortBufferException e) { |
|
throw new ProviderException(e); |
|
} finally { |
|
|
|
engine = null; |
|
} |
|
return output; |
|
} |
|
|
|
@Override |
|
protected int engineDoFinal(byte[] input, int inputOffset, int inputLen, |
|
byte[] output, int outputOffset) throws ShortBufferException, |
|
IllegalBlockSizeException, BadPaddingException { |
|
|
|
if (input == null) { |
|
input = EMPTY_BUF; |
|
} |
|
try { |
|
ArrayUtil.nullAndBoundsCheck(input, inputOffset, inputLen); |
|
} catch (ArrayIndexOutOfBoundsException e) { |
|
|
|
engine = null; |
|
throw new IllegalBlockSizeException("input array invalid"); |
|
} |
|
checkInit(); |
|
int len = engine.doFinal(input, inputOffset, inputLen, output, |
|
outputOffset); |
|
|
|
|
|
engine = null; |
|
|
|
return len; |
|
} |
|
|
|
@Override |
|
protected int engineDoFinal(ByteBuffer src, ByteBuffer dst) |
|
throws ShortBufferException, IllegalBlockSizeException, |
|
BadPaddingException { |
|
checkInit(); |
|
|
|
int len = engine.doFinal(src, dst); |
|
|
|
|
|
engine = null; |
|
|
|
return len; |
|
} |
|
|
|
@Override |
|
protected byte[] engineWrap(Key key) throws IllegalBlockSizeException, |
|
InvalidKeyException { |
|
byte[] encodedKey = null; |
|
|
|
checkInit(); |
|
try { |
|
encodedKey = key.getEncoded(); |
|
if ((encodedKey == null) || (encodedKey.length == 0)) { |
|
throw new InvalidKeyException( |
|
"Cannot get an encoding of the key to be wrapped"); |
|
} |
|
return engineDoFinal(encodedKey, 0, encodedKey.length); |
|
} catch (BadPaddingException e) { |
|
// should never happen |
|
} finally { |
|
|
|
engine = null; |
|
if (encodedKey != null) { |
|
Arrays.fill(encodedKey, (byte) 0); |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
@Override |
|
protected Key engineUnwrap(byte[] wrappedKey, String wrappedKeyAlgorithm, |
|
int wrappedKeyType) throws InvalidKeyException, |
|
NoSuchAlgorithmException { |
|
checkInit(); |
|
|
|
byte[] encodedKey; |
|
try { |
|
encodedKey = engineDoFinal(wrappedKey, 0, |
|
wrappedKey.length); |
|
} catch (BadPaddingException ePadding) { |
|
throw new InvalidKeyException( |
|
"The wrapped key is not padded correctly"); |
|
} catch (IllegalBlockSizeException eBlockSize) { |
|
throw new InvalidKeyException( |
|
"The wrapped key does not have the correct length"); |
|
} |
|
try { |
|
return ConstructKeys.constructKey(encodedKey, wrappedKeyAlgorithm, |
|
wrappedKeyType); |
|
} finally { |
|
Arrays.fill(encodedKey, (byte)0); |
|
} |
|
} |
|
|
|
|
|
static void increment32(byte[] value) { |
|
|
|
int n = value.length - 1; |
|
while ((n >= value.length - 4) && (++value[n] == 0)) { |
|
n--; |
|
} |
|
} |
|
|
|
private static final VarHandle wrapToByteArray = |
|
MethodHandles.byteArrayViewVarHandle(long[].class, |
|
ByteOrder.BIG_ENDIAN); |
|
|
|
private static byte[] getLengthBlock(int ivLenInBytes) { |
|
byte[] out = new byte[16]; |
|
wrapToByteArray.set(out, 8, ((long)ivLenInBytes & 0xFFFFFFFFL) << 3); |
|
return out; |
|
} |
|
|
|
private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) { |
|
byte[] out = new byte[16]; |
|
wrapToByteArray.set(out, 0, ((long)aLenInBytes & 0xFFFFFFFFL) << 3); |
|
wrapToByteArray.set(out, 8, ((long)cLenInBytes & 0xFFFFFFFFL) << 3); |
|
return out; |
|
} |
|
|
|
private static byte[] expandToOneBlock(byte[] in, int inOfs, int len, |
|
int blockSize) { |
|
if (len > blockSize) { |
|
throw new ProviderException("input " + len + " too long"); |
|
} |
|
if (len == blockSize && inOfs == 0) { |
|
return in; |
|
} else { |
|
byte[] paddedIn = new byte[blockSize]; |
|
System.arraycopy(in, inOfs, paddedIn, 0, len); |
|
return paddedIn; |
|
} |
|
} |
|
|
|
private static byte[] getJ0(byte[] iv, byte[] subkeyH, int blockSize) { |
|
byte[] j0; |
|
if (iv.length == 12) { |
|
j0 = expandToOneBlock(iv, 0, iv.length, blockSize); |
|
j0[blockSize - 1] = 1; |
|
} else { |
|
GHASH g = new GHASH(subkeyH); |
|
int lastLen = iv.length % blockSize; |
|
if (lastLen != 0) { |
|
g.update(iv, 0, iv.length - lastLen); |
|
byte[] padded = |
|
expandToOneBlock(iv, iv.length - lastLen, lastLen, |
|
blockSize); |
|
g.update(padded); |
|
} else { |
|
g.update(iv); |
|
} |
|
g.update(getLengthBlock(iv.length)); |
|
j0 = g.digest(); |
|
} |
|
return j0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
private void checkDataLength(int ... lengths) { |
|
int max = MAX_BUF_SIZE; |
|
for (int len : lengths) { |
|
max = Math.subtractExact(max, len); |
|
} |
|
if (engine.processed > max) { |
|
throw new ProviderException("SunJCE provider only supports " + |
|
"input size up to " + MAX_BUF_SIZE + " bytes"); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
abstract class GCMEngine { |
|
byte[] preCounterBlock; |
|
GCTR gctrPAndC; |
|
GHASH ghashAllToS; |
|
|
|
|
|
final int blockSize; |
|
|
|
|
|
int processed = 0; |
|
|
|
|
|
ByteArrayOutputStream aadBuffer = null; |
|
int sizeOfAAD = 0; |
|
boolean aadProcessed = false; |
|
|
|
|
|
ByteArrayOutputStream ibuffer = null; |
|
|
|
|
|
ByteBuffer originalDst = null; |
|
byte[] originalOut = null; |
|
int originalOutOfs = 0; |
|
|
|
|
|
GCMEngine(SymmetricCipher blockCipher) { |
|
blockSize = blockCipher.getBlockSize(); |
|
byte[] subkeyH = new byte[blockSize]; |
|
blockCipher.encryptBlock(subkeyH, 0, subkeyH,0); |
|
preCounterBlock = getJ0(iv, subkeyH, blockSize); |
|
byte[] j0Plus1 = preCounterBlock.clone(); |
|
increment32(j0Plus1); |
|
gctrPAndC = new GCTR(blockCipher, j0Plus1); |
|
ghashAllToS = new GHASH(subkeyH); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
abstract int getOutputSize(int inLen, boolean isFinal); |
|
|
|
|
|
abstract byte[] doUpdate(byte[] in, int inOff, int inLen); |
|
abstract int doUpdate(byte[] in, int inOff, int inLen, byte[] out, |
|
int outOff) throws ShortBufferException; |
|
abstract int doUpdate(ByteBuffer src, ByteBuffer dst) |
|
throws ShortBufferException; |
|
|
|
|
|
abstract int doFinal(byte[] in, int inOff, int inLen, byte[] out, |
|
int outOff) throws IllegalBlockSizeException, AEADBadTagException, |
|
ShortBufferException; |
|
abstract int doFinal(ByteBuffer src, ByteBuffer dst) |
|
throws IllegalBlockSizeException, AEADBadTagException, |
|
ShortBufferException; |
|
|
|
|
|
void initBuffer(int len) { |
|
if (ibuffer == null) { |
|
ibuffer = new ByteArrayOutputStream(len); |
|
} |
|
} |
|
|
|
|
|
int getBufferedLength() { |
|
return (ibuffer == null ? 0 : ibuffer.size()); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int mergeBlock(byte[] buffer, int bufOfs, byte[] in, int inOfs, |
|
int inLen, byte[] block) { |
|
return mergeBlock(buffer, bufOfs, buffer.length - bufOfs, in, |
|
inOfs, inLen, block); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int mergeBlock(byte[] buffer, int bufOfs, int bufLen, byte[] in, |
|
int inOfs, int inLen, byte[] block) { |
|
if (bufLen > blockSize) { |
|
throw new RuntimeException("mergeBlock called on an ibuffer " + |
|
"too big: " + bufLen + " bytes"); |
|
} |
|
|
|
System.arraycopy(buffer, bufOfs, block, 0, bufLen); |
|
int inUsed = Math.min(block.length - bufLen, inLen); |
|
System.arraycopy(in, inOfs, block, bufLen, inUsed); |
|
return inUsed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void updateAAD(byte[] src, int offset, int len) { |
|
if (encryption) { |
|
checkReInit(); |
|
} |
|
|
|
if (aadBuffer == null) { |
|
if (sizeOfAAD == 0 && !aadProcessed) { |
|
aadBuffer = new ByteArrayOutputStream(len); |
|
} else { |
|
|
|
throw new IllegalStateException |
|
("Update has been called; no more AAD data"); |
|
} |
|
} |
|
aadBuffer.write(src, offset, len); |
|
} |
|
|
|
|
|
void processAAD() { |
|
if (aadBuffer != null) { |
|
if (aadBuffer.size() > 0) { |
|
byte[] aad = aadBuffer.toByteArray(); |
|
sizeOfAAD = aad.length; |
|
|
|
int lastLen = aad.length % blockSize; |
|
if (lastLen != 0) { |
|
ghashAllToS.update(aad, 0, aad.length - lastLen); |
|
byte[] padded = expandToOneBlock(aad, |
|
aad.length - lastLen, lastLen, blockSize); |
|
ghashAllToS.update(padded); |
|
} else { |
|
ghashAllToS.update(aad); |
|
} |
|
} |
|
aadBuffer = null; |
|
} |
|
aadProcessed = true; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int doLastBlock(GCM op, ByteBuffer buffer, ByteBuffer src, ByteBuffer dst) { |
|
int resultLen = 0; |
|
|
|
int bLen = (buffer != null ? buffer.remaining() : 0); |
|
if (bLen > 0) { |
|
|
|
if (bLen >= blockSize) { |
|
resultLen += op.update(buffer, dst); |
|
} |
|
|
|
|
|
if (bLen - resultLen > 0) { |
|
|
|
byte[] block = new byte[blockSize]; |
|
int over = buffer.remaining(); |
|
buffer.get(block, 0, over); |
|
|
|
|
|
int slen = Math.min(src.remaining(), blockSize - over); |
|
if (slen > 0) { |
|
src.get(block, over, slen); |
|
} |
|
int len = slen + over; |
|
if (len == blockSize) { |
|
resultLen += op.update(block, 0, blockSize, dst); |
|
} else { |
|
resultLen += op.doFinal(block, 0, len, block, |
|
0); |
|
if (dst != null) { |
|
dst.put(block, 0, len); |
|
} |
|
processed += resultLen; |
|
return resultLen; |
|
} |
|
} |
|
} |
|
|
|
// en/decrypt whatever remains in src. |
|
|
|
if (src.remaining() > TRIGGERLEN) { |
|
resultLen += throttleData(op, src, dst); |
|
} |
|
|
|
resultLen += op.doFinal(src, dst); |
|
processed += resultLen; |
|
return resultLen; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int throttleData(GCM op, byte[] in, int inOfs, int inLen, |
|
byte[] out, int outOfs) { |
|
|
|
int segments = (inLen / 6); |
|
segments -= segments % blockSize; |
|
int len = 0; |
|
int i = 0; |
|
do { |
|
len += op.update(in, inOfs + len, segments, out,outOfs + len); |
|
} while (++i < 5); |
|
|
|
len += op.update(in, inOfs + len, inLen - len, out, outOfs + len); |
|
return len; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int throttleData(GCM op, ByteBuffer src, ByteBuffer dst) { |
|
int inLen = src.limit(); |
|
int segments = (src.remaining() / 6); |
|
segments -= segments % blockSize; |
|
int i = 0, resultLen = 0; |
|
do { |
|
src.limit(src.position() + segments); |
|
resultLen += op.update(src, dst); |
|
} while (++i < 5); |
|
|
|
src.limit(inLen); |
|
|
|
if (src.remaining() > blockSize) { |
|
resultLen += op.update(src, dst); |
|
} |
|
|
|
return resultLen; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
ByteBuffer overlapDetection(ByteBuffer src, ByteBuffer dst) { |
|
if (src.isDirect() && dst.isDirect()) { |
|
DirectBuffer dsrc = (DirectBuffer) src; |
|
DirectBuffer ddst = (DirectBuffer) dst; |
|
|
|
|
|
long srcaddr = dsrc.address(); |
|
long dstaddr = ddst.address(); |
|
|
|
// Find the lowest attachment that is the base memory address |
|
|
|
while (dsrc.attachment() != null) { |
|
srcaddr = ((DirectBuffer) dsrc.attachment()).address(); |
|
dsrc = (DirectBuffer) dsrc.attachment(); |
|
} |
|
|
|
// Find the lowest attachment that is the base memory address |
|
|
|
while (ddst.attachment() != null) { |
|
dstaddr = ((DirectBuffer) ddst.attachment()).address(); |
|
ddst = (DirectBuffer) ddst.attachment(); |
|
} |
|
|
|
|
|
if (srcaddr != dstaddr) { |
|
return dst; |
|
} |
|
// At this point we know these objects share the same memory. |
|
// This checks the starting position of the src and dst address |
|
// for overlap. |
|
// It uses the base address minus the passed object's address to |
|
// get the offset from the base address, then add the position() |
|
// from the passed object. That gives up the true offset from |
|
// the base address. As long as the src side is >= the dst |
|
|
|
if (((DirectBuffer) src).address() - srcaddr + src.position() >= |
|
((DirectBuffer) dst).address() - dstaddr + dst.position()) { |
|
return dst; |
|
} |
|
|
|
} else if (!src.isDirect() && !dst.isDirect()) { |
|
|
|
if (!src.isReadOnly()) { |
|
|
|
if (src.array() != dst.array()) { |
|
return dst; |
|
} |
|
|
|
// Position plus arrayOffset() will give us the true offset |
|
|
|
if (src.position() + src.arrayOffset() >= |
|
dst.position() + dst.arrayOffset()) { |
|
return dst; |
|
} |
|
} |
|
} else { |
|
|
|
return dst; |
|
} |
|
|
|
|
|
ByteBuffer tmp = dst.duplicate(); |
|
|
|
ByteBuffer bb = ByteBuffer.allocate(dst.remaining()); |
|
tmp.limit(dst.limit()); |
|
tmp.position(dst.position()); |
|
bb.put(tmp); |
|
bb.flip(); |
|
originalDst = dst; |
|
return bb; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
byte[] overlapDetection(byte[] in, int inOfs, byte[] out, int outOfs) { |
|
if (in == out && inOfs < outOfs) { |
|
originalOut = out; |
|
originalOutOfs = outOfs; |
|
return new byte[out.length]; |
|
} |
|
return out; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
void restoreDst(ByteBuffer dst) { |
|
if (originalDst == null) { |
|
return; |
|
} |
|
|
|
dst.flip(); |
|
originalDst.put(dst); |
|
originalDst = null; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
void restoreOut(byte[] out, int len) { |
|
if (originalOut == null) { |
|
return; |
|
} |
|
|
|
System.arraycopy(out, originalOutOfs, originalOut, originalOutOfs, |
|
len); |
|
originalOut = null; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
class GCMEncrypt extends GCMEngine { |
|
GCTRGHASH gctrghash; |
|
|
|
GCMEncrypt(SymmetricCipher blockCipher) { |
|
super(blockCipher); |
|
gctrghash = new GCTRGHASH(gctrPAndC, ghashAllToS); |
|
} |
|
|
|
@Override |
|
public int getOutputSize(int inLen, boolean isFinal) { |
|
int len = getBufferedLength(); |
|
if (isFinal) { |
|
return len + inLen + tagLenBytes; |
|
} else { |
|
len += inLen; |
|
return len - (len % blockCipher.getBlockSize()); |
|
} |
|
} |
|
|
|
@Override |
|
byte[] doUpdate(byte[] in, int inOff, int inLen) { |
|
checkReInit(); |
|
byte[] output = new byte[getOutputSize(inLen, false)]; |
|
try { |
|
doUpdate(in, inOff, inLen, output, 0); |
|
} catch (ShortBufferException e) { |
|
|
|
throw new ProviderException("output buffer creation failed", e); |
|
} |
|
return output; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int doUpdate(byte[] in, int inOfs, int inLen, byte[] out, |
|
int outOfs) throws ShortBufferException { |
|
|
|
checkReInit(); |
|
|
|
// 'inLen' stores the length to use with buffer 'in'. |
|
|
|
int len = 0; |
|
int bLen = getBufferedLength(); |
|
checkDataLength(inLen, bLen); |
|
|
|
processAAD(); |
|
out = overlapDetection(in, inOfs, out, outOfs); |
|
|
|
|
|
if (bLen > 0) { |
|
byte[] buffer = ibuffer.toByteArray(); |
|
|
|
int remainder = blockSize - bLen; |
|
|
|
// Construct and encrypt a block if there is enough 'buffer' and |
|
|
|
if ((inLen + bLen) >= blockSize) { |
|
byte[] block = new byte[blockSize]; |
|
|
|
System.arraycopy(buffer, 0, block, 0, bLen); |
|
System.arraycopy(in, inOfs, block, bLen, remainder); |
|
|
|
len = gctrghash.update(block, 0, blockSize, out, outOfs); |
|
inOfs += remainder; |
|
inLen -= remainder; |
|
outOfs += blockSize; |
|
ibuffer.reset(); |
|
} |
|
} |
|
|
|
|
|
if (inLen >= blockSize) { |
|
len += gctrghash.update(in, inOfs, inLen, out, outOfs); |
|
} |
|
|
|
|
|
int remainder = inLen % blockSize; |
|
if (remainder > 0) { |
|
initBuffer(remainder); |
|
inLen -= remainder; |
|
|
|
ibuffer.write(in, inOfs + inLen, remainder); |
|
} |
|
|
|
restoreOut(out, len); |
|
processed += len; |
|
return len; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int doUpdate(ByteBuffer src, ByteBuffer dst) |
|
throws ShortBufferException { |
|
checkReInit(); |
|
int bLen = getBufferedLength(); |
|
checkDataLength(src.remaining(), bLen); |
|
|
|
|
|
int len = 0; |
|
|
|
processAAD(); |
|
|
|
dst = overlapDetection(src, dst); |
|
|
|
if (bLen > 0) { |
|
|
|
int remainder = blockSize - bLen; |
|
|
|
if (src.remaining() >= remainder) { |
|
byte[] block = new byte[blockSize]; |
|
ByteBuffer buffer = ByteBuffer.wrap(ibuffer.toByteArray()); |
|
buffer.get(block, 0, bLen); |
|
src.get(block, bLen, remainder); |
|
len += cryptBlocks( |
|
ByteBuffer.wrap(block, 0, blockSize), dst); |
|
ibuffer.reset(); |
|
} |
|
} |
|
|
|
|
|
if (src.remaining() >= blockSize) { |
|
len += cryptBlocks(src, dst); |
|
} |
|
|
|
|
|
if (src.remaining() > 0) { |
|
initBuffer(src.remaining()); |
|
byte[] b = new byte[src.remaining()]; |
|
src.get(b); |
|
|
|
try { |
|
ibuffer.write(b); |
|
} catch (IOException e) { |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
restoreDst(dst); |
|
return len; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, |
|
int outOfs) throws IllegalBlockSizeException, ShortBufferException { |
|
checkReInit(); |
|
try { |
|
ArrayUtil.nullAndBoundsCheck(out, outOfs, getOutputSize(inLen, |
|
true)); |
|
} catch (ArrayIndexOutOfBoundsException aiobe) { |
|
throw new ShortBufferException("Output buffer invalid"); |
|
} |
|
|
|
int bLen = getBufferedLength(); |
|
checkDataLength(inLen, bLen, tagLenBytes); |
|
processAAD(); |
|
out = overlapDetection(in, inOfs, out, outOfs); |
|
|
|
int resultLen = 0; |
|
byte[] block; |
|
|
|
|
|
if (bLen > 0) { |
|
byte[] buffer = ibuffer.toByteArray(); |
|
|
|
|
|
if (bLen + inLen >= blockSize) { |
|
int r, bufOfs = 0; |
|
block = new byte[blockSize]; |
|
r = mergeBlock(buffer, bufOfs, in, inOfs, inLen, block); |
|
inOfs += r; |
|
inLen -= r; |
|
r = gctrghash.update(block, 0, blockSize, out, |
|
outOfs); |
|
outOfs += r; |
|
resultLen += r; |
|
processed += r; |
|
} else { |
|
|
|
block = new byte[bLen + inLen]; |
|
System.arraycopy(buffer, 0, block, 0, bLen); |
|
System.arraycopy(in, inOfs, block, bLen, inLen); |
|
inLen += bLen; |
|
in = block; |
|
inOfs = 0; |
|
} |
|
} |
|
|
|
|
|
if (inLen > TRIGGERLEN) { |
|
int r = throttleData(gctrghash, in, inOfs, inLen, out, outOfs); |
|
inOfs += r; |
|
inLen -= r; |
|
outOfs += r; |
|
resultLen += r; |
|
processed += r; |
|
} |
|
|
|
processed += gctrghash.doFinal(in, inOfs, inLen, out, outOfs); |
|
outOfs += inLen; |
|
resultLen += inLen; |
|
|
|
block = getLengthBlock(sizeOfAAD, processed); |
|
ghashAllToS.update(block); |
|
block = ghashAllToS.digest(); |
|
new GCTR(blockCipher, preCounterBlock).doFinal(block, 0, |
|
tagLenBytes, block, 0); |
|
|
|
|
|
System.arraycopy(block, 0, out, outOfs, tagLenBytes); |
|
int len = resultLen + tagLenBytes; |
|
restoreOut(out, len); |
|
|
|
reInit = true; |
|
return len; |
|
} |
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int doFinal(ByteBuffer src, ByteBuffer dst) throws |
|
IllegalBlockSizeException, ShortBufferException { |
|
checkReInit(); |
|
dst = overlapDetection(src, dst); |
|
int len = src.remaining() + getBufferedLength(); |
|
|
|
|
|
checkDataLength(len, tagLenBytes); |
|
if (dst.remaining() < len + tagLenBytes) { |
|
throw new ShortBufferException("Output buffer too small, must" + |
|
"be at least " + (len + tagLenBytes) + " bytes long"); |
|
} |
|
|
|
processAAD(); |
|
if (len > 0) { |
|
processed += doLastBlock(gctrghash, |
|
(ibuffer == null || ibuffer.size() == 0) ? null : |
|
ByteBuffer.wrap(ibuffer.toByteArray()), src, dst); |
|
} |
|
|
|
|
|
if (ibuffer != null) { |
|
ibuffer.reset(); |
|
} |
|
|
|
byte[] block = getLengthBlock(sizeOfAAD, processed); |
|
ghashAllToS.update(block); |
|
block = ghashAllToS.digest(); |
|
new GCTR(blockCipher, preCounterBlock).doFinal(block, 0, |
|
tagLenBytes, block, 0); |
|
dst.put(block, 0, tagLenBytes); |
|
restoreDst(dst); |
|
|
|
reInit = true; |
|
return (len + tagLenBytes); |
|
} |
|
|
|
|
|
int cryptBlocks(ByteBuffer src, ByteBuffer dst) { |
|
int len; |
|
if (src.remaining() > TRIGGERLEN) { |
|
len = throttleData(gctrghash, src, dst); |
|
} else { |
|
len = gctrghash.update(src, dst); |
|
} |
|
processed += len; |
|
return len; |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
class GCMDecrypt extends GCMEngine { |
|
|
|
byte[] tag; |
|
|
|
int tagOfs = 0; |
|
|
|
GCMDecrypt(SymmetricCipher blockCipher) { |
|
super(blockCipher); |
|
} |
|
|
|
@Override |
|
public int getOutputSize(int inLen, boolean isFinal) { |
|
if (!isFinal) { |
|
return 0; |
|
} |
|
return Math.max(inLen + getBufferedLength() - tagLenBytes, 0); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void findTag(byte[] in, int inOfs, int inLen) { |
|
tag = new byte[tagLenBytes]; |
|
if (inLen >= tagLenBytes) { |
|
tagOfs = inLen - tagLenBytes; |
|
System.arraycopy(in, inOfs + tagOfs, tag, 0, |
|
tagLenBytes); |
|
} else { |
|
|
|
byte[] buffer = ibuffer.toByteArray(); |
|
tagOfs = mergeBlock(buffer, |
|
buffer.length - (tagLenBytes - inLen), in, inOfs, inLen, |
|
tag) - tagLenBytes; |
|
} |
|
} |
|
|
|
|
|
@Override |
|
byte[] doUpdate(byte[] in, int inOff, int inLen) { |
|
try { |
|
doUpdate(in, inOff, inLen, null, 0); |
|
} catch (ShortBufferException e) { |
|
// update decryption has no output |
|
} |
|
return new byte[0]; |
|
} |
|
|
|
|
|
@Override |
|
public int doUpdate(byte[] in, int inOfs, int inLen, byte[] out, |
|
int outOfs) throws ShortBufferException { |
|
|
|
processAAD(); |
|
if (inLen > 0) { |
|
// store internally until decryptFinal is called because |
|
// spec mentioned that only return recovered data after tag |
|
|
|
initBuffer(inLen); |
|
ibuffer.write(in, inOfs, inLen); |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
|
|
@Override |
|
public int doUpdate(ByteBuffer src, ByteBuffer dst) |
|
throws ShortBufferException { |
|
|
|
processAAD(); |
|
if (src.remaining() > 0) { |
|
// If there is an array, use that to avoid the extra copy to |
|
|
|
if (src.hasArray()) { |
|
doUpdate(src.array(), src.arrayOffset() + src.position(), |
|
src.remaining(), null, 0); |
|
src.position(src.limit()); |
|
} else { |
|
byte[] b = new byte[src.remaining()]; |
|
src.get(b); |
|
initBuffer(b.length); |
|
try { |
|
ibuffer.write(b); |
|
} catch (IOException e) { |
|
throw new ProviderException( |
|
"Unable to add remaining input to the buffer", e); |
|
} |
|
} |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, |
|
int outOfs) throws IllegalBlockSizeException, AEADBadTagException, |
|
ShortBufferException { |
|
GHASH save = null; |
|
|
|
int len = inLen + getBufferedLength(); |
|
try { |
|
ArrayUtil.nullAndBoundsCheck(out, outOfs, len - tagLenBytes); |
|
} catch (ArrayIndexOutOfBoundsException aiobe) { |
|
throw new ShortBufferException("Output buffer invalid"); |
|
} |
|
|
|
if (len < tagLenBytes) { |
|
throw new AEADBadTagException("Input too short - need tag"); |
|
} |
|
|
|
if (len - tagLenBytes > out.length - outOfs) { |
|
save = ghashAllToS.clone(); |
|
} |
|
|
|
checkDataLength(len - tagLenBytes); |
|
processAAD(); |
|
|
|
findTag(in, inOfs, inLen); |
|
byte[] block = getLengthBlock(sizeOfAAD, |
|
decryptBlocks(ghashAllToS, in, inOfs, inLen, null, 0)); |
|
ghashAllToS.update(block); |
|
block = ghashAllToS.digest(); |
|
new GCTR(blockCipher, preCounterBlock).doFinal(block, 0, |
|
tagLenBytes, block, 0); |
|
|
|
|
|
int mismatch = 0; |
|
for (int i = 0; i < tagLenBytes; i++) { |
|
mismatch |= tag[i] ^ block[i]; |
|
} |
|
|
|
if (mismatch != 0) { |
|
throw new AEADBadTagException("Tag mismatch!"); |
|
} |
|
|
|
if (save != null) { |
|
ghashAllToS = save; |
|
throw new ShortBufferException("Output buffer too small, must" + |
|
"be at least " + (len - tagLenBytes) + " bytes long"); |
|
} |
|
|
|
out = overlapDetection(in, inOfs, out, outOfs); |
|
len = decryptBlocks(gctrPAndC, in, inOfs, inLen, out, outOfs); |
|
restoreOut(out, len); |
|
return len; |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
public int doFinal(ByteBuffer src, ByteBuffer dst) |
|
throws IllegalBlockSizeException, AEADBadTagException, |
|
ShortBufferException { |
|
GHASH save = null; |
|
|
|
ByteBuffer tag; |
|
ByteBuffer ct = src.duplicate(); |
|
ByteBuffer buffer = null; |
|
|
|
|
|
int len = ct.remaining() - tagLenBytes; |
|
|
|
|
|
if (getBufferedLength() != 0) { |
|
buffer = ByteBuffer.wrap(ibuffer.toByteArray()); |
|
len += buffer.remaining(); |
|
} |
|
|
|
checkDataLength(len); |
|
|
|
// Save GHASH context to allow the tag to be checked even though |
|
// the dst buffer is too short. Context will be restored so the |
|
|
|
if (len > dst.remaining()) { |
|
save = ghashAllToS.clone(); |
|
} |
|
|
|
|
|
if (ct.remaining() >= tagLenBytes) { |
|
tag = src.duplicate(); |
|
tag.position(ct.limit() - tagLenBytes); |
|
ct.limit(ct.limit() - tagLenBytes); |
|
} else if (buffer != null) { |
|
|
|
tag = ByteBuffer.allocate(tagLenBytes); |
|
int limit = buffer.remaining() - (tagLenBytes - ct.remaining()); |
|
buffer.mark(); |
|
buffer.position(limit); |
|
|
|
tag.put(buffer); |
|
|
|
buffer.reset(); |
|
|
|
buffer.limit(limit); |
|
tag.put(ct); |
|
tag.flip(); |
|
} else { |
|
throw new AEADBadTagException("Input too short - need tag"); |
|
} |
|
|
|
// Set the mark for a later reset. Either it will be zero, or the |
|
|
|
ct.mark(); |
|
|
|
processAAD(); |
|
|
|
doLastBlock(ghashAllToS, buffer, ct, null); |
|
|
|
byte[] block = getLengthBlock(sizeOfAAD, len); |
|
ghashAllToS.update(block); |
|
block = ghashAllToS.digest(); |
|
new GCTR(blockCipher, preCounterBlock).doFinal(block, 0, |
|
tagLenBytes, block, 0); |
|
|
|
|
|
int mismatch = 0; |
|
for (int i = 0; i < tagLenBytes; i++) { |
|
mismatch |= tag.get() ^ block[i]; |
|
} |
|
|
|
if (mismatch != 0) { |
|
throw new AEADBadTagException("Tag mismatch!"); |
|
} |
|
|
|
if (save != null) { |
|
ghashAllToS = save; |
|
throw new ShortBufferException("Output buffer too small, must" + |
|
" be at least " + len + " bytes long"); |
|
} |
|
|
|
|
|
if (buffer != null) { |
|
buffer.flip(); |
|
} |
|
ct.reset(); |
|
processed = 0; |
|
|
|
dst = overlapDetection(src, dst); |
|
|
|
|
|
doLastBlock(gctrPAndC, buffer, ct, dst); |
|
restoreDst(dst); |
|
src.position(src.limit()); |
|
if (ibuffer != null) { |
|
ibuffer.reset(); |
|
} |
|
return processed; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int decryptBlocks(GCM op, byte[] in, int inOfs, int inLen, |
|
byte[] out, int outOfs) { |
|
byte[] buffer; |
|
byte[] block; |
|
int len = 0; |
|
|
|
// Calculate the encrypted data length inside the ibuffer |
|
|
|
int bLen = getBufferedLength(); |
|
|
|
|
|
if (tagOfs < 0) { |
|
inLen = 0; |
|
bLen += tagOfs; |
|
} else { |
|
inLen -= tagLenBytes; |
|
} |
|
|
|
if (bLen > 0) { |
|
buffer = ibuffer.toByteArray(); |
|
|
|
if (bLen >= blockSize) { |
|
len += op.update(buffer, 0, bLen, out, outOfs); |
|
outOfs += len; |
|
// Use len as it becomes the ibuffer offset, if |
|
// needed, in the next op |
|
} |
|
|
|
|
|
int bufRemainder = bLen - len; |
|
if (bufRemainder > 0) { |
|
block = new byte[blockSize]; |
|
int inUsed = mergeBlock(buffer, len, bufRemainder, in, |
|
inOfs, inLen, block); |
|
|
|
inOfs += inUsed; |
|
inLen -= inUsed; |
|
// If is more than block between the merged data and 'in', |
|
|
|
if (inLen > 0) { |
|
int resultLen = op.update(block, 0, blockSize, |
|
out, outOfs); |
|
outOfs += resultLen; |
|
len += resultLen; |
|
} else { |
|
in = block; |
|
inOfs = 0; |
|
inLen = inUsed + bufRemainder; |
|
} |
|
} |
|
} |
|
|
|
|
|
if (inLen > TRIGGERLEN) { |
|
int l = throttleData(op, in, inOfs, inLen, out, outOfs); |
|
inOfs += l; |
|
inLen -= l; |
|
outOfs += l; |
|
len += l; |
|
} |
|
return len + op.doFinal(in, inOfs, inLen, out, outOfs); |
|
} |
|
} |
|
|
|
public static final class AESGCM extends GaloisCounterMode { |
|
public AESGCM() { |
|
super(-1, new AESCrypt()); |
|
} |
|
} |
|
|
|
public static final class AES128 extends GaloisCounterMode { |
|
public AES128() { |
|
super(16, new AESCrypt()); |
|
} |
|
} |
|
|
|
public static final class AES192 extends GaloisCounterMode { |
|
public AES192() { |
|
super(24, new AESCrypt()); |
|
} |
|
} |
|
|
|
public static final class AES256 extends GaloisCounterMode { |
|
public AES256() { |
|
super(32, new AESCrypt()); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
*/ |
|
static final class GCTRGHASH implements GCM { |
|
GCTR gctr; |
|
GHASH ghash; |
|
|
|
GCTRGHASH(GCTR c, GHASH g) { |
|
gctr = c; |
|
ghash = g; |
|
} |
|
|
|
@Override |
|
public int update(byte[] in, int inOfs, int inLen, byte[] out, |
|
int outOfs) { |
|
int len = gctr.update(in, inOfs, inLen, out, outOfs); |
|
ghash.update(out, outOfs, len); |
|
return len; |
|
} |
|
|
|
@Override |
|
public int update(byte[] in, int inOfs, int inLen, ByteBuffer dst) { |
|
dst.mark(); |
|
int len = gctr.update(in, inOfs, inLen, dst); |
|
dst.reset(); |
|
ghash.update(dst, len); |
|
return len; |
|
} |
|
|
|
@Override |
|
public int update(ByteBuffer src, ByteBuffer dst) { |
|
dst.mark(); |
|
int len = gctr.update(src, dst); |
|
dst.reset(); |
|
ghash.update(dst, len); |
|
return len; |
|
} |
|
|
|
@Override |
|
public int doFinal(byte[] in, int inOfs, int inLen, byte[] out, int outOfs) { |
|
int len = gctr.doFinal(in, inOfs, inLen, out, outOfs); |
|
ghash.doFinal(out, outOfs, len); |
|
return len; |
|
} |
|
|
|
@Override |
|
public int doFinal(ByteBuffer src, ByteBuffer dst) { |
|
dst.mark(); |
|
int l = gctr.doFinal(src, dst); |
|
dst.reset(); |
|
ghash.doFinal(dst, l); |
|
return l; |
|
} |
|
} |
|
} |