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