Home | History | Annotate | Download | only in modes
      1 package org.bouncycastle.crypto.modes;
      2 
      3 import org.bouncycastle.crypto.BlockCipher;
      4 import org.bouncycastle.crypto.CipherParameters;
      5 import org.bouncycastle.crypto.DataLengthException;
      6 import org.bouncycastle.crypto.InvalidCipherTextException;
      7 import org.bouncycastle.crypto.modes.gcm.GCMMultiplier;
      8 import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier;
      9 import org.bouncycastle.crypto.params.AEADParameters;
     10 import org.bouncycastle.crypto.params.KeyParameter;
     11 import org.bouncycastle.crypto.params.ParametersWithIV;
     12 import org.bouncycastle.crypto.util.Pack;
     13 import org.bouncycastle.util.Arrays;
     14 
     15 /**
     16  * Implements the Galois/Counter mode (GCM) detailed in
     17  * NIST Special Publication 800-38D.
     18  */
     19 public class GCMBlockCipher
     20     implements AEADBlockCipher
     21 {
     22     private static final int BLOCK_SIZE = 16;
     23     private static final byte[] ZEROES = new byte[BLOCK_SIZE];
     24 
     25     // not final due to a compiler bug
     26     private BlockCipher   cipher;
     27     private GCMMultiplier multiplier;
     28 
     29     // These fields are set by init and not modified by processing
     30     private boolean             forEncryption;
     31     private int                 macSize;
     32     private byte[]              nonce;
     33     private byte[]              A;
     34     private KeyParameter        keyParam;
     35     private byte[]              H;
     36     private byte[]              initS;
     37     private byte[]              J0;
     38 
     39     // These fields are modified during processing
     40     private byte[]      bufBlock;
     41     private byte[]      macBlock;
     42     private byte[]      S;
     43     private byte[]      counter;
     44     private int         bufOff;
     45     private long        totalLength;
     46 
     47     public GCMBlockCipher(BlockCipher c)
     48     {
     49         this(c, null);
     50     }
     51 
     52     public GCMBlockCipher(BlockCipher c, GCMMultiplier m)
     53     {
     54         if (c.getBlockSize() != BLOCK_SIZE)
     55         {
     56             throw new IllegalArgumentException(
     57                 "cipher required with a block size of " + BLOCK_SIZE + ".");
     58         }
     59 
     60         if (m == null)
     61         {
     62             // TODO Consider a static property specifying default multiplier
     63             m = new Tables8kGCMMultiplier();
     64         }
     65 
     66         this.cipher = c;
     67         this.multiplier = m;
     68     }
     69 
     70     public BlockCipher getUnderlyingCipher()
     71     {
     72         return cipher;
     73     }
     74 
     75     public String getAlgorithmName()
     76     {
     77         return cipher.getAlgorithmName() + "/GCM";
     78     }
     79 
     80     public void init(boolean forEncryption, CipherParameters params)
     81         throws IllegalArgumentException
     82     {
     83         this.forEncryption = forEncryption;
     84         this.macBlock = null;
     85 
     86         if (params instanceof AEADParameters)
     87         {
     88             AEADParameters param = (AEADParameters)params;
     89 
     90             nonce = param.getNonce();
     91             A = param.getAssociatedText();
     92 
     93             int macSizeBits = param.getMacSize();
     94             if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0)
     95             {
     96                 throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits);
     97             }
     98 
     99             macSize = macSizeBits / 8;
    100             keyParam = param.getKey();
    101         }
    102         else if (params instanceof ParametersWithIV)
    103         {
    104             ParametersWithIV param = (ParametersWithIV)params;
    105 
    106             nonce = param.getIV();
    107             A = null;
    108             macSize = 16;
    109             keyParam = (KeyParameter)param.getParameters();
    110         }
    111         else
    112         {
    113             throw new IllegalArgumentException("invalid parameters passed to GCM");
    114         }
    115 
    116         int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize);
    117         this.bufBlock = new byte[bufLength];
    118 
    119         if (nonce == null || nonce.length < 1)
    120         {
    121             throw new IllegalArgumentException("IV must be at least 1 byte");
    122         }
    123 
    124         if (A == null)
    125         {
    126             // Avoid lots of null checks
    127             A = new byte[0];
    128         }
    129 
    130         // Cipher always used in forward mode
    131         cipher.init(true, keyParam);
    132 
    133         // TODO This should be configurable by init parameters
    134         // (but must be 16 if nonce length not 12) (BLOCK_SIZE?)
    135 //        this.tagLength = 16;
    136 
    137         this.H = new byte[BLOCK_SIZE];
    138         cipher.processBlock(ZEROES, 0, H, 0);
    139         multiplier.init(H);
    140 
    141         this.initS = gHASH(A);
    142 
    143         if (nonce.length == 12)
    144         {
    145             this.J0 = new byte[16];
    146             System.arraycopy(nonce, 0, J0, 0, nonce.length);
    147             this.J0[15] = 0x01;
    148         }
    149         else
    150         {
    151             this.J0 = gHASH(nonce);
    152             byte[] X = new byte[16];
    153             packLength((long)nonce.length * 8, X, 8);
    154             xor(this.J0, X);
    155             multiplier.multiplyH(this.J0);
    156         }
    157 
    158         this.S = Arrays.clone(initS);
    159         this.counter = Arrays.clone(J0);
    160         this.bufOff = 0;
    161         this.totalLength = 0;
    162     }
    163 
    164     public byte[] getMac()
    165     {
    166         return Arrays.clone(macBlock);
    167     }
    168 
    169     public int getOutputSize(int len)
    170     {
    171         if (forEncryption)
    172         {
    173              return len + bufOff + macSize;
    174         }
    175 
    176         return len + bufOff - macSize;
    177     }
    178 
    179     public int getUpdateOutputSize(int len)
    180     {
    181         return ((len + bufOff) / BLOCK_SIZE) * BLOCK_SIZE;
    182     }
    183 
    184     public int processByte(byte in, byte[] out, int outOff)
    185         throws DataLengthException
    186     {
    187         return process(in, out, outOff);
    188     }
    189 
    190     public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
    191         throws DataLengthException
    192     {
    193         int resultLen = 0;
    194 
    195         for (int i = 0; i != len; i++)
    196         {
    197 //            resultLen += process(in[inOff + i], out, outOff + resultLen);
    198             bufBlock[bufOff++] = in[inOff + i];
    199 
    200             if (bufOff == bufBlock.length)
    201             {
    202                 gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff + resultLen);
    203                 if (!forEncryption)
    204                 {
    205                     System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
    206                 }
    207 //              bufOff = 0;
    208                 bufOff = bufBlock.length - BLOCK_SIZE;
    209 //              return bufBlock.Length;
    210                 resultLen += BLOCK_SIZE;
    211             }
    212         }
    213 
    214         return resultLen;
    215     }
    216 
    217     private int process(byte in, byte[] out, int outOff)
    218         throws DataLengthException
    219     {
    220         bufBlock[bufOff++] = in;
    221 
    222         if (bufOff == bufBlock.length)
    223         {
    224             gCTRBlock(bufBlock, BLOCK_SIZE, out, outOff);
    225             if (!forEncryption)
    226             {
    227                 System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
    228             }
    229 //            bufOff = 0;
    230             bufOff = bufBlock.length - BLOCK_SIZE;
    231 //            return bufBlock.length;
    232             return BLOCK_SIZE;
    233         }
    234 
    235         return 0;
    236     }
    237 
    238     public int doFinal(byte[] out, int outOff)
    239         throws IllegalStateException, InvalidCipherTextException
    240     {
    241         int extra = bufOff;
    242         if (!forEncryption)
    243         {
    244             if (extra < macSize)
    245             {
    246                 throw new InvalidCipherTextException("data too short");
    247             }
    248             extra -= macSize;
    249         }
    250 
    251         if (extra > 0)
    252         {
    253             byte[] tmp = new byte[BLOCK_SIZE];
    254             System.arraycopy(bufBlock, 0, tmp, 0, extra);
    255             gCTRBlock(tmp, extra, out, outOff);
    256         }
    257 
    258         // Final gHASH
    259         byte[] X = new byte[16];
    260         packLength((long)A.length * 8, X, 0);
    261         packLength(totalLength * 8, X, 8);
    262 
    263         xor(S, X);
    264         multiplier.multiplyH(S);
    265 
    266         // TODO Fix this if tagLength becomes configurable
    267         // T = MSBt(GCTRk(J0,S))
    268         byte[] tag = new byte[BLOCK_SIZE];
    269         cipher.processBlock(J0, 0, tag, 0);
    270         xor(tag, S);
    271 
    272         int resultLen = extra;
    273 
    274         // We place into macBlock our calculated value for T
    275         this.macBlock = new byte[macSize];
    276         System.arraycopy(tag, 0, macBlock, 0, macSize);
    277 
    278         if (forEncryption)
    279         {
    280             // Append T to the message
    281             System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize);
    282             resultLen += macSize;
    283         }
    284         else
    285         {
    286             // Retrieve the T value from the message and compare to calculated one
    287             byte[] msgMac = new byte[macSize];
    288             System.arraycopy(bufBlock, extra, msgMac, 0, macSize);
    289             if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac))
    290             {
    291                 throw new InvalidCipherTextException("mac check in GCM failed");
    292             }
    293         }
    294 
    295         reset(false);
    296 
    297         return resultLen;
    298     }
    299 
    300     public void reset()
    301     {
    302         reset(true);
    303     }
    304 
    305     private void reset(
    306         boolean clearMac)
    307     {
    308         S = Arrays.clone(initS);
    309         counter = Arrays.clone(J0);
    310         bufOff = 0;
    311         totalLength = 0;
    312 
    313         if (bufBlock != null)
    314         {
    315             Arrays.fill(bufBlock, (byte)0);
    316         }
    317 
    318         if (clearMac)
    319         {
    320             macBlock = null;
    321         }
    322 
    323         cipher.reset();
    324     }
    325 
    326     private void gCTRBlock(byte[] buf, int bufCount, byte[] out, int outOff)
    327     {
    328 //        inc(counter);
    329         for (int i = 15; i >= 12; --i)
    330         {
    331             byte b = (byte)((counter[i] + 1) & 0xff);
    332             counter[i] = b;
    333 
    334             if (b != 0)
    335             {
    336                 break;
    337             }
    338         }
    339 
    340         byte[] tmp = new byte[BLOCK_SIZE];
    341         cipher.processBlock(counter, 0, tmp, 0);
    342 
    343         byte[] hashBytes;
    344         if (forEncryption)
    345         {
    346             System.arraycopy(ZEROES, bufCount, tmp, bufCount, BLOCK_SIZE - bufCount);
    347             hashBytes = tmp;
    348         }
    349         else
    350         {
    351             hashBytes = buf;
    352         }
    353 
    354         for (int i = bufCount - 1; i >= 0; --i)
    355         {
    356             tmp[i] ^= buf[i];
    357             out[outOff + i] = tmp[i];
    358         }
    359 
    360 //        gHASHBlock(hashBytes);
    361         xor(S, hashBytes);
    362         multiplier.multiplyH(S);
    363 
    364         totalLength += bufCount;
    365     }
    366 
    367     private byte[] gHASH(byte[] b)
    368     {
    369         byte[] Y = new byte[16];
    370 
    371         for (int pos = 0; pos < b.length; pos += 16)
    372         {
    373             byte[] X = new byte[16];
    374             int num = Math.min(b.length - pos, 16);
    375             System.arraycopy(b, pos, X, 0, num);
    376             xor(Y, X);
    377             multiplier.multiplyH(Y);
    378         }
    379 
    380         return Y;
    381     }
    382 
    383 //    private void gHASHBlock(byte[] block)
    384 //    {
    385 //        xor(S, block);
    386 //        multiplier.multiplyH(S);
    387 //    }
    388 
    389 //    private static void inc(byte[] block)
    390 //    {
    391 //        for (int i = 15; i >= 12; --i)
    392 //        {
    393 //            byte b = (byte)((block[i] + 1) & 0xff);
    394 //            block[i] = b;
    395 //
    396 //            if (b != 0)
    397 //            {
    398 //                break;
    399 //            }
    400 //        }
    401 //    }
    402 
    403     private static void xor(byte[] block, byte[] val)
    404     {
    405         for (int i = 15; i >= 0; --i)
    406         {
    407             block[i] ^= val[i];
    408         }
    409     }
    410 
    411     private static void packLength(long count, byte[] bs, int off)
    412     {
    413         Pack.intToBigEndian((int)(count >>> 32), bs, off);
    414         Pack.intToBigEndian((int)count, bs, off + 4);
    415     }
    416 }
    417