diff options
Diffstat (limited to 'bcprov/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java')
-rw-r--r-- | bcprov/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java | 403 |
1 files changed, 278 insertions, 125 deletions
diff --git a/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java b/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java index 7c98efa..9e617ec 100644 --- a/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java +++ b/bcprov/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java @@ -4,7 +4,9 @@ import org.bouncycastle.crypto.BlockCipher; import org.bouncycastle.crypto.CipherParameters; import org.bouncycastle.crypto.DataLengthException; import org.bouncycastle.crypto.InvalidCipherTextException; +import org.bouncycastle.crypto.modes.gcm.GCMExponentiator; import org.bouncycastle.crypto.modes.gcm.GCMMultiplier; +import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator; import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier; import org.bouncycastle.crypto.params.AEADParameters; import org.bouncycastle.crypto.params.KeyParameter; @@ -20,28 +22,31 @@ public class GCMBlockCipher implements AEADBlockCipher { private static final int BLOCK_SIZE = 16; - private static final byte[] ZEROES = new byte[BLOCK_SIZE]; // not final due to a compiler bug private BlockCipher cipher; private GCMMultiplier multiplier; + private GCMExponentiator exp; // These fields are set by init and not modified by processing private boolean forEncryption; private int macSize; private byte[] nonce; - private byte[] A; + private byte[] initialAssociatedText; private byte[] H; - private byte[] initS; private byte[] J0; // These fields are modified during processing private byte[] bufBlock; private byte[] macBlock; - private byte[] S; + private byte[] S, S_at, S_atPre; private byte[] counter; private int bufOff; private long totalLength; + private byte[] atBlock; + private int atBlockPos; + private long atLength; + private long atLengthPre; public GCMBlockCipher(BlockCipher c) { @@ -82,14 +87,14 @@ public class GCMBlockCipher this.forEncryption = forEncryption; this.macBlock = null; - KeyParameter keyParam; + KeyParameter keyParam; if (params instanceof AEADParameters) { AEADParameters param = (AEADParameters)params; nonce = param.getNonce(); - A = param.getAssociatedText(); + initialAssociatedText = param.getAssociatedText(); int macSizeBits = param.getMacSize(); if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0) @@ -105,7 +110,7 @@ public class GCMBlockCipher ParametersWithIV param = (ParametersWithIV)params; nonce = param.getIV(); - A = null; + initialAssociatedText = null; macSize = 16; keyParam = (KeyParameter)param.getParameters(); } @@ -122,48 +127,54 @@ public class GCMBlockCipher throw new IllegalArgumentException("IV must be at least 1 byte"); } - if (A == null) - { - // Avoid lots of null checks - A = new byte[0]; - } + // TODO This should be configurable by init parameters + // (but must be 16 if nonce length not 12) (BLOCK_SIZE?) +// this.tagLength = 16; // Cipher always used in forward mode // if keyParam is null we're reusing the last key. if (keyParam != null) { cipher.init(true, keyParam); - } - // TODO This should be configurable by init parameters - // (but must be 16 if nonce length not 12) (BLOCK_SIZE?) -// this.tagLength = 16; + this.H = new byte[BLOCK_SIZE]; + cipher.processBlock(H, 0, H, 0); - this.H = new byte[BLOCK_SIZE]; - cipher.processBlock(ZEROES, 0, H, 0); - multiplier.init(H); + // GCMMultiplier tables don't change unless the key changes (and are expensive to init) + multiplier.init(H); + exp = null; + } - this.initS = gHASH(A); + this.J0 = new byte[BLOCK_SIZE]; if (nonce.length == 12) { - this.J0 = new byte[16]; System.arraycopy(nonce, 0, J0, 0, nonce.length); - this.J0[15] = 0x01; + this.J0[BLOCK_SIZE - 1] = 0x01; } else { - this.J0 = gHASH(nonce); - byte[] X = new byte[16]; - packLength((long)nonce.length * 8, X, 8); - xor(this.J0, X); - multiplier.multiplyH(this.J0); + gHASH(J0, nonce, nonce.length); + byte[] X = new byte[BLOCK_SIZE]; + Pack.longToBigEndian((long)nonce.length * 8, X, 8); + gHASHBlock(J0, X); } - this.S = Arrays.clone(initS); + this.S = new byte[BLOCK_SIZE]; + this.S_at = new byte[BLOCK_SIZE]; + this.S_atPre = new byte[BLOCK_SIZE]; + this.atBlock = new byte[BLOCK_SIZE]; + this.atBlockPos = 0; + this.atLength = 0; + this.atLengthPre = 0; this.counter = Arrays.clone(J0); this.bufOff = 0; this.totalLength = 0; + + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } } public byte[] getMac() @@ -173,23 +184,88 @@ public class GCMBlockCipher public int getOutputSize(int len) { + int totalData = len + bufOff; + if (forEncryption) { - return len + bufOff + macSize; + return totalData + macSize; } - return len + bufOff - macSize; + return totalData < macSize ? 0 : totalData - macSize; } public int getUpdateOutputSize(int len) { - return ((len + bufOff) / BLOCK_SIZE) * BLOCK_SIZE; + int totalData = len + bufOff; + if (!forEncryption) + { + if (totalData < macSize) + { + return 0; + } + totalData -= macSize; + } + return totalData - totalData % BLOCK_SIZE; + } + + public void processAADByte(byte in) + { + atBlock[atBlockPos] = in; + if (++atBlockPos == BLOCK_SIZE) + { + // Hash each block as it fills + gHASHBlock(S_at, atBlock); + atBlockPos = 0; + atLength += BLOCK_SIZE; + } + } + + public void processAADBytes(byte[] in, int inOff, int len) + { + for (int i = 0; i < len; ++i) + { + atBlock[atBlockPos] = in[inOff + i]; + if (++atBlockPos == BLOCK_SIZE) + { + // Hash each block as it fills + gHASHBlock(S_at, atBlock); + atBlockPos = 0; + atLength += BLOCK_SIZE; + } + } + } + + private void initCipher() + { + if (atLength > 0) + { + System.arraycopy(S_at, 0, S_atPre, 0, BLOCK_SIZE); + atLengthPre = atLength; + } + + // Finish hash for partial AAD block + if (atBlockPos > 0) + { + gHASHPartial(S_atPre, atBlock, 0, atBlockPos); + atLengthPre += atBlockPos; + } + + if (atLengthPre > 0) + { + System.arraycopy(S_atPre, 0, S, 0, BLOCK_SIZE); + } } public int processByte(byte in, byte[] out, int outOff) throws DataLengthException { - return process(in, out, outOff); + bufBlock[bufOff] = in; + if (++bufOff == bufBlock.length) + { + outputBlock(out, outOff); + return BLOCK_SIZE; + } + return 0; } public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) @@ -197,21 +273,12 @@ public class GCMBlockCipher { int resultLen = 0; - for (int i = 0; i != len; i++) + for (int i = 0; i < len; ++i) { -// resultLen += process(in[inOff + i], out, outOff + resultLen); - bufBlock[bufOff++] = in[inOff + i]; - - if (bufOff == bufBlock.length) + bufBlock[bufOff] = in[inOff + i]; + if (++bufOff == bufBlock.length) { - gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff + resultLen); - if (!forEncryption) - { - System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); - } -// bufOff = 0; - bufOff = bufBlock.length - BLOCK_SIZE; -// return bufBlock.Length; + outputBlock(out, outOff + resultLen); resultLen += BLOCK_SIZE; } } @@ -219,30 +286,32 @@ public class GCMBlockCipher return resultLen; } - private int process(byte in, byte[] out, int outOff) - throws DataLengthException + private void outputBlock(byte[] output, int offset) { - bufBlock[bufOff++] = in; - - if (bufOff == bufBlock.length) + if (totalLength == 0) { - gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff); - if (!forEncryption) - { - System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); - } -// bufOff = 0; - bufOff = bufBlock.length - BLOCK_SIZE; -// return bufBlock.length; - return BLOCK_SIZE; + initCipher(); + } + gCTRBlock(bufBlock, output, offset); + if (forEncryption) + { + bufOff = 0; + } + else + { + System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize); + bufOff = macSize; } - - return 0; } public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException { + if (totalLength == 0) + { + initCipher(); + } + int extra = bufOff; if (!forEncryption) { @@ -255,18 +324,57 @@ public class GCMBlockCipher if (extra > 0) { - byte[] tmp = new byte[BLOCK_SIZE]; - System.arraycopy(bufBlock, 0, tmp, 0, extra); - gCTRBlock(tmp, extra, out, outOff); + gCTRPartial(bufBlock, 0, extra, out, outOff); + } + + atLength += atBlockPos; + + if (atLength > atLengthPre) + { + /* + * Some AAD was sent after the cipher started. We determine the difference b/w the hash value + * we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at). + * Then we carry this difference forward by multiplying by H^c, where c is the number of (full or + * partial) cipher-text blocks produced, and adjust the current hash. + */ + + // Finish hash for partial AAD block + if (atBlockPos > 0) + { + gHASHPartial(S_at, atBlock, 0, atBlockPos); + } + + // Find the difference between the AAD hashes + if (atLengthPre > 0) + { + xor(S_at, S_atPre); + } + + // Number of cipher-text blocks produced + long c = ((totalLength * 8) + 127) >>> 7; + + // Calculate the adjustment factor + byte[] H_c = new byte[16]; + if (exp == null) + { + exp = new Tables1kGCMExponentiator(); + exp.init(H); + } + exp.exponentiateX(c, H_c); + + // Carry the difference forward + multiply(S_at, H_c); + + // Adjust the current hash + xor(S, S_at); } // Final gHASH - byte[] X = new byte[16]; - packLength((long)A.length * 8, X, 0); - packLength(totalLength * 8, X, 8); + byte[] X = new byte[BLOCK_SIZE]; + Pack.longToBigEndian(atLength * 8, X, 0); + Pack.longToBigEndian(totalLength * 8, X, 8); - xor(S, X); - multiplier.multiplyH(S); + gHASHBlock(S, X); // TODO Fix this if tagLength becomes configurable // T = MSBt(GCTRk(J0,S)) @@ -310,7 +418,15 @@ public class GCMBlockCipher private void reset( boolean clearMac) { - S = Arrays.clone(initS); + cipher.reset(); + + S = new byte[BLOCK_SIZE]; + S_at = new byte[BLOCK_SIZE]; + S_atPre = new byte[BLOCK_SIZE]; + atBlock = new byte[BLOCK_SIZE]; + atBlockPos = 0; + atLength = 0; + atLengthPre = 0; counter = Arrays.clone(J0); bufOff = 0; totalLength = 0; @@ -325,12 +441,59 @@ public class GCMBlockCipher macBlock = null; } - cipher.reset(); + if (initialAssociatedText != null) + { + processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); + } + } + + private void gCTRBlock(byte[] block, byte[] out, int outOff) + { + byte[] tmp = getNextCounterBlock(); + + xor(tmp, block); + System.arraycopy(tmp, 0, out, outOff, BLOCK_SIZE); + + gHASHBlock(S, forEncryption ? tmp : block); + + totalLength += BLOCK_SIZE; + } + + private void gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff) + { + byte[] tmp = getNextCounterBlock(); + + xor(tmp, buf, off, len); + System.arraycopy(tmp, 0, out, outOff, len); + + gHASHPartial(S, forEncryption ? tmp : buf, 0, len); + + totalLength += len; + } + + private void gHASH(byte[] Y, byte[] b, int len) + { + for (int pos = 0; pos < len; pos += BLOCK_SIZE) + { + int num = Math.min(len - pos, BLOCK_SIZE); + gHASHPartial(Y, b, pos, num); + } + } + + private void gHASHBlock(byte[] Y, byte[] b) + { + xor(Y, b); + multiplier.multiplyH(Y); + } + + private void gHASHPartial(byte[] Y, byte[] b, int off, int len) + { + xor(Y, b, off, len); + multiplier.multiplyH(Y); } - private void gCTRBlock(byte[] buf, int bufCount, byte[] out, int outOff) + private byte[] getNextCounterBlock() { -// inc(counter); for (int i = 15; i >= 12; --i) { byte b = (byte)((counter[i] + 1) & 0xff); @@ -343,68 +506,56 @@ public class GCMBlockCipher } byte[] tmp = new byte[BLOCK_SIZE]; + // TODO Sure would be nice if ciphers could operate on int[] cipher.processBlock(counter, 0, tmp, 0); + return tmp; + } - byte[] hashBytes; - if (forEncryption) - { - System.arraycopy(ZEROES, bufCount, tmp, bufCount, BLOCK_SIZE - bufCount); - hashBytes = tmp; - } - else - { - hashBytes = buf; - } + private static void multiply(byte[] block, byte[] val) + { + byte[] tmp = Arrays.clone(block); + byte[] c = new byte[16]; - for (int i = bufCount - 1; i >= 0; --i) + for (int i = 0; i < 16; ++i) { - tmp[i] ^= buf[i]; - out[outOff + i] = tmp[i]; - } + byte bits = val[i]; + for (int j = 7; j >= 0; --j) + { + if ((bits & (1 << j)) != 0) + { + xor(c, tmp); + } -// gHASHBlock(hashBytes); - xor(S, hashBytes); - multiplier.multiplyH(S); + boolean lsb = (tmp[15] & 1) != 0; + shiftRight(tmp); + if (lsb) + { + // R = new byte[]{ 0xe1, ... }; +// xor(v, R); + tmp[0] ^= (byte)0xe1; + } + } + } - totalLength += bufCount; + System.arraycopy(c, 0, block, 0, 16); } - private byte[] gHASH(byte[] b) + private static void shiftRight(byte[] block) { - byte[] Y = new byte[16]; - - for (int pos = 0; pos < b.length; pos += 16) + int i = 0; + int bit = 0; + for (;;) { - byte[] X = new byte[16]; - int num = Math.min(b.length - pos, 16); - System.arraycopy(b, pos, X, 0, num); - xor(Y, X); - multiplier.multiplyH(Y); + int b = block[i] & 0xff; + block[i] = (byte) ((b >>> 1) | bit); + if (++i == 16) + { + break; + } + bit = (b & 1) << 7; } - - return Y; } -// private void gHASHBlock(byte[] block) -// { -// xor(S, block); -// multiplier.multiplyH(S); -// } - -// private static void inc(byte[] block) -// { -// for (int i = 15; i >= 12; --i) -// { -// byte b = (byte)((block[i] + 1) & 0xff); -// block[i] = b; -// -// if (b != 0) -// { -// break; -// } -// } -// } - private static void xor(byte[] block, byte[] val) { for (int i = 15; i >= 0; --i) @@ -413,9 +564,11 @@ public class GCMBlockCipher } } - private static void packLength(long count, byte[] bs, int off) + private static void xor(byte[] block, byte[] val, int off, int len) { - Pack.intToBigEndian((int)(count >>> 32), bs, off); - Pack.intToBigEndian((int)count, bs, off + 4); + while (len-- > 0) + { + block[len] ^= val[off + len]; + } } } |