• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.bouncycastle.crypto.modes;
2 
3 import java.io.ByteArrayOutputStream;
4 
5 import org.bouncycastle.crypto.BlockCipher;
6 import org.bouncycastle.crypto.CipherParameters;
7 import org.bouncycastle.crypto.DataLengthException;
8 import org.bouncycastle.crypto.InvalidCipherTextException;
9 import org.bouncycastle.crypto.Mac;
10 import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
11 import org.bouncycastle.crypto.params.AEADParameters;
12 import org.bouncycastle.crypto.params.ParametersWithIV;
13 import org.bouncycastle.util.Arrays;
14 
15 /**
16  * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
17  * NIST Special Publication 800-38C.
18  * <p>
19  * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
20  */
21 public class CCMBlockCipher
22     implements AEADBlockCipher
23 {
24     private BlockCipher           cipher;
25     private int                   blockSize;
26     private boolean               forEncryption;
27     private byte[]                nonce;
28     private byte[]                associatedText;
29     private int                   macSize;
30     private CipherParameters      keyParam;
31     private byte[]                macBlock;
32     private ByteArrayOutputStream data = new ByteArrayOutputStream();
33 
34     /**
35      * Basic constructor.
36      *
37      * @param c the block cipher to be used.
38      */
CCMBlockCipher(BlockCipher c)39     public CCMBlockCipher(BlockCipher c)
40     {
41         this.cipher = c;
42         this.blockSize = c.getBlockSize();
43         this.macBlock = new byte[blockSize];
44 
45         if (blockSize != 16)
46         {
47             throw new IllegalArgumentException("cipher required with a block size of 16.");
48         }
49     }
50 
51     /**
52      * return the underlying block cipher that we are wrapping.
53      *
54      * @return the underlying block cipher that we are wrapping.
55      */
getUnderlyingCipher()56     public BlockCipher getUnderlyingCipher()
57     {
58         return cipher;
59     }
60 
61 
init(boolean forEncryption, CipherParameters params)62     public void init(boolean forEncryption, CipherParameters params)
63           throws IllegalArgumentException
64     {
65         this.forEncryption = forEncryption;
66 
67         if (params instanceof AEADParameters)
68         {
69             AEADParameters param = (AEADParameters)params;
70 
71             nonce = param.getNonce();
72             associatedText = param.getAssociatedText();
73             macSize = param.getMacSize() / 8;
74             keyParam = param.getKey();
75         }
76         else if (params instanceof ParametersWithIV)
77         {
78             ParametersWithIV param = (ParametersWithIV)params;
79 
80             nonce = param.getIV();
81             associatedText = null;
82             macSize = macBlock.length / 2;
83             keyParam = param.getParameters();
84         }
85         else
86         {
87             throw new IllegalArgumentException("invalid parameters passed to CCM");
88         }
89     }
90 
getAlgorithmName()91     public String getAlgorithmName()
92     {
93         return cipher.getAlgorithmName() + "/CCM";
94     }
95 
processByte(byte in, byte[] out, int outOff)96     public int processByte(byte in, byte[] out, int outOff)
97         throws DataLengthException, IllegalStateException
98     {
99         data.write(in);
100 
101         return 0;
102     }
103 
processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff)104     public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff)
105         throws DataLengthException, IllegalStateException
106     {
107         data.write(in, inOff, inLen);
108 
109         return 0;
110     }
111 
doFinal(byte[] out, int outOff)112     public int doFinal(byte[] out, int outOff)
113         throws IllegalStateException, InvalidCipherTextException
114     {
115         byte[] text = data.toByteArray();
116         byte[] enc = processPacket(text, 0, text.length);
117 
118         System.arraycopy(enc, 0, out, outOff, enc.length);
119 
120         reset();
121 
122         return enc.length;
123     }
124 
reset()125     public void reset()
126     {
127         cipher.reset();
128         data.reset();
129     }
130 
131     /**
132      * Returns a byte array containing the mac calculated as part of the
133      * last encrypt or decrypt operation.
134      *
135      * @return the last mac calculated.
136      */
getMac()137     public byte[] getMac()
138     {
139         byte[] mac = new byte[macSize];
140 
141         System.arraycopy(macBlock, 0, mac, 0, mac.length);
142 
143         return mac;
144     }
145 
getUpdateOutputSize(int len)146     public int getUpdateOutputSize(int len)
147     {
148         return 0;
149     }
150 
getOutputSize(int len)151     public int getOutputSize(int len)
152     {
153         if (forEncryption)
154         {
155             return data.size() + len + macSize;
156         }
157         else
158         {
159             return data.size() + len - macSize;
160         }
161     }
162 
processPacket(byte[] in, int inOff, int inLen)163     public byte[] processPacket(byte[] in, int inOff, int inLen)
164         throws IllegalStateException, InvalidCipherTextException
165     {
166         if (keyParam == null)
167         {
168             throw new IllegalStateException("CCM cipher unitialized.");
169         }
170 
171         BlockCipher ctrCipher = new SICBlockCipher(cipher);
172         byte[] iv = new byte[blockSize];
173         byte[] out;
174 
175         iv[0] = (byte)(((15 - nonce.length) - 1) & 0x7);
176 
177         System.arraycopy(nonce, 0, iv, 1, nonce.length);
178 
179         ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv));
180 
181         if (forEncryption)
182         {
183             int index = inOff;
184             int outOff = 0;
185 
186             out = new byte[inLen + macSize];
187 
188             calculateMac(in, inOff, inLen, macBlock);
189 
190             ctrCipher.processBlock(macBlock, 0, macBlock, 0);   // S0
191 
192             while (index < inLen - blockSize)                   // S1...
193             {
194                 ctrCipher.processBlock(in, index, out, outOff);
195                 outOff += blockSize;
196                 index += blockSize;
197             }
198 
199             byte[] block = new byte[blockSize];
200 
201             System.arraycopy(in, index, block, 0, inLen - index);
202 
203             ctrCipher.processBlock(block, 0, block, 0);
204 
205             System.arraycopy(block, 0, out, outOff, inLen - index);
206 
207             outOff += inLen - index;
208 
209             System.arraycopy(macBlock, 0, out, outOff, out.length - outOff);
210         }
211         else
212         {
213             int index = inOff;
214             int outOff = 0;
215 
216             out = new byte[inLen - macSize];
217 
218             System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize);
219 
220             ctrCipher.processBlock(macBlock, 0, macBlock, 0);
221 
222             for (int i = macSize; i != macBlock.length; i++)
223             {
224                 macBlock[i] = 0;
225             }
226 
227             while (outOff < out.length - blockSize)
228             {
229                 ctrCipher.processBlock(in, index, out, outOff);
230                 outOff += blockSize;
231                 index += blockSize;
232             }
233 
234             byte[] block = new byte[blockSize];
235 
236             System.arraycopy(in, index, block, 0, out.length - outOff);
237 
238             ctrCipher.processBlock(block, 0, block, 0);
239 
240             System.arraycopy(block, 0, out, outOff, out.length - outOff);
241 
242             byte[] calculatedMacBlock = new byte[blockSize];
243 
244             calculateMac(out, 0, out.length, calculatedMacBlock);
245 
246             if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock))
247             {
248                 throw new InvalidCipherTextException("mac check in CCM failed");
249             }
250         }
251 
252         return out;
253     }
254 
calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)255     private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
256     {
257         Mac    cMac = new CBCBlockCipherMac(cipher, macSize * 8);
258 
259         cMac.init(keyParam);
260 
261         //
262         // build b0
263         //
264         byte[] b0 = new byte[16];
265 
266         if (hasAssociatedText())
267         {
268             b0[0] |= 0x40;
269         }
270 
271         b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3;
272 
273         b0[0] |= ((15 - nonce.length) - 1) & 0x7;
274 
275         System.arraycopy(nonce, 0, b0, 1, nonce.length);
276 
277         int q = dataLen;
278         int count = 1;
279         while (q > 0)
280         {
281             b0[b0.length - count] = (byte)(q & 0xff);
282             q >>>= 8;
283             count++;
284         }
285 
286         cMac.update(b0, 0, b0.length);
287 
288         //
289         // process associated text
290         //
291         if (hasAssociatedText())
292         {
293             int extra;
294 
295             if (associatedText.length < ((1 << 16) - (1 << 8)))
296             {
297                 cMac.update((byte)(associatedText.length >> 8));
298                 cMac.update((byte)associatedText.length);
299 
300                 extra = 2;
301             }
302             else // can't go any higher than 2^32
303             {
304                 cMac.update((byte)0xff);
305                 cMac.update((byte)0xfe);
306                 cMac.update((byte)(associatedText.length >> 24));
307                 cMac.update((byte)(associatedText.length >> 16));
308                 cMac.update((byte)(associatedText.length >> 8));
309                 cMac.update((byte)associatedText.length);
310 
311                 extra = 6;
312             }
313 
314             cMac.update(associatedText, 0, associatedText.length);
315 
316             extra = (extra + associatedText.length) % 16;
317             if (extra != 0)
318             {
319                 for (int i = 0; i != 16 - extra; i++)
320                 {
321                     cMac.update((byte)0x00);
322                 }
323             }
324         }
325 
326         //
327         // add the text
328         //
329         cMac.update(data, dataOff, dataLen);
330 
331         return cMac.doFinal(macBlock, 0);
332     }
333 
hasAssociatedText()334     private boolean hasAssociatedText()
335     {
336         return associatedText != null && associatedText.length != 0;
337     }
338 }
339