|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
|
|
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; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
final class GaloisCounterMode extends FeedbackCipher { |
|
|
|
static int DEFAULT_TAG_LEN = AES_BLOCK_SIZE; |
|
static int DEFAULT_IV_LEN = 12; |
|
|
|
// 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; |
|
|
|
|
|
private ByteArrayOutputStream aadBuffer = new ByteArrayOutputStream(); |
|
private int sizeOfAAD = 0; |
|
|
|
|
|
private ByteArrayOutputStream ibuffer = null; |
|
|
|
|
|
private int tagLenBytes = DEFAULT_TAG_LEN; |
|
|
|
// these following 2 fields can only be initialized after init() is |
|
|
|
private byte[] subkeyH = null; |
|
private byte[] preCounterBlock = null; |
|
|
|
private GCTR gctrPAndC = null; |
|
private GHASH ghashAllToS = null; |
|
|
|
|
|
private int processed = 0; |
|
|
|
|
|
private byte[] aadBufferSave = null; |
|
private int sizeOfAADSave = 0; |
|
private byte[] ibufferSave = null; |
|
private int processedSave = 0; |
|
|
|
|
|
static void increment32(byte[] value) { |
|
if (value.length != AES_BLOCK_SIZE) { |
|
|
|
throw new ProviderException("Illegal counter block length"); |
|
} |
|
|
|
int n = value.length - 1; |
|
while ((n >= value.length - 4) && (++value[n] == 0)) { |
|
n--; |
|
} |
|
} |
|
|
|
private static byte[] getLengthBlock(int ivLenInBytes) { |
|
long ivLen = ((long)ivLenInBytes) << 3; |
|
byte[] out = new byte[AES_BLOCK_SIZE]; |
|
out[8] = (byte)(ivLen >>> 56); |
|
out[9] = (byte)(ivLen >>> 48); |
|
out[10] = (byte)(ivLen >>> 40); |
|
out[11] = (byte)(ivLen >>> 32); |
|
out[12] = (byte)(ivLen >>> 24); |
|
out[13] = (byte)(ivLen >>> 16); |
|
out[14] = (byte)(ivLen >>> 8); |
|
out[15] = (byte)ivLen; |
|
return out; |
|
} |
|
|
|
private static byte[] getLengthBlock(int aLenInBytes, int cLenInBytes) { |
|
long aLen = ((long)aLenInBytes) << 3; |
|
long cLen = ((long)cLenInBytes) << 3; |
|
byte[] out = new byte[AES_BLOCK_SIZE]; |
|
out[0] = (byte)(aLen >>> 56); |
|
out[1] = (byte)(aLen >>> 48); |
|
out[2] = (byte)(aLen >>> 40); |
|
out[3] = (byte)(aLen >>> 32); |
|
out[4] = (byte)(aLen >>> 24); |
|
out[5] = (byte)(aLen >>> 16); |
|
out[6] = (byte)(aLen >>> 8); |
|
out[7] = (byte)aLen; |
|
out[8] = (byte)(cLen >>> 56); |
|
out[9] = (byte)(cLen >>> 48); |
|
out[10] = (byte)(cLen >>> 40); |
|
out[11] = (byte)(cLen >>> 32); |
|
out[12] = (byte)(cLen >>> 24); |
|
out[13] = (byte)(cLen >>> 16); |
|
out[14] = (byte)(cLen >>> 8); |
|
out[15] = (byte)cLen; |
|
return out; |
|
} |
|
|
|
private static byte[] expandToOneBlock(byte[] in, int inOfs, int len) { |
|
if (len > AES_BLOCK_SIZE) { |
|
throw new ProviderException("input " + len + " too long"); |
|
} |
|
if (len == AES_BLOCK_SIZE && inOfs == 0) { |
|
return in; |
|
} else { |
|
byte[] paddedIn = new byte[AES_BLOCK_SIZE]; |
|
System.arraycopy(in, inOfs, paddedIn, 0, len); |
|
return paddedIn; |
|
} |
|
} |
|
|
|
private static byte[] getJ0(byte[] iv, byte[] subkeyH) { |
|
byte[] j0; |
|
if (iv.length == 12) { |
|
j0 = expandToOneBlock(iv, 0, iv.length); |
|
j0[AES_BLOCK_SIZE - 1] = 1; |
|
} else { |
|
GHASH g = new GHASH(subkeyH); |
|
int lastLen = iv.length % AES_BLOCK_SIZE; |
|
if (lastLen != 0) { |
|
g.update(iv, 0, iv.length - lastLen); |
|
byte[] padded = |
|
expandToOneBlock(iv, iv.length - lastLen, lastLen); |
|
g.update(padded); |
|
} else { |
|
g.update(iv); |
|
} |
|
byte[] lengthBlock = getLengthBlock(iv.length); |
|
g.update(lengthBlock); |
|
j0 = g.digest(); |
|
} |
|
return j0; |
|
} |
|
|
|
private static void checkDataLength(int processed, int len) { |
|
if (processed > MAX_BUF_SIZE - len) { |
|
throw new ProviderException("SunJCE provider only supports " + |
|
"input size up to " + MAX_BUF_SIZE + " bytes"); |
|
} |
|
} |
|
|
|
GaloisCounterMode(SymmetricCipher embeddedCipher) { |
|
super(embeddedCipher); |
|
aadBuffer = new ByteArrayOutputStream(); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
String getFeedback() { |
|
return "GCM"; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void reset() { |
|
if (aadBuffer == null) { |
|
aadBuffer = new ByteArrayOutputStream(); |
|
} else { |
|
aadBuffer.reset(); |
|
} |
|
if (gctrPAndC != null) gctrPAndC.reset(); |
|
if (ghashAllToS != null) ghashAllToS.reset(); |
|
processed = 0; |
|
sizeOfAAD = 0; |
|
if (ibuffer != null) { |
|
ibuffer.reset(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
void save() { |
|
processedSave = processed; |
|
sizeOfAADSave = sizeOfAAD; |
|
aadBufferSave = |
|
((aadBuffer == null || aadBuffer.size() == 0)? |
|
null : aadBuffer.toByteArray()); |
|
if (gctrPAndC != null) gctrPAndC.save(); |
|
if (ghashAllToS != null) ghashAllToS.save(); |
|
if (ibuffer != null) { |
|
ibufferSave = ibuffer.toByteArray(); |
|
} |
|
} |
|
|
|
|
|
|
|
*/ |
|
void restore() { |
|
processed = processedSave; |
|
sizeOfAAD = sizeOfAADSave; |
|
if (aadBuffer != null) { |
|
aadBuffer.reset(); |
|
if (aadBufferSave != null) { |
|
aadBuffer.write(aadBufferSave, 0, aadBufferSave.length); |
|
} |
|
} |
|
if (gctrPAndC != null) gctrPAndC.restore(); |
|
if (ghashAllToS != null) ghashAllToS.restore(); |
|
if (ibuffer != null) { |
|
ibuffer.reset(); |
|
ibuffer.write(ibufferSave, 0, ibufferSave.length); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
@Override |
|
void init(boolean decrypting, String algorithm, byte[] key, byte[] iv) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
init(decrypting, algorithm, key, iv, DEFAULT_TAG_LEN); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void init(boolean decrypting, String algorithm, byte[] keyValue, |
|
byte[] ivValue, int tagLenBytes) |
|
throws InvalidKeyException, InvalidAlgorithmParameterException { |
|
if (keyValue == null) { |
|
throw new InvalidKeyException("Internal error"); |
|
} |
|
if (ivValue == null) { |
|
throw new InvalidAlgorithmParameterException("Internal error"); |
|
} |
|
if (ivValue.length == 0) { |
|
throw new InvalidAlgorithmParameterException("IV is empty"); |
|
} |
|
|
|
|
|
this.embeddedCipher.init(false, algorithm, keyValue); |
|
this.subkeyH = new byte[AES_BLOCK_SIZE]; |
|
this.embeddedCipher.encryptBlock(new byte[AES_BLOCK_SIZE], 0, |
|
this.subkeyH, 0); |
|
|
|
this.iv = ivValue.clone(); |
|
preCounterBlock = getJ0(iv, subkeyH); |
|
byte[] j0Plus1 = preCounterBlock.clone(); |
|
increment32(j0Plus1); |
|
gctrPAndC = new GCTR(embeddedCipher, j0Plus1); |
|
ghashAllToS = new GHASH(subkeyH); |
|
|
|
this.tagLenBytes = tagLenBytes; |
|
if (aadBuffer == null) { |
|
aadBuffer = new ByteArrayOutputStream(); |
|
} else { |
|
aadBuffer.reset(); |
|
} |
|
processed = 0; |
|
sizeOfAAD = 0; |
|
if (decrypting) { |
|
ibuffer = new ByteArrayOutputStream(); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
void updateAAD(byte[] src, int offset, int len) { |
|
if (aadBuffer != null) { |
|
aadBuffer.write(src, offset, len); |
|
} else { |
|
|
|
throw new IllegalStateException |
|
("Update has been called; no more AAD data"); |
|
} |
|
} |
|
|
|
|
|
void processAAD() { |
|
if (aadBuffer != null) { |
|
if (aadBuffer.size() > 0) { |
|
byte[] aad = aadBuffer.toByteArray(); |
|
sizeOfAAD = aad.length; |
|
|
|
int lastLen = aad.length % AES_BLOCK_SIZE; |
|
if (lastLen != 0) { |
|
ghashAllToS.update(aad, 0, aad.length - lastLen); |
|
byte[] padded = expandToOneBlock(aad, aad.length - lastLen, |
|
lastLen); |
|
ghashAllToS.update(padded); |
|
} else { |
|
ghashAllToS.update(aad); |
|
} |
|
} |
|
aadBuffer = null; |
|
} |
|
} |
|
|
|
|
|
void doLastBlock(byte[] in, int inOfs, int len, byte[] out, int outOfs, |
|
boolean isEncrypt) throws IllegalBlockSizeException { |
|
|
|
gctrPAndC.doFinal(in, inOfs, len, out, outOfs); |
|
processed += len; |
|
|
|
byte[] ct; |
|
int ctOfs; |
|
if (isEncrypt) { |
|
ct = out; |
|
ctOfs = outOfs; |
|
} else { |
|
ct = in; |
|
ctOfs = inOfs; |
|
} |
|
int lastLen = len % AES_BLOCK_SIZE; |
|
if (lastLen != 0) { |
|
ghashAllToS.update(ct, ctOfs, len - lastLen); |
|
byte[] padded = |
|
expandToOneBlock(ct, (ctOfs + len - lastLen), lastLen); |
|
ghashAllToS.update(padded); |
|
} else { |
|
ghashAllToS.update(ct, ctOfs, len); |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int encrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { |
|
if ((len % blockSize) != 0) { |
|
throw new ProviderException("Internal error in input buffering"); |
|
} |
|
|
|
checkDataLength(processed, len); |
|
|
|
processAAD(); |
|
if (len > 0) { |
|
gctrPAndC.update(in, inOfs, len, out, outOfs); |
|
processed += len; |
|
ghashAllToS.update(out, outOfs, len); |
|
} |
|
return len; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int encryptFinal(byte[] in, int inOfs, int len, byte[] out, int outOfs) |
|
throws IllegalBlockSizeException, ShortBufferException { |
|
if (len > MAX_BUF_SIZE - tagLenBytes) { |
|
throw new ShortBufferException |
|
("Can't fit both data and tag into one buffer"); |
|
} |
|
if (out.length - outOfs < (len + tagLenBytes)) { |
|
throw new ShortBufferException("Output buffer too small"); |
|
} |
|
|
|
checkDataLength(processed, len); |
|
|
|
processAAD(); |
|
if (len > 0) { |
|
doLastBlock(in, inOfs, len, out, outOfs, true); |
|
} |
|
|
|
byte[] lengthBlock = |
|
getLengthBlock(sizeOfAAD, processed); |
|
ghashAllToS.update(lengthBlock); |
|
byte[] s = ghashAllToS.digest(); |
|
byte[] sOut = new byte[s.length]; |
|
GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock); |
|
gctrForSToTag.doFinal(s, 0, s.length, sOut, 0); |
|
|
|
System.arraycopy(sOut, 0, out, (outOfs + len), tagLenBytes); |
|
return (len + tagLenBytes); |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int decrypt(byte[] in, int inOfs, int len, byte[] out, int outOfs) { |
|
if ((len % blockSize) != 0) { |
|
throw new ProviderException("Internal error in input buffering"); |
|
} |
|
|
|
checkDataLength(ibuffer.size(), len); |
|
|
|
processAAD(); |
|
|
|
if (len > 0) { |
|
// store internally until decryptFinal is called because |
|
// spec mentioned that only return recovered data after tag |
|
|
|
ibuffer.write(in, inOfs, len); |
|
} |
|
return 0; |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
*/ |
|
int decryptFinal(byte[] in, int inOfs, int len, |
|
byte[] out, int outOfs) |
|
throws IllegalBlockSizeException, AEADBadTagException, |
|
ShortBufferException { |
|
if (len < tagLenBytes) { |
|
throw new AEADBadTagException("Input too short - need tag"); |
|
} |
|
// do this check here can also catch the potential integer overflow |
|
|
|
checkDataLength(ibuffer.size(), (len - tagLenBytes)); |
|
|
|
if (out.length - outOfs < ((ibuffer.size() + len) - tagLenBytes)) { |
|
throw new ShortBufferException("Output buffer too small"); |
|
} |
|
|
|
processAAD(); |
|
|
|
|
|
byte[] tag = new byte[tagLenBytes]; |
|
System.arraycopy(in, inOfs + len - tagLenBytes, tag, 0, tagLenBytes); |
|
len -= tagLenBytes; |
|
|
|
if (len != 0) { |
|
ibuffer.write(in, inOfs, len); |
|
} |
|
|
|
|
|
in = ibuffer.toByteArray(); |
|
inOfs = 0; |
|
len = in.length; |
|
ibuffer.reset(); |
|
|
|
if (len > 0) { |
|
doLastBlock(in, inOfs, len, out, outOfs, false); |
|
} |
|
|
|
byte[] lengthBlock = |
|
getLengthBlock(sizeOfAAD, processed); |
|
ghashAllToS.update(lengthBlock); |
|
|
|
byte[] s = ghashAllToS.digest(); |
|
byte[] sOut = new byte[s.length]; |
|
GCTR gctrForSToTag = new GCTR(embeddedCipher, this.preCounterBlock); |
|
gctrForSToTag.doFinal(s, 0, s.length, sOut, 0); |
|
|
|
|
|
int mismatch = 0; |
|
for (int i = 0; i < tagLenBytes; i++) { |
|
mismatch |= tag[i] ^ sOut[i]; |
|
} |
|
|
|
if (mismatch != 0) { |
|
throw new AEADBadTagException("Tag mismatch!"); |
|
} |
|
|
|
return len; |
|
} |
|
|
|
|
|
int getTagLen() { |
|
return this.tagLenBytes; |
|
} |
|
|
|
int getBufferedLength() { |
|
if (ibuffer == null) { |
|
return 0; |
|
} else { |
|
return ibuffer.size(); |
|
} |
|
} |
|
} |