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.Mac;
8 import org.bouncycastle.crypto.OutputLengthException;
9 import org.bouncycastle.crypto.macs.CMac;
10 import org.bouncycastle.crypto.params.AEADParameters;
11 import org.bouncycastle.crypto.params.ParametersWithIV;
12 import org.bouncycastle.util.Arrays;
13 
14 /**
15  * A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and
16  * Efficiency - by M. Bellare, P. Rogaway, D. Wagner.
17  *
18  * https://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf
19  *
20  * EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block
21  * cipher to encrypt and authenticate data. It's on-line (the length of a
22  * message isn't needed to begin processing it), has good performances, it's
23  * simple and provably secure (provided the underlying block cipher is secure).
24  *
25  * Of course, this implementations is NOT thread-safe.
26  */
27 public class EAXBlockCipher
28     implements AEADBlockCipher
29 {
30     private static final byte nTAG = 0x0;
31 
32     private static final byte hTAG = 0x1;
33 
34     private static final byte cTAG = 0x2;
35 
36     private SICBlockCipher cipher;
37 
38     private boolean forEncryption;
39 
40     private int blockSize;
41 
42     private Mac mac;
43 
44     private byte[] nonceMac;
45     private byte[] associatedTextMac;
46     private byte[] macBlock;
47 
48     private int macSize;
49     private byte[] bufBlock;
50     private int bufOff;
51 
52     private boolean cipherInitialized;
53     private byte[] initialAssociatedText;
54 
55     /**
56      * Constructor that accepts an instance of a block cipher engine.
57      *
58      * @param cipher the engine to use
59      */
EAXBlockCipher(BlockCipher cipher)60     public EAXBlockCipher(BlockCipher cipher)
61     {
62         blockSize = cipher.getBlockSize();
63         mac = new CMac(cipher);
64         macBlock = new byte[blockSize];
65         associatedTextMac = new byte[mac.getMacSize()];
66         nonceMac = new byte[mac.getMacSize()];
67         this.cipher = new SICBlockCipher(cipher);
68     }
69 
getAlgorithmName()70     public String getAlgorithmName()
71     {
72         return cipher.getUnderlyingCipher().getAlgorithmName() + "/EAX";
73     }
74 
getUnderlyingCipher()75     public BlockCipher getUnderlyingCipher()
76     {
77         return cipher.getUnderlyingCipher();
78     }
79 
getBlockSize()80     public int getBlockSize()
81     {
82         return cipher.getBlockSize();
83     }
84 
init(boolean forEncryption, CipherParameters params)85     public void init(boolean forEncryption, CipherParameters params)
86         throws IllegalArgumentException
87     {
88         this.forEncryption = forEncryption;
89 
90         byte[] nonce;
91         CipherParameters keyParam;
92 
93         if (params instanceof AEADParameters)
94         {
95             AEADParameters param = (AEADParameters)params;
96 
97             nonce = param.getNonce();
98             initialAssociatedText = param.getAssociatedText();
99             macSize = param.getMacSize() / 8;
100             keyParam = param.getKey();
101         }
102         else if (params instanceof ParametersWithIV)
103         {
104             ParametersWithIV param = (ParametersWithIV)params;
105 
106             nonce = param.getIV();
107             initialAssociatedText = null;
108             macSize = mac.getMacSize() / 2;
109             keyParam = param.getParameters();
110         }
111         else
112         {
113             throw new IllegalArgumentException("invalid parameters passed to EAX");
114         }
115 
116         bufBlock = new byte[forEncryption ? blockSize : (blockSize + macSize)];
117 
118         byte[] tag = new byte[blockSize];
119 
120         // Key reuse implemented in CBC mode of underlying CMac
121         mac.init(keyParam);
122 
123         tag[blockSize - 1] = nTAG;
124         mac.update(tag, 0, blockSize);
125         mac.update(nonce, 0, nonce.length);
126         mac.doFinal(nonceMac, 0);
127 
128         // Same BlockCipher underlies this and the mac, so reuse last key on cipher
129         cipher.init(true, new ParametersWithIV(null, nonceMac));
130 
131         reset();
132     }
133 
initCipher()134     private void initCipher()
135     {
136         if (cipherInitialized)
137         {
138             return;
139         }
140 
141         cipherInitialized = true;
142 
143         mac.doFinal(associatedTextMac, 0);
144 
145         byte[] tag = new byte[blockSize];
146         tag[blockSize - 1] = cTAG;
147         mac.update(tag, 0, blockSize);
148     }
149 
calculateMac()150     private void calculateMac()
151     {
152         byte[] outC = new byte[blockSize];
153         mac.doFinal(outC, 0);
154 
155         for (int i = 0; i < macBlock.length; i++)
156         {
157             macBlock[i] = (byte)(nonceMac[i] ^ associatedTextMac[i] ^ outC[i]);
158         }
159     }
160 
reset()161     public void reset()
162     {
163         reset(true);
164     }
165 
reset( boolean clearMac)166     private void reset(
167         boolean clearMac)
168     {
169         cipher.reset(); // TODO Redundant since the mac will reset it?
170         mac.reset();
171 
172         bufOff = 0;
173         Arrays.fill(bufBlock, (byte)0);
174 
175         if (clearMac)
176         {
177             Arrays.fill(macBlock, (byte)0);
178         }
179 
180         byte[] tag = new byte[blockSize];
181         tag[blockSize - 1] = hTAG;
182         mac.update(tag, 0, blockSize);
183 
184         cipherInitialized = false;
185 
186         if (initialAssociatedText != null)
187         {
188            processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
189         }
190     }
191 
processAADByte(byte in)192     public void processAADByte(byte in)
193     {
194         if (cipherInitialized)
195         {
196             throw new IllegalStateException("AAD data cannot be added after encryption/decryption processing has begun.");
197         }
198         mac.update(in);
199     }
200 
processAADBytes(byte[] in, int inOff, int len)201     public void processAADBytes(byte[] in, int inOff, int len)
202     {
203         if (cipherInitialized)
204         {
205             throw new IllegalStateException("AAD data cannot be added after encryption/decryption processing has begun.");
206         }
207         mac.update(in, inOff, len);
208     }
209 
processByte(byte in, byte[] out, int outOff)210     public int processByte(byte in, byte[] out, int outOff)
211         throws DataLengthException
212     {
213         initCipher();
214 
215         return process(in, out, outOff);
216     }
217 
processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)218     public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
219         throws DataLengthException
220     {
221         initCipher();
222 
223         if (in.length < (inOff + len))
224         {
225             throw new DataLengthException("Input buffer too short");
226         }
227 
228         int resultLen = 0;
229 
230         for (int i = 0; i != len; i++)
231         {
232             resultLen += process(in[inOff + i], out, outOff + resultLen);
233         }
234 
235         return resultLen;
236     }
237 
doFinal(byte[] out, int outOff)238     public int doFinal(byte[] out, int outOff)
239         throws IllegalStateException, InvalidCipherTextException
240     {
241         initCipher();
242 
243         int extra = bufOff;
244         byte[] tmp = new byte[bufBlock.length];
245 
246         bufOff = 0;
247 
248         if (forEncryption)
249         {
250             if (out.length < (outOff + extra + macSize))
251             {
252                 throw new OutputLengthException("Output buffer too short");
253             }
254             cipher.processBlock(bufBlock, 0, tmp, 0);
255 
256             System.arraycopy(tmp, 0, out, outOff, extra);
257 
258             mac.update(tmp, 0, extra);
259 
260             calculateMac();
261 
262             System.arraycopy(macBlock, 0, out, outOff + extra, macSize);
263 
264             reset(false);
265 
266             return extra + macSize;
267         }
268         else
269         {
270             if (extra < macSize)
271             {
272                 throw new InvalidCipherTextException("data too short");
273             }
274             if (out.length < (outOff + extra - macSize))
275             {
276                 throw new OutputLengthException("Output buffer too short");
277             }
278             if (extra > macSize)
279             {
280                 mac.update(bufBlock, 0, extra - macSize);
281 
282                 cipher.processBlock(bufBlock, 0, tmp, 0);
283 
284                 System.arraycopy(tmp, 0, out, outOff, extra - macSize);
285             }
286 
287             calculateMac();
288 
289             if (!verifyMac(bufBlock, extra - macSize))
290             {
291                 throw new InvalidCipherTextException("mac check in EAX failed");
292             }
293 
294             reset(false);
295 
296             return extra - macSize;
297         }
298     }
299 
getMac()300     public byte[] getMac()
301     {
302         byte[] mac = new byte[macSize];
303 
304         System.arraycopy(macBlock, 0, mac, 0, macSize);
305 
306         return mac;
307     }
308 
getUpdateOutputSize(int len)309     public int getUpdateOutputSize(int len)
310     {
311         int totalData = len + bufOff;
312         if (!forEncryption)
313         {
314             if (totalData < macSize)
315             {
316                 return 0;
317             }
318             totalData -= macSize;
319         }
320         return totalData - totalData % blockSize;
321     }
322 
getOutputSize(int len)323     public int getOutputSize(int len)
324     {
325         int totalData = len + bufOff;
326 
327         if (forEncryption)
328         {
329             return totalData + macSize;
330         }
331 
332         return totalData < macSize ? 0 : totalData - macSize;
333     }
334 
process(byte b, byte[] out, int outOff)335     private int process(byte b, byte[] out, int outOff)
336     {
337         bufBlock[bufOff++] = b;
338 
339         if (bufOff == bufBlock.length)
340         {
341             if (out.length < (outOff + blockSize))
342             {
343                 throw new OutputLengthException("Output buffer is too short");
344             }
345             // TODO Could move the processByte(s) calls to here
346 //            initCipher();
347 
348             int size;
349 
350             if (forEncryption)
351             {
352                 size = cipher.processBlock(bufBlock, 0, out, outOff);
353 
354                 mac.update(out, outOff, blockSize);
355             }
356             else
357             {
358                 mac.update(bufBlock, 0, blockSize);
359 
360                 size = cipher.processBlock(bufBlock, 0, out, outOff);
361             }
362 
363             bufOff = 0;
364             if (!forEncryption)
365             {
366                 System.arraycopy(bufBlock, blockSize, bufBlock, 0, macSize);
367                 bufOff = macSize;
368             }
369 
370             return size;
371         }
372 
373         return 0;
374     }
375 
verifyMac(byte[] mac, int off)376     private boolean verifyMac(byte[] mac, int off)
377     {
378         int nonEqual = 0;
379 
380         for (int i = 0; i < macSize; i++)
381         {
382             nonEqual |= (macBlock[i] ^ mac[off + i]);
383         }
384 
385         return nonEqual == 0;
386     }
387 }
388