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