package org.bouncycastle.crypto.modes;

import java.io.ByteArrayOutputStream;

import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.CipherParameters;
import org.bouncycastle.crypto.DataLengthException;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.OutputLengthException;
import org.bouncycastle.crypto.modes.kgcm.KGCMMultiplier;
import org.bouncycastle.crypto.modes.kgcm.Tables16kKGCMMultiplier_512;
import org.bouncycastle.crypto.modes.kgcm.Tables4kKGCMMultiplier_128;
import org.bouncycastle.crypto.modes.kgcm.Tables8kKGCMMultiplier_256;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Pack;

Implementation of DSTU7624 GCM mode
/** * Implementation of DSTU7624 GCM mode */
public class KGCMBlockCipher implements AEADBlockCipher { private static final int MIN_MAC_BITS = 64; private static KGCMMultiplier createDefaultMultiplier(int blockSize) { switch (blockSize) { case 16: return new Tables4kKGCMMultiplier_128(); case 32: return new Tables8kKGCMMultiplier_256(); case 64: return new Tables16kKGCMMultiplier_512(); default: throw new IllegalArgumentException("Only 128, 256, and 512 -bit block sizes supported"); } } private BlockCipher engine; private BufferedBlockCipher ctrEngine; private int macSize; private boolean forEncryption; private byte[] initialAssociatedText; private byte[] macBlock; private byte[] iv; private KGCMMultiplier multiplier; private long[] b; private final int blockSize; private ExposedByteArrayOutputStream associatedText = new ExposedByteArrayOutputStream(); private ExposedByteArrayOutputStream data = new ExposedByteArrayOutputStream(); public KGCMBlockCipher(BlockCipher dstu7624Engine) { this.engine = dstu7624Engine; this.ctrEngine = new BufferedBlockCipher(new KCTRBlockCipher(this.engine)); this.macSize = -1; this.blockSize = engine.getBlockSize(); this.initialAssociatedText = new byte[blockSize]; this.iv = new byte[blockSize]; this.multiplier = createDefaultMultiplier(blockSize); this.b = new long[blockSize >>> 3]; this.macBlock = null; } public void init(boolean forEncryption, CipherParameters params) throws IllegalArgumentException { this.forEncryption = forEncryption; KeyParameter engineParam; if (params instanceof AEADParameters) { AEADParameters param = (AEADParameters)params; byte[] iv = param.getNonce(); int diff = this.iv.length - iv.length; Arrays.fill(this.iv, (byte)0); System.arraycopy(iv, 0, this.iv, diff, iv.length); initialAssociatedText = param.getAssociatedText(); int macSizeBits = param.getMacSize(); if (macSizeBits < MIN_MAC_BITS || macSizeBits > (blockSize << 3) || (macSizeBits & 7) != 0) { throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits); } macSize = macSizeBits >>> 3; engineParam = param.getKey(); if (initialAssociatedText != null) { processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); } } else if (params instanceof ParametersWithIV) { ParametersWithIV param = (ParametersWithIV)params; byte[] iv = param.getIV(); int diff = this.iv.length - iv.length; Arrays.fill(this.iv, (byte)0); System.arraycopy(iv, 0, this.iv, diff, iv.length); initialAssociatedText = null; macSize = blockSize; // Set default mac size engineParam = (KeyParameter)param.getParameters(); } else { throw new IllegalArgumentException("Invalid parameter passed"); } // TODO Nonce re-use check (sample code from GCMBlockCipher) // if (forEncryption) // { // if (nonce != null && Arrays.areEqual(nonce, newNonce)) // { // if (keyParam == null) // { // throw new IllegalArgumentException("cannot reuse nonce for GCM encryption"); // } // if (lastKey != null && Arrays.areEqual(lastKey, keyParam.getKey())) // { // throw new IllegalArgumentException("cannot reuse nonce for GCM encryption"); // } // } // } this.macBlock = new byte[blockSize]; ctrEngine.init(true, new ParametersWithIV(engineParam, this.iv)); engine.init(true, engineParam); } public String getAlgorithmName() { return engine.getAlgorithmName() + "/KGCM"; } public BlockCipher getUnderlyingCipher() { return engine; } public void processAADByte(byte in) { associatedText.write(in); } public void processAADBytes(byte[] in, int inOff, int len) { associatedText.write(in, inOff, len); } private void processAAD(byte[] authText, int authOff, int len) { int pos = authOff, end = authOff + len; while (pos < end) { xorWithInput(b, authText, pos); multiplier.multiplyH(b); pos += blockSize; } } public int processByte(byte in, byte[] out, int outOff) throws DataLengthException, IllegalStateException { data.write(in); return 0; } public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff) throws DataLengthException, IllegalStateException { if (in.length < (inOff + inLen)) { throw new DataLengthException("input buffer too short"); } data.write(in, inOff, inLen); return 0; } public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException { int len = data.size(); if (!forEncryption && len < macSize) { throw new InvalidCipherTextException("data too short"); } // TODO Total blocks restriction in GCM mode (extend limit naturally for larger block sizes?) // Set up the multiplier { byte[] temp = new byte[blockSize]; engine.processBlock(temp, 0, temp, 0); long[] H = new long[blockSize >>> 3]; Pack.littleEndianToLong(temp, 0, H); multiplier.init(H); Arrays.fill(temp, (byte)0); Arrays.fill(H, 0L); } int lenAAD = associatedText.size(); if (lenAAD > 0) { processAAD(associatedText.getBuffer(), 0, lenAAD); } //use alternative cipher to produce output int resultLen; if (forEncryption) { if (out.length - outOff - macSize < len) { throw new OutputLengthException("Output buffer too short"); } resultLen = ctrEngine.processBytes(data.getBuffer(), 0, len, out, outOff); resultLen += ctrEngine.doFinal(out, outOff + resultLen); calculateMac(out, outOff, len, lenAAD); } else { int ctLen = len - macSize; if (out.length - outOff < ctLen) { throw new OutputLengthException("Output buffer too short"); } calculateMac(data.getBuffer(), 0, ctLen, lenAAD); resultLen = ctrEngine.processBytes(data.getBuffer(), 0, ctLen, out, outOff); resultLen += ctrEngine.doFinal(out, outOff + resultLen); } if (macBlock == null) { throw new IllegalStateException("mac is not calculated"); } if (forEncryption) { System.arraycopy(macBlock, 0, out, outOff + resultLen, macSize); reset(); return resultLen + macSize; } else { byte[] mac = new byte[macSize]; System.arraycopy(data.getBuffer(), len - macSize, mac, 0, macSize); byte[] calculatedMac = new byte[macSize]; System.arraycopy(macBlock, 0, calculatedMac, 0, macSize); if (!Arrays.constantTimeAreEqual(mac, calculatedMac)) { throw new InvalidCipherTextException("mac verification failed"); } reset(); return resultLen; } } public byte[] getMac() { byte[] mac = new byte[macSize]; System.arraycopy(macBlock, 0, mac, 0, macSize); return mac; } public int getUpdateOutputSize(int len) { return 0; } public int getOutputSize(int len) { int totalData = len + data.size(); if (forEncryption) { return totalData + macSize; } return totalData < macSize ? 0 : totalData - macSize; } public void reset() { Arrays.fill(b, 0L); engine.reset(); data.reset(); associatedText.reset(); if (initialAssociatedText != null) { processAADBytes(initialAssociatedText, 0, initialAssociatedText.length); } } private void calculateMac(byte[] input, int inOff, int len, int lenAAD) { int pos = inOff, end = inOff + len; while (pos < end) { xorWithInput(b, input, pos); multiplier.multiplyH(b); pos += blockSize; } long lambda_o = (lenAAD & 0xFFFFFFFFL) << 3; long lambda_c = (len & 0xFFFFFFFFL) << 3; // byte[] temp = new byte[blockSize]; // Pack.longToLittleEndian(lambda_o, temp, 0); // Pack.longToLittleEndian(lambda_c, temp, blockSize / 2); // // xorWithInput(b, temp, 0); b[0] ^= lambda_o; b[blockSize >>> 4] ^= lambda_c; macBlock = Pack.longToLittleEndian(b); engine.processBlock(macBlock, 0, macBlock, 0); } private static void xorWithInput(long[] z, byte[] buf, int off) { for (int i = 0; i < z.length; ++i) { z[i] ^= Pack.littleEndianToLong(buf, off); off += 8; } } private class ExposedByteArrayOutputStream extends ByteArrayOutputStream { public ExposedByteArrayOutputStream() { } public byte[] getBuffer() { return this.buf; } } }