private void reset(boolean clearMac) { 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; if (bufBlock != null) { Arrays.fill(bufBlock, (byte) 0); } if (clearMac) { macBlock = null; } if (initialAssociatedText != null) { processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); } }
/** * NOTE: MAC sizes from 32 bits to 128 bits (must be a multiple of 8) are supported. The default * is 128 bits. Sizes less than 96 are not recommended, but are supported for specialized * applications. */ @Override public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException { this.forEncryption = forEncryption; this.macBlock = null; KeyParameter keyParam; if (params instanceof AEADParameters) { AEADParameters param = (AEADParameters) params; nonce = param.getNonce(); initialAssociatedText = param.getAssociatedText(); int macSizeBits = param.getMacSize(); if (macSizeBits < 32 || macSizeBits > 128 || macSizeBits % 8 != 0) { throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits); } macSize = macSizeBits / 8; keyParam = param.getKey(); } else if (params instanceof ParametersWithIV) { ParametersWithIV param = (ParametersWithIV) params; nonce = param.getIV(); initialAssociatedText = null; macSize = 16; keyParam = (KeyParameter) param.getParameters(); } else { throw new IllegalArgumentException("invalid parameters passed to GCM"); } int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize); this.bufBlock = new byte[bufLength]; if (nonce == null || nonce.length < 1) { throw new IllegalArgumentException("IV must be at least 1 byte"); } // TODO Restrict macSize to 16 if nonce length not 12? // Cipher always used in forward mode // if keyParam is null we're reusing the last key. if (keyParam != null) { cipher.init(true, keyParam); this.H = new byte[BLOCK_SIZE]; cipher.processBlock(H, 0, H, 0); // GCMMultiplier tables don't change unless the key changes (and are expensive to init) multiplier.init(H); exp = null; } else if (this.H == null) { throw new IllegalArgumentException("Key must be specified in initial init"); } this.J0 = new byte[BLOCK_SIZE]; if (nonce.length == 12) { System.arraycopy(nonce, 0, J0, 0, nonce.length); this.J0[BLOCK_SIZE - 1] = 0x01; } else { gHASH(J0, nonce, nonce.length); byte[] X = new byte[BLOCK_SIZE]; Pack.longToBigEndian((long) nonce.length * 8, X, 8); gHASHBlock(J0, X); } 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); } }
@Override public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException { if (totalLength == 0) { initCipher(); } int extra = bufOff; if (forEncryption) { if (out.length < (outOff + extra + macSize)) { throw new OutputLengthException("Output buffer too short"); } } else { if (extra < macSize) { throw new InvalidCipherTextException("data too short"); } extra -= macSize; if (out.length < (outOff + extra)) { throw new OutputLengthException("Output buffer too short"); } } if (extra > 0) { 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) { GCMUtil.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 GCMUtil.multiply(S_at, H_c); // Adjust the current hash GCMUtil.xor(S, S_at); } // Final gHASH byte[] X = new byte[BLOCK_SIZE]; Pack.longToBigEndian(atLength * 8, X, 0); Pack.longToBigEndian(totalLength * 8, X, 8); gHASHBlock(S, X); // T = MSBt(GCTRk(J0,S)) byte[] tag = new byte[BLOCK_SIZE]; cipher.processBlock(J0, 0, tag, 0); GCMUtil.xor(tag, S); int resultLen = extra; // We place into macBlock our calculated value for T this.macBlock = new byte[macSize]; System.arraycopy(tag, 0, macBlock, 0, macSize); if (forEncryption) { // Append T to the message System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize); resultLen += macSize; } else { // Retrieve the T value from the message and compare to calculated one byte[] msgMac = new byte[macSize]; System.arraycopy(bufBlock, extra, msgMac, 0, macSize); if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac)) { throw new InvalidCipherTextException("mac check in GCM failed"); } } reset(false); return resultLen; }
@Override public byte[] getMac() { return Arrays.clone(macBlock); }