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