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.OutputLengthException;
      8 import org.bouncycastle.crypto.modes.gcm.GCMExponentiator;
      9 import org.bouncycastle.crypto.modes.gcm.GCMMultiplier;
     10 import org.bouncycastle.crypto.modes.gcm.GCMUtil;
     11 import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator;
     12 import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier;
     13 import org.bouncycastle.crypto.params.AEADParameters;
     14 import org.bouncycastle.crypto.params.KeyParameter;
     15 import org.bouncycastle.crypto.params.ParametersWithIV;
     16 import org.bouncycastle.util.Arrays;
     17 import org.bouncycastle.util.Pack;
     18 
     19 /**
     20  * Implements the Galois/Counter mode (GCM) detailed in
     21  * NIST Special Publication 800-38D.
     22  */
     23 public class GCMBlockCipher
     24     implements AEADBlockCipher
     25 {
     26     private static final int BLOCK_SIZE = 16;
     27     // BEGIN android-added
     28     // 2^36-32 : limitation imposed by NIST GCM as otherwise the counter is wrapped and it can leak
     29     // plaintext and authentication key
     30     private static final long MAX_INPUT_SIZE = 68719476704L;
     31     // END android-added
     32 
     33     // not final due to a compiler bug
     34     private BlockCipher   cipher;
     35     private GCMMultiplier multiplier;
     36     private GCMExponentiator exp;
     37 
     38     // These fields are set by init and not modified by processing
     39     private boolean             forEncryption;
     40     private int                 macSize;
     41     private byte[]              nonce;
     42     private byte[]              initialAssociatedText;
     43     private byte[]              H;
     44     private byte[]              J0;
     45 
     46     // These fields are modified during processing
     47     private byte[]      bufBlock;
     48     private byte[]      macBlock;
     49     private byte[]      S, S_at, S_atPre;
     50     private byte[]      counter;
     51     private int         bufOff;
     52     private long        totalLength;
     53     private byte[]      atBlock;
     54     private int         atBlockPos;
     55     private long        atLength;
     56     private long        atLengthPre;
     57 
     58     public GCMBlockCipher(BlockCipher c)
     59     {
     60         this(c, null);
     61     }
     62 
     63     public GCMBlockCipher(BlockCipher c, GCMMultiplier m)
     64     {
     65         if (c.getBlockSize() != BLOCK_SIZE)
     66         {
     67             throw new IllegalArgumentException(
     68                 "cipher required with a block size of " + BLOCK_SIZE + ".");
     69         }
     70 
     71         if (m == null)
     72         {
     73             // TODO Consider a static property specifying default multiplier
     74             m = new Tables8kGCMMultiplier();
     75         }
     76 
     77         this.cipher = c;
     78         this.multiplier = m;
     79     }
     80 
     81     public BlockCipher getUnderlyingCipher()
     82     {
     83         return cipher;
     84     }
     85 
     86     public String getAlgorithmName()
     87     {
     88         return cipher.getAlgorithmName() + "/GCM";
     89     }
     90 
     91     /**
     92      * NOTE: MAC sizes from 32 bits to 128 bits (must be a multiple of 8) are supported. The default is 128 bits.
     93      * Sizes less than 96 are not recommended, but are supported for specialized applications.
     94      */
     95     public void init(boolean forEncryption, CipherParameters params)
     96         throws IllegalArgumentException
     97     {
     98         this.forEncryption = forEncryption;
     99         this.macBlock = null;
    100 
    101         KeyParameter keyParam;
    102 
    103         if (params instanceof AEADParameters)
    104         {
    105             AEADParameters param = (AEADParameters)params;
    106 
    107             nonce = param.getNonce();
    108             initialAssociatedText = param.getAssociatedText();
    109 
    110             int macSizeBits = param.getMacSize();
    111             if (macSizeBits < 32 || macSizeBits > 128 || macSizeBits % 8 != 0)
    112             {
    113                 throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits);
    114             }
    115 
    116             macSize = macSizeBits / 8;
    117             keyParam = param.getKey();
    118         }
    119         else if (params instanceof ParametersWithIV)
    120         {
    121             ParametersWithIV param = (ParametersWithIV)params;
    122 
    123             nonce = param.getIV();
    124             initialAssociatedText  = null;
    125             macSize = 16;
    126             keyParam = (KeyParameter)param.getParameters();
    127         }
    128         else
    129         {
    130             throw new IllegalArgumentException("invalid parameters passed to GCM");
    131         }
    132 
    133         int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize);
    134         this.bufBlock = new byte[bufLength];
    135 
    136         if (nonce == null || nonce.length < 1)
    137         {
    138             throw new IllegalArgumentException("IV must be at least 1 byte");
    139         }
    140 
    141         // TODO Restrict macSize to 16 if nonce length not 12?
    142 
    143         // Cipher always used in forward mode
    144         // if keyParam is null we're reusing the last key.
    145         if (keyParam != null)
    146         {
    147             cipher.init(true, keyParam);
    148 
    149             this.H = new byte[BLOCK_SIZE];
    150             cipher.processBlock(H, 0, H, 0);
    151 
    152             // GCMMultiplier tables don't change unless the key changes (and are expensive to init)
    153             multiplier.init(H);
    154             exp = null;
    155         }
    156         else if (this.H == null)
    157         {
    158             throw new IllegalArgumentException("Key must be specified in initial init");
    159         }
    160 
    161         this.J0 = new byte[BLOCK_SIZE];
    162 
    163         if (nonce.length == 12)
    164         {
    165             System.arraycopy(nonce, 0, J0, 0, nonce.length);
    166             this.J0[BLOCK_SIZE - 1] = 0x01;
    167         }
    168         else
    169         {
    170             gHASH(J0, nonce, nonce.length);
    171             byte[] X = new byte[BLOCK_SIZE];
    172             Pack.longToBigEndian((long)nonce.length * 8, X, 8);
    173             gHASHBlock(J0, X);
    174         }
    175 
    176         this.S = new byte[BLOCK_SIZE];
    177         this.S_at = new byte[BLOCK_SIZE];
    178         this.S_atPre = new byte[BLOCK_SIZE];
    179         this.atBlock = new byte[BLOCK_SIZE];
    180         this.atBlockPos = 0;
    181         this.atLength = 0;
    182         this.atLengthPre = 0;
    183         this.counter = Arrays.clone(J0);
    184         this.bufOff = 0;
    185         this.totalLength = 0;
    186 
    187         if (initialAssociatedText != null)
    188         {
    189             processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
    190         }
    191     }
    192 
    193     public byte[] getMac()
    194     {
    195         return Arrays.clone(macBlock);
    196     }
    197 
    198     public int getOutputSize(int len)
    199     {
    200         int totalData = len + bufOff;
    201 
    202         if (forEncryption)
    203         {
    204             return totalData + macSize;
    205         }
    206 
    207         return totalData < macSize ? 0 : totalData - macSize;
    208     }
    209 
    210     // BEGIN android-added
    211     /** Helper used to ensure that {@link #MAX_INPUT_SIZE} is not exceeded. */
    212     private long getTotalInputSizeAfterNewInput(int newInputLen)
    213     {
    214         return totalLength + newInputLen + bufOff;
    215     }
    216     // END android-added
    217 
    218     public int getUpdateOutputSize(int len)
    219     {
    220         int totalData = len + bufOff;
    221         if (!forEncryption)
    222         {
    223             if (totalData < macSize)
    224             {
    225                 return 0;
    226             }
    227             totalData -= macSize;
    228         }
    229         return totalData - totalData % BLOCK_SIZE;
    230     }
    231 
    232     public void processAADByte(byte in)
    233     {
    234         // BEGIN android-added
    235         if (getTotalInputSizeAfterNewInput(1) > MAX_INPUT_SIZE) {
    236             throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes");
    237         }
    238         // END android-added
    239         atBlock[atBlockPos] = in;
    240         if (++atBlockPos == BLOCK_SIZE)
    241         {
    242             // Hash each block as it fills
    243             gHASHBlock(S_at, atBlock);
    244             atBlockPos = 0;
    245             atLength += BLOCK_SIZE;
    246         }
    247     }
    248 
    249     public void processAADBytes(byte[] in, int inOff, int len)
    250     {
    251         // BEGIN android-added
    252         if (getTotalInputSizeAfterNewInput(len) > MAX_INPUT_SIZE) {
    253             throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes");
    254         }
    255         // END android-added
    256         for (int i = 0; i < len; ++i)
    257         {
    258             atBlock[atBlockPos] = in[inOff + i];
    259             if (++atBlockPos == BLOCK_SIZE)
    260             {
    261                 // Hash each block as it fills
    262                 gHASHBlock(S_at, atBlock);
    263                 atBlockPos = 0;
    264                 atLength += BLOCK_SIZE;
    265             }
    266         }
    267     }
    268 
    269     private void initCipher()
    270     {
    271         if (atLength > 0)
    272         {
    273             System.arraycopy(S_at, 0, S_atPre, 0, BLOCK_SIZE);
    274             atLengthPre = atLength;
    275         }
    276 
    277         // Finish hash for partial AAD block
    278         if (atBlockPos > 0)
    279         {
    280             gHASHPartial(S_atPre, atBlock, 0, atBlockPos);
    281             atLengthPre += atBlockPos;
    282         }
    283 
    284         if (atLengthPre > 0)
    285         {
    286             System.arraycopy(S_atPre, 0, S, 0, BLOCK_SIZE);
    287         }
    288     }
    289 
    290     public int processByte(byte in, byte[] out, int outOff)
    291         throws DataLengthException
    292     {
    293         // BEGIN android-added
    294         if (getTotalInputSizeAfterNewInput(1) > MAX_INPUT_SIZE) {
    295             throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes");
    296         }
    297         // END android-added
    298         bufBlock[bufOff] = in;
    299         if (++bufOff == bufBlock.length)
    300         {
    301             outputBlock(out, outOff);
    302             return BLOCK_SIZE;
    303         }
    304         return 0;
    305     }
    306 
    307     public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
    308         throws DataLengthException
    309     {
    310         // BEGIN android-added
    311         if (getTotalInputSizeAfterNewInput(len) > MAX_INPUT_SIZE) {
    312             throw new DataLengthException("Input exceeded " + MAX_INPUT_SIZE + " bytes");
    313         }
    314         // END android-added
    315         if (in.length < (inOff + len))
    316         {
    317             throw new DataLengthException("Input buffer too short");
    318         }
    319         int resultLen = 0;
    320 
    321         for (int i = 0; i < len; ++i)
    322         {
    323             bufBlock[bufOff] = in[inOff + i];
    324             if (++bufOff == bufBlock.length)
    325             {
    326                 outputBlock(out, outOff + resultLen);
    327                 resultLen += BLOCK_SIZE;
    328             }
    329         }
    330 
    331         return resultLen;
    332     }
    333 
    334     private void outputBlock(byte[] output, int offset)
    335     {
    336         if (output.length < (offset + BLOCK_SIZE))
    337         {
    338             throw new OutputLengthException("Output buffer too short");
    339         }
    340         if (totalLength == 0)
    341         {
    342             initCipher();
    343         }
    344         gCTRBlock(bufBlock, output, offset);
    345         if (forEncryption)
    346         {
    347             bufOff = 0;
    348         }
    349         else
    350         {
    351             System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
    352             bufOff = macSize;
    353         }
    354     }
    355 
    356     public int doFinal(byte[] out, int outOff)
    357         throws IllegalStateException, InvalidCipherTextException
    358     {
    359         if (totalLength == 0)
    360         {
    361             initCipher();
    362         }
    363 
    364         int extra = bufOff;
    365 
    366         if (forEncryption)
    367         {
    368             if (out.length < (outOff + extra + macSize))
    369             {
    370                 throw new OutputLengthException("Output buffer too short");
    371             }
    372         }
    373         else
    374         {
    375             if (extra < macSize)
    376             {
    377                 throw new InvalidCipherTextException("data too short");
    378             }
    379             extra -= macSize;
    380 
    381             if (out.length < (outOff + extra))
    382             {
    383                 throw new OutputLengthException("Output buffer too short");
    384             }
    385         }
    386 
    387         if (extra > 0)
    388         {
    389             gCTRPartial(bufBlock, 0, extra, out, outOff);
    390         }
    391 
    392         atLength += atBlockPos;
    393 
    394         if (atLength > atLengthPre)
    395         {
    396             /*
    397              *  Some AAD was sent after the cipher started. We determine the difference b/w the hash value
    398              *  we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at).
    399              *  Then we carry this difference forward by multiplying by H^c, where c is the number of (full or
    400              *  partial) cipher-text blocks produced, and adjust the current hash.
    401              */
    402 
    403             // Finish hash for partial AAD block
    404             if (atBlockPos > 0)
    405             {
    406                 gHASHPartial(S_at, atBlock, 0, atBlockPos);
    407             }
    408 
    409             // Find the difference between the AAD hashes
    410             if (atLengthPre > 0)
    411             {
    412                 GCMUtil.xor(S_at, S_atPre);
    413             }
    414 
    415             // Number of cipher-text blocks produced
    416             long c = ((totalLength * 8) + 127) >>> 7;
    417 
    418             // Calculate the adjustment factor
    419             byte[] H_c = new byte[16];
    420             if (exp == null)
    421             {
    422                 exp = new Tables1kGCMExponentiator();
    423                 exp.init(H);
    424             }
    425             exp.exponentiateX(c, H_c);
    426 
    427             // Carry the difference forward
    428             GCMUtil.multiply(S_at, H_c);
    429 
    430             // Adjust the current hash
    431             GCMUtil.xor(S, S_at);
    432         }
    433 
    434         // Final gHASH
    435         byte[] X = new byte[BLOCK_SIZE];
    436         Pack.longToBigEndian(atLength * 8, X, 0);
    437         Pack.longToBigEndian(totalLength * 8, X, 8);
    438 
    439         gHASHBlock(S, X);
    440 
    441         // T = MSBt(GCTRk(J0,S))
    442         byte[] tag = new byte[BLOCK_SIZE];
    443         cipher.processBlock(J0, 0, tag, 0);
    444         GCMUtil.xor(tag, S);
    445 
    446         int resultLen = extra;
    447 
    448         // We place into macBlock our calculated value for T
    449         this.macBlock = new byte[macSize];
    450         System.arraycopy(tag, 0, macBlock, 0, macSize);
    451 
    452         if (forEncryption)
    453         {
    454             // Append T to the message
    455             System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize);
    456             resultLen += macSize;
    457         }
    458         else
    459         {
    460             // Retrieve the T value from the message and compare to calculated one
    461             byte[] msgMac = new byte[macSize];
    462             System.arraycopy(bufBlock, extra, msgMac, 0, macSize);
    463             if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac))
    464             {
    465                 throw new InvalidCipherTextException("mac check in GCM failed");
    466             }
    467         }
    468 
    469         reset(false);
    470 
    471         return resultLen;
    472     }
    473 
    474     public void reset()
    475     {
    476         reset(true);
    477     }
    478 
    479     private void reset(
    480         boolean clearMac)
    481     {
    482         cipher.reset();
    483 
    484         S = new byte[BLOCK_SIZE];
    485         S_at = new byte[BLOCK_SIZE];
    486         S_atPre = new byte[BLOCK_SIZE];
    487         atBlock = new byte[BLOCK_SIZE];
    488         atBlockPos = 0;
    489         atLength = 0;
    490         atLengthPre = 0;
    491         counter = Arrays.clone(J0);
    492         bufOff = 0;
    493         totalLength = 0;
    494 
    495         if (bufBlock != null)
    496         {
    497             Arrays.fill(bufBlock, (byte)0);
    498         }
    499 
    500         if (clearMac)
    501         {
    502             macBlock = null;
    503         }
    504 
    505         if (initialAssociatedText != null)
    506         {
    507             processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
    508         }
    509     }
    510 
    511     private void gCTRBlock(byte[] block, byte[] out, int outOff)
    512     {
    513         byte[] tmp = getNextCounterBlock();
    514 
    515         GCMUtil.xor(tmp, block);
    516         System.arraycopy(tmp, 0, out, outOff, BLOCK_SIZE);
    517 
    518         gHASHBlock(S, forEncryption ? tmp : block);
    519 
    520         totalLength += BLOCK_SIZE;
    521     }
    522 
    523     private void gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff)
    524     {
    525         byte[] tmp = getNextCounterBlock();
    526 
    527         GCMUtil.xor(tmp, buf, off, len);
    528         System.arraycopy(tmp, 0, out, outOff, len);
    529 
    530         gHASHPartial(S, forEncryption ? tmp : buf, 0, len);
    531 
    532         totalLength += len;
    533     }
    534 
    535     private void gHASH(byte[] Y, byte[] b, int len)
    536     {
    537         for (int pos = 0; pos < len; pos += BLOCK_SIZE)
    538         {
    539             int num = Math.min(len - pos, BLOCK_SIZE);
    540             gHASHPartial(Y, b, pos, num);
    541         }
    542     }
    543 
    544     private void gHASHBlock(byte[] Y, byte[] b)
    545     {
    546         GCMUtil.xor(Y, b);
    547         multiplier.multiplyH(Y);
    548     }
    549 
    550     private void gHASHPartial(byte[] Y, byte[] b, int off, int len)
    551     {
    552         GCMUtil.xor(Y, b, off, len);
    553         multiplier.multiplyH(Y);
    554     }
    555 
    556     private byte[] getNextCounterBlock()
    557     {
    558         int c = 1;
    559         c += counter[15] & 0xFF; counter[15] = (byte)c; c >>>= 8;
    560         c += counter[14] & 0xFF; counter[14] = (byte)c; c >>>= 8;
    561         c += counter[13] & 0xFF; counter[13] = (byte)c; c >>>= 8;
    562         c += counter[12] & 0xFF; counter[12] = (byte)c;
    563 
    564         byte[] tmp = new byte[BLOCK_SIZE];
    565         // TODO Sure would be nice if ciphers could operate on int[]
    566         cipher.processBlock(counter, 0, tmp, 0);
    567         return tmp;
    568     }
    569 }
    570