| package org.bouncycastle.crypto.modes; |
| |
| import org.bouncycastle.crypto.BlockCipher; |
| import org.bouncycastle.crypto.CipherParameters; |
| import org.bouncycastle.crypto.DataLengthException; |
| import org.bouncycastle.crypto.InvalidCipherTextException; |
| import org.bouncycastle.crypto.Mac; |
| import org.bouncycastle.crypto.macs.CMac; |
| import org.bouncycastle.crypto.params.AEADParameters; |
| import org.bouncycastle.crypto.params.ParametersWithIV; |
| import org.bouncycastle.util.Arrays; |
| |
| /** |
| * A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and |
| * Efficiency - by M. Bellare, P. Rogaway, D. Wagner. |
| * |
| * http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf |
| * |
| * EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block |
| * cipher to encrypt and authenticate data. It's on-line (the length of a |
| * message isn't needed to begin processing it), has good performances, it's |
| * simple and provably secure (provided the underlying block cipher is secure). |
| * |
| * Of course, this implementations is NOT thread-safe. |
| */ |
| public class EAXBlockCipher |
| implements AEADBlockCipher |
| { |
| private static final byte nTAG = 0x0; |
| |
| private static final byte hTAG = 0x1; |
| |
| private static final byte cTAG = 0x2; |
| |
| private SICBlockCipher cipher; |
| |
| private boolean forEncryption; |
| |
| private int blockSize; |
| |
| private Mac mac; |
| |
| private byte[] nonceMac; |
| private byte[] associatedTextMac; |
| private byte[] macBlock; |
| |
| private int macSize; |
| private byte[] bufBlock; |
| private int bufOff; |
| |
| /** |
| * Constructor that accepts an instance of a block cipher engine. |
| * |
| * @param cipher the engine to use |
| */ |
| public EAXBlockCipher(BlockCipher cipher) |
| { |
| blockSize = cipher.getBlockSize(); |
| mac = new CMac(cipher); |
| macBlock = new byte[blockSize]; |
| bufBlock = new byte[blockSize * 2]; |
| associatedTextMac = new byte[mac.getMacSize()]; |
| nonceMac = new byte[mac.getMacSize()]; |
| this.cipher = new SICBlockCipher(cipher); |
| } |
| |
| public String getAlgorithmName() |
| { |
| return cipher.getUnderlyingCipher().getAlgorithmName() + "/EAX"; |
| } |
| |
| public BlockCipher getUnderlyingCipher() |
| { |
| return cipher.getUnderlyingCipher(); |
| } |
| |
| public int getBlockSize() |
| { |
| return cipher.getBlockSize(); |
| } |
| |
| public void init(boolean forEncryption, CipherParameters params) |
| throws IllegalArgumentException |
| { |
| this.forEncryption = forEncryption; |
| |
| byte[] nonce, associatedText; |
| CipherParameters keyParam; |
| |
| if (params instanceof AEADParameters) |
| { |
| AEADParameters param = (AEADParameters)params; |
| |
| nonce = param.getNonce(); |
| associatedText = param.getAssociatedText(); |
| macSize = param.getMacSize() / 8; |
| keyParam = param.getKey(); |
| } |
| else if (params instanceof ParametersWithIV) |
| { |
| ParametersWithIV param = (ParametersWithIV)params; |
| |
| nonce = param.getIV(); |
| associatedText = new byte[0]; |
| macSize = mac.getMacSize() / 2; |
| keyParam = param.getParameters(); |
| } |
| else |
| { |
| throw new IllegalArgumentException("invalid parameters passed to EAX"); |
| } |
| |
| byte[] tag = new byte[blockSize]; |
| |
| mac.init(keyParam); |
| tag[blockSize - 1] = hTAG; |
| mac.update(tag, 0, blockSize); |
| mac.update(associatedText, 0, associatedText.length); |
| mac.doFinal(associatedTextMac, 0); |
| |
| tag[blockSize - 1] = nTAG; |
| mac.update(tag, 0, blockSize); |
| mac.update(nonce, 0, nonce.length); |
| mac.doFinal(nonceMac, 0); |
| |
| tag[blockSize - 1] = cTAG; |
| mac.update(tag, 0, blockSize); |
| |
| cipher.init(true, new ParametersWithIV(keyParam, nonceMac)); |
| } |
| |
| private void calculateMac() |
| { |
| byte[] outC = new byte[blockSize]; |
| mac.doFinal(outC, 0); |
| |
| for (int i = 0; i < macBlock.length; i++) |
| { |
| macBlock[i] = (byte)(nonceMac[i] ^ associatedTextMac[i] ^ outC[i]); |
| } |
| } |
| |
| public void reset() |
| { |
| reset(true); |
| } |
| |
| private void reset( |
| boolean clearMac) |
| { |
| cipher.reset(); |
| mac.reset(); |
| |
| bufOff = 0; |
| Arrays.fill(bufBlock, (byte)0); |
| |
| if (clearMac) |
| { |
| Arrays.fill(macBlock, (byte)0); |
| } |
| |
| byte[] tag = new byte[blockSize]; |
| tag[blockSize - 1] = cTAG; |
| mac.update(tag, 0, blockSize); |
| } |
| |
| public int processByte(byte in, byte[] out, int outOff) |
| throws DataLengthException |
| { |
| return process(in, out, outOff); |
| } |
| |
| public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff) |
| throws DataLengthException |
| { |
| int resultLen = 0; |
| |
| for (int i = 0; i != len; i++) |
| { |
| resultLen += process(in[inOff + i], out, outOff + resultLen); |
| } |
| |
| return resultLen; |
| } |
| |
| public int doFinal(byte[] out, int outOff) |
| throws IllegalStateException, InvalidCipherTextException |
| { |
| int extra = bufOff; |
| byte[] tmp = new byte[bufBlock.length]; |
| |
| bufOff = 0; |
| |
| if (forEncryption) |
| { |
| cipher.processBlock(bufBlock, 0, tmp, 0); |
| cipher.processBlock(bufBlock, blockSize, tmp, blockSize); |
| |
| System.arraycopy(tmp, 0, out, outOff, extra); |
| |
| mac.update(tmp, 0, extra); |
| |
| calculateMac(); |
| |
| System.arraycopy(macBlock, 0, out, outOff + extra, macSize); |
| |
| reset(false); |
| |
| return extra + macSize; |
| } |
| else |
| { |
| if (extra > macSize) |
| { |
| mac.update(bufBlock, 0, extra - macSize); |
| |
| cipher.processBlock(bufBlock, 0, tmp, 0); |
| cipher.processBlock(bufBlock, blockSize, tmp, blockSize); |
| |
| System.arraycopy(tmp, 0, out, outOff, extra - macSize); |
| } |
| |
| calculateMac(); |
| |
| if (!verifyMac(bufBlock, extra - macSize)) |
| { |
| throw new InvalidCipherTextException("mac check in EAX failed"); |
| } |
| |
| reset(false); |
| |
| return extra - macSize; |
| } |
| } |
| |
| public byte[] getMac() |
| { |
| byte[] mac = new byte[macSize]; |
| |
| System.arraycopy(macBlock, 0, mac, 0, macSize); |
| |
| return mac; |
| } |
| |
| public int getUpdateOutputSize(int len) |
| { |
| return ((len + bufOff) / blockSize) * blockSize; |
| } |
| |
| public int getOutputSize(int len) |
| { |
| if (forEncryption) |
| { |
| return len + bufOff + macSize; |
| } |
| else |
| { |
| return len + bufOff - macSize; |
| } |
| } |
| |
| private int process(byte b, byte[] out, int outOff) |
| { |
| bufBlock[bufOff++] = b; |
| |
| if (bufOff == bufBlock.length) |
| { |
| int size; |
| |
| if (forEncryption) |
| { |
| size = cipher.processBlock(bufBlock, 0, out, outOff); |
| |
| mac.update(out, outOff, blockSize); |
| } |
| else |
| { |
| mac.update(bufBlock, 0, blockSize); |
| |
| size = cipher.processBlock(bufBlock, 0, out, outOff); |
| } |
| |
| bufOff = blockSize; |
| System.arraycopy(bufBlock, blockSize, bufBlock, 0, blockSize); |
| |
| return size; |
| } |
| |
| return 0; |
| } |
| |
| private boolean verifyMac(byte[] mac, int off) |
| { |
| for (int i = 0; i < macSize; i++) |
| { |
| if (macBlock[i] != mac[off + i]) |
| { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| } |