• 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.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 
GCMBlockCipher(BlockCipher c)47     public GCMBlockCipher(BlockCipher c)
48     {
49         this(c, null);
50     }
51 
GCMBlockCipher(BlockCipher c, GCMMultiplier m)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 
getUnderlyingCipher()70     public BlockCipher getUnderlyingCipher()
71     {
72         return cipher;
73     }
74 
getAlgorithmName()75     public String getAlgorithmName()
76     {
77         return cipher.getAlgorithmName() + "/GCM";
78     }
79 
init(boolean forEncryption, CipherParameters params)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 
getMac()164     public byte[] getMac()
165     {
166         return Arrays.clone(macBlock);
167     }
168 
getOutputSize(int len)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 
getUpdateOutputSize(int len)179     public int getUpdateOutputSize(int len)
180     {
181         return ((len + bufOff) / BLOCK_SIZE) * BLOCK_SIZE;
182     }
183 
processByte(byte in, byte[] out, int outOff)184     public int processByte(byte in, byte[] out, int outOff)
185         throws DataLengthException
186     {
187         return process(in, out, outOff);
188     }
189 
processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)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 
process(byte in, byte[] out, int outOff)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 
doFinal(byte[] out, int outOff)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 
reset()300     public void reset()
301     {
302         reset(true);
303     }
304 
reset( boolean clearMac)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 
gCTRBlock(byte[] buf, int bufCount, byte[] out, int outOff)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 
gHASH(byte[] b)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 
xor(byte[] block, byte[] val)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 
packLength(long count, byte[] bs, int off)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