using System; using Org.BouncyCastle.Crypto.Macs; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Math; using Org.BouncyCastle.Utilities; namespace Org.BouncyCastle.Crypto.Modes { /// /// Implements the Galois/Counter mode (GCM) detailed in /// NIST Special Publication 800-38D. /// public class GcmBlockCipher : IAeadBlockCipher { private const int BlockSize = 16; private static readonly byte[] Zeroes = new byte[BlockSize]; private static readonly BigInteger R = new BigInteger("11100001", 2).ShiftLeft(120); private readonly IBlockCipher cipher; // These fields are set by Init and not modified by processing private bool forEncryption; private int macSize; private byte[] nonce; private byte[] A; private KeyParameter keyParam; // private int tagLength; private BigInteger H; private BigInteger initS; private byte[] J0; // These fields are modified during processing private byte[] bufBlock; private byte[] macBlock; private BigInteger S; private byte[] counter; private int bufOff; private long totalLength; // Debug variables // private int nCount, xCount, yCount; public GcmBlockCipher( IBlockCipher c) { if (c.GetBlockSize() != BlockSize) throw new ArgumentException("cipher required with a block size of " + BlockSize + "."); this.cipher = c; } public virtual string AlgorithmName { get { return cipher.AlgorithmName + "/GCM"; } } public virtual int GetBlockSize() { return BlockSize; } public virtual void Init( bool forEncryption, ICipherParameters parameters) { this.forEncryption = forEncryption; this.macSize = 16; // TODO Make configurable? this.macBlock = null; // TODO If macSize limitation is removed, be very careful about bufBlock int bufLength = forEncryption ? BlockSize : (BlockSize + macSize); this.bufBlock = new byte[bufLength]; if (parameters is AeadParameters) { AeadParameters param = (AeadParameters)parameters; nonce = param.GetNonce(); A = param.GetAssociatedText(); // macSize = param.getMacSize() / 8; if (param.MacSize != 128) { // TODO Make configurable? throw new ArgumentException("only 128-bit MAC supported currently"); } keyParam = param.Key; } else if (parameters is ParametersWithIV) { ParametersWithIV param = (ParametersWithIV)parameters; nonce = param.GetIV(); A = null; keyParam = (KeyParameter)param.Parameters; } else { throw new ArgumentException("invalid parameters passed to GCM"); } if (nonce == null || nonce.Length < 1) { throw new ArgumentException("IV must be at least 1 byte"); } if (A == null) { // Avoid lots of null checks A = new byte[0]; } // Cipher always used input forward mode cipher.Init(true, keyParam); // TODO This should be configurable by Init parameters // (but must be 16 if nonce length not 12) (BlockSize?) // this.tagLength = 16; byte[] h = new byte[BlockSize]; cipher.ProcessBlock(Zeroes, 0, h, 0); //trace("H: " + new string(Hex.encode(h))); this.H = new BigInteger(1, h); this.initS = gHASH(A, false); if (nonce.Length == 12) { this.J0 = new byte[16]; Array.Copy(nonce, 0, J0, 0, nonce.Length); this.J0[15] = 0x01; } else { BigInteger N = gHASH(nonce, true); BigInteger X = BigInteger.ValueOf(nonce.Length * 8); //trace("len({})||len(IV): " + dumpBigInt(X)); N = multiply(N.Xor(X), H); //trace("GHASH(H,{},IV): " + dumpBigInt(N)); this.J0 = asBlock(N); } this.S = initS; this.counter = Arrays.Clone(J0); //trace("Y" + yCount + ": " + new string(Hex.encode(counter))); this.bufOff = 0; this.totalLength = 0; } public virtual byte[] GetMac() { return Arrays.Clone(macBlock); } public virtual int GetOutputSize( int len) { if (forEncryption) { return len + bufOff + macSize; } return len + bufOff - macSize; } public virtual int GetUpdateOutputSize( int len) { return ((len + bufOff) / BlockSize) * BlockSize; } public virtual int ProcessByte( byte input, byte[] output, int outOff) { return Process(input, output, outOff); } public virtual int ProcessBytes( byte[] input, int inOff, int len, byte[] output, int outOff) { int resultLen = 0; for (int i = 0; i != len; i++) { resultLen += Process(input[inOff + i], output, outOff + resultLen); } return resultLen; } private int Process( byte input, byte[] output, int outOff) { bufBlock[bufOff++] = input; if (bufOff == bufBlock.Length) { gCTRBlock(bufBlock, BlockSize, output, outOff); if (!forEncryption) { Array.Copy(bufBlock, BlockSize, bufBlock, 0, BlockSize); } // bufOff = 0; bufOff = bufBlock.Length - BlockSize; // return bufBlock.Length; return BlockSize; } return 0; } public int DoFinal(byte[] output, int outOff) { int extra = bufOff; if (!forEncryption) { if (extra < macSize) throw new InvalidCipherTextException("data too short"); extra -= macSize; } if (extra > 0) { byte[] tmp = new byte[BlockSize]; Array.Copy(bufBlock, 0, tmp, 0, extra); gCTRBlock(tmp, extra, output, outOff); } // Final gHASH BigInteger X = BigInteger.ValueOf(A.Length * 8).ShiftLeft(64).Add( BigInteger.ValueOf(totalLength * 8)); //trace("len(A)||len(C): " + dumpBigInt(X)); S = multiply(S.Xor(X), H); //trace("GHASH(H,A,C): " + dumpBigInt(S)); // T = MSBt(GCTRk(J0,S)) byte[] tBytes = new byte[BlockSize]; cipher.ProcessBlock(J0, 0, tBytes, 0); //trace("E(K,Y0): " + new string(Hex.encode(tmp))); BigInteger T = S.Xor(new BigInteger(1, tBytes)); // TODO Fix this if tagLength becomes configurable byte[] tag = asBlock(T); //trace("T: " + new string(Hex.encode(tag))); int resultLen = extra; if (forEncryption) { this.macBlock = tag; Array.Copy(tag, 0, output, outOff + bufOff, tag.Length); resultLen += tag.Length; } else { this.macBlock = new byte[macSize]; Array.Copy(bufBlock, extra, macBlock, 0, macSize); if (!Arrays.AreEqual(tag, this.macBlock)) throw new InvalidCipherTextException("mac check input GCM failed"); } Reset(false); return resultLen; } public virtual void Reset() { Reset(true); } private void Reset( bool clearMac) { // Debug // nCount = xCount = yCount = 0; S = initS; counter = Arrays.Clone(J0); bufOff = 0; totalLength = 0; if (bufBlock != null) { Array.Clear(bufBlock, 0, bufBlock.Length); } if (clearMac) { macBlock = null; } cipher.Reset(); } private void gCTRBlock(byte[] buf, int bufCount, byte[] output, int outOff) { inc(counter); //trace("Y" + ++yCount + ": " + new string(Hex.encode(counter))); byte[] tmp = new byte[BlockSize]; cipher.ProcessBlock(counter, 0, tmp, 0); //trace("E(K,Y" + yCount + "): " + new string(Hex.encode(tmp))); if (forEncryption) { Array.Copy(Zeroes, bufCount, tmp, bufCount, BlockSize - bufCount); for (int i = bufCount - 1; i >= 0; --i) { tmp[i] ^= buf[i]; output[outOff + i] = tmp[i]; } gHASHBlock(tmp); } else { for (int i = bufCount - 1; i >= 0; --i) { tmp[i] ^= buf[i]; output[outOff + i] = tmp[i]; } gHASHBlock(buf); } totalLength += bufCount; } private BigInteger gHASH(byte[] b, bool nonce) { //trace("" + b.Length); BigInteger Y = BigInteger.Zero; for (int pos = 0; pos < b.Length; pos += 16) { byte[] x = new byte[16]; int num = System.Math.Min(b.Length - pos, 16); Array.Copy(b, pos, x, 0, num); BigInteger X = new BigInteger(1, x); Y = multiply(Y.Xor(X), H); // if (nonce) // { // trace("N" + ++nCount + ": " + dumpBigInt(Y)); // } // else // { // trace("X" + ++xCount + ": " + dumpBigInt(Y) + " (gHASH)"); // } } return Y; } private void gHASHBlock(byte[] block) { if (block.Length > BlockSize) { byte[] tmp = new byte[BlockSize]; Array.Copy(block, 0, tmp, 0, BlockSize); block = tmp; } BigInteger X = new BigInteger(1, block); S = multiply(S.Xor(X), H); //trace("X" + ++xCount + ": " + dumpBigInt(S) + " (gHASHBlock)"); } private static void inc(byte[] block) { // assert block.Length == 16; for (int i = 15; i >= 12; --i) { byte b = (byte)((block[i] + 1) & 0xff); block[i] = b; if (b != 0) { break; } } } private BigInteger multiply( BigInteger X, BigInteger Y) { BigInteger Z = BigInteger.Zero; BigInteger V = X; for (int i = 0; i < 128; ++i) { if (Y.TestBit(127 - i)) { Z = Z.Xor(V); } bool lsb = V.TestBit(0); V = V.ShiftRight(1); if (lsb) { V = V.Xor(R); } } return Z; } private byte[] asBlock( BigInteger bi) { byte[] b = BigIntegers.AsUnsignedByteArray(bi); if (b.Length < 16) { byte[] tmp = new byte[16]; Array.Copy(b, 0, tmp, tmp.Length - b.Length, b.Length); b = tmp; } return b; } // private string dumpBigInt(BigInteger bi) // { // byte[] b = asBlock(bi); // // return new string(Hex.encode(b)); // } // // private void trace(string msg) // { // System.err.println(msg); // } } }