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