001: package org.bouncycastle.crypto.modes;
002:
003: import org.bouncycastle.crypto.BlockCipher;
004: import org.bouncycastle.crypto.CipherParameters;
005: import org.bouncycastle.crypto.DataLengthException;
006: import org.bouncycastle.crypto.InvalidCipherTextException;
007: import org.bouncycastle.crypto.Mac;
008: import org.bouncycastle.crypto.macs.CBCBlockCipherMac;
009: import org.bouncycastle.crypto.params.AEADParameters;
010: import org.bouncycastle.crypto.params.ParametersWithIV;
011: import org.bouncycastle.util.Arrays;
012:
013: import java.io.ByteArrayOutputStream;
014:
015: /**
016: * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
017: * NIST Special Publication 800-38C.
018: * <p>
019: * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
020: */
021: public class CCMBlockCipher implements AEADBlockCipher {
022: private BlockCipher cipher;
023: private int blockSize;
024: private boolean forEncryption;
025: private byte[] nonce;
026: private byte[] associatedText;
027: private int macSize;
028: private CipherParameters keyParam;
029: private byte[] macBlock;
030: private ByteArrayOutputStream data = new ByteArrayOutputStream();
031:
032: /**
033: * Basic constructor.
034: *
035: * @param c the block cipher to be used.
036: */
037: public CCMBlockCipher(BlockCipher c) {
038: this .cipher = c;
039: this .blockSize = c.getBlockSize();
040: this .macBlock = new byte[blockSize];
041:
042: if (blockSize != 16) {
043: throw new IllegalArgumentException(
044: "cipher required with a block size of 16.");
045: }
046: }
047:
048: /**
049: * return the underlying block cipher that we are wrapping.
050: *
051: * @return the underlying block cipher that we are wrapping.
052: */
053: public BlockCipher getUnderlyingCipher() {
054: return cipher;
055: }
056:
057: public void init(boolean forEncryption, CipherParameters params)
058: throws IllegalArgumentException {
059: this .forEncryption = forEncryption;
060:
061: if (params instanceof AEADParameters) {
062: AEADParameters param = (AEADParameters) params;
063:
064: nonce = param.getNonce();
065: associatedText = param.getAssociatedText();
066: macSize = param.getMacSize() / 8;
067: keyParam = param.getKey();
068: } else if (params instanceof ParametersWithIV) {
069: ParametersWithIV param = (ParametersWithIV) params;
070:
071: nonce = param.getIV();
072: associatedText = null;
073: macSize = macBlock.length / 2;
074: keyParam = param.getParameters();
075: } else {
076: throw new IllegalArgumentException(
077: "invalid parameters passed to CCM");
078: }
079: }
080:
081: public String getAlgorithmName() {
082: return cipher.getAlgorithmName() + "/CCM";
083: }
084:
085: public int processByte(byte in, byte[] out, int outOff)
086: throws DataLengthException, IllegalStateException {
087: data.write(in);
088:
089: return 0;
090: }
091:
092: public int processBytes(byte[] in, int inOff, int inLen,
093: byte[] out, int outOff) throws DataLengthException,
094: IllegalStateException {
095: data.write(in, inOff, inLen);
096:
097: return 0;
098: }
099:
100: public int doFinal(byte[] out, int outOff)
101: throws IllegalStateException, InvalidCipherTextException {
102: byte[] text = data.toByteArray();
103: byte[] enc = processPacket(text, 0, text.length);
104:
105: System.arraycopy(enc, 0, out, outOff, enc.length);
106:
107: reset();
108:
109: return enc.length;
110: }
111:
112: public void reset() {
113: cipher.reset();
114: data.reset();
115: }
116:
117: /**
118: * Returns a byte array containing the mac calculated as part of the
119: * last encrypt or decrypt operation.
120: *
121: * @return the last mac calculated.
122: */
123: public byte[] getMac() {
124: byte[] mac = new byte[macSize];
125:
126: System.arraycopy(macBlock, 0, mac, 0, mac.length);
127:
128: return mac;
129: }
130:
131: public int getUpdateOutputSize(int len) {
132: return 0;
133: }
134:
135: public int getOutputSize(int len) {
136: if (forEncryption) {
137: return data.size() + len + macSize;
138: } else {
139: return data.size() + len - macSize;
140: }
141: }
142:
143: public byte[] processPacket(byte[] in, int inOff, int inLen)
144: throws IllegalStateException, InvalidCipherTextException {
145: if (keyParam == null) {
146: throw new IllegalStateException("CCM cipher unitialized.");
147: }
148:
149: BlockCipher ctrCipher = new SICBlockCipher(cipher);
150: byte[] iv = new byte[blockSize];
151: byte[] out;
152:
153: iv[0] = (byte) (((15 - nonce.length) - 1) & 0x7);
154:
155: System.arraycopy(nonce, 0, iv, 1, nonce.length);
156:
157: ctrCipher.init(forEncryption,
158: new ParametersWithIV(keyParam, iv));
159:
160: if (forEncryption) {
161: int index = inOff;
162: int outOff = 0;
163:
164: out = new byte[inLen + macSize];
165:
166: calculateMac(in, inOff, inLen, macBlock);
167:
168: ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0
169:
170: while (index < inLen - blockSize) // S1...
171: {
172: ctrCipher.processBlock(in, index, out, outOff);
173: outOff += blockSize;
174: index += blockSize;
175: }
176:
177: byte[] block = new byte[blockSize];
178:
179: System.arraycopy(in, index, block, 0, inLen - index);
180:
181: ctrCipher.processBlock(block, 0, block, 0);
182:
183: System.arraycopy(block, 0, out, outOff, inLen - index);
184:
185: outOff += inLen - index;
186:
187: System.arraycopy(macBlock, 0, out, outOff, out.length
188: - outOff);
189: } else {
190: int index = inOff;
191: int outOff = 0;
192:
193: out = new byte[inLen - macSize];
194:
195: System.arraycopy(in, inOff + inLen - macSize, macBlock, 0,
196: macSize);
197:
198: ctrCipher.processBlock(macBlock, 0, macBlock, 0);
199:
200: for (int i = macSize; i != macBlock.length; i++) {
201: macBlock[i] = 0;
202: }
203:
204: while (outOff < out.length - blockSize) {
205: ctrCipher.processBlock(in, index, out, outOff);
206: outOff += blockSize;
207: index += blockSize;
208: }
209:
210: byte[] block = new byte[blockSize];
211:
212: System.arraycopy(in, index, block, 0, out.length - outOff);
213:
214: ctrCipher.processBlock(block, 0, block, 0);
215:
216: System
217: .arraycopy(block, 0, out, outOff, out.length
218: - outOff);
219:
220: byte[] calculatedMacBlock = new byte[blockSize];
221:
222: calculateMac(out, 0, out.length, calculatedMacBlock);
223:
224: if (!Arrays.areEqual(macBlock, calculatedMacBlock)) {
225: throw new InvalidCipherTextException(
226: "mac check in CCM failed");
227: }
228: }
229:
230: return out;
231: }
232:
233: private int calculateMac(byte[] data, int dataOff, int dataLen,
234: byte[] macBlock) {
235: Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8);
236:
237: cMac.init(keyParam);
238:
239: //
240: // build b0
241: //
242: byte[] b0 = new byte[16];
243:
244: if (hasAssociatedText()) {
245: b0[0] |= 0x40;
246: }
247:
248: b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3;
249:
250: b0[0] |= ((15 - nonce.length) - 1) & 0x7;
251:
252: System.arraycopy(nonce, 0, b0, 1, nonce.length);
253:
254: int q = dataLen;
255: int count = 1;
256: while (q > 0) {
257: b0[b0.length - count] = (byte) (q & 0xff);
258: q >>>= 8;
259: count++;
260: }
261:
262: cMac.update(b0, 0, b0.length);
263:
264: //
265: // process associated text
266: //
267: if (hasAssociatedText()) {
268: int extra;
269:
270: if (associatedText.length < ((1 << 16) - (1 << 8))) {
271: cMac.update((byte) (associatedText.length >> 8));
272: cMac.update((byte) associatedText.length);
273:
274: extra = 2;
275: } else // can't go any higher than 2^32
276: {
277: cMac.update((byte) 0xff);
278: cMac.update((byte) 0xfe);
279: cMac.update((byte) (associatedText.length >> 24));
280: cMac.update((byte) (associatedText.length >> 16));
281: cMac.update((byte) (associatedText.length >> 8));
282: cMac.update((byte) associatedText.length);
283:
284: extra = 6;
285: }
286:
287: cMac.update(associatedText, 0, associatedText.length);
288:
289: extra = (extra + associatedText.length) % 16;
290: if (extra != 0) {
291: for (int i = 0; i != 16 - extra; i++) {
292: cMac.update((byte) 0x00);
293: }
294: }
295: }
296:
297: //
298: // add the text
299: //
300: cMac.update(data, dataOff, dataLen);
301:
302: return cMac.doFinal(macBlock, 0);
303: }
304:
305: private boolean hasAssociatedText() {
306: return associatedText != null && associatedText.length != 0;
307: }
308: }
|