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.CMac;
009: import org.bouncycastle.crypto.params.AEADParameters;
010: import org.bouncycastle.crypto.params.ParametersWithIV;
011:
012: /**
013: * A Two-Pass Authenticated-Encryption Scheme Optimized for Simplicity and
014: * Efficiency - by M. Bellare, P. Rogaway, D. Wagner.
015: *
016: * http://www.cs.ucdavis.edu/~rogaway/papers/eax.pdf
017: *
018: * EAX is an AEAD scheme based on CTR and OMAC1/CMAC, that uses a single block
019: * cipher to encrypt and authenticate data. It's on-line (the length of a
020: * message isn't needed to begin processing it), has good performances, it's
021: * simple and provably secure (provided the underlying block cipher is secure).
022: *
023: * Of course, this implementations is NOT thread-safe.
024: */
025: public class EAXBlockCipher implements AEADBlockCipher {
026: private static final byte nTAG = 0x0;
027:
028: private static final byte hTAG = 0x1;
029:
030: private static final byte cTAG = 0x2;
031:
032: private SICBlockCipher cipher;
033:
034: private boolean forEncryption;
035:
036: private int blockSize;
037:
038: private Mac mac;
039:
040: private byte[] nonceMac;
041: private byte[] associatedTextMac;
042: private byte[] macBlock;
043:
044: private int macSize;
045: private byte[] bufBlock;
046: private int bufOff;
047:
048: /**
049: * Constructor that accepts an instance of a block cipher engine.
050: *
051: * @param cipher the engine to use
052: */
053: public EAXBlockCipher(BlockCipher cipher) {
054: blockSize = cipher.getBlockSize();
055: mac = new CMac(cipher);
056: macBlock = new byte[blockSize];
057: bufBlock = new byte[blockSize * 2];
058: associatedTextMac = new byte[mac.getMacSize()];
059: nonceMac = new byte[mac.getMacSize()];
060: this .cipher = new SICBlockCipher(cipher);
061: }
062:
063: public String getAlgorithmName() {
064: return cipher.getUnderlyingCipher().getAlgorithmName() + "/EAX";
065: }
066:
067: public BlockCipher getUnderlyingCipher() {
068: return cipher.getUnderlyingCipher();
069: }
070:
071: public int getBlockSize() {
072: return cipher.getBlockSize();
073: }
074:
075: public void init(boolean forEncryption, CipherParameters params)
076: throws IllegalArgumentException {
077: this .forEncryption = forEncryption;
078:
079: byte[] nonce, associatedText;
080: CipherParameters keyParam;
081:
082: if (params instanceof AEADParameters) {
083: AEADParameters param = (AEADParameters) params;
084:
085: nonce = param.getNonce();
086: associatedText = param.getAssociatedText();
087: macSize = param.getMacSize() / 8;
088: keyParam = param.getKey();
089: } else if (params instanceof ParametersWithIV) {
090: ParametersWithIV param = (ParametersWithIV) params;
091:
092: nonce = param.getIV();
093: associatedText = new byte[0];
094: macSize = mac.getMacSize() / 2;
095: keyParam = param.getParameters();
096: } else {
097: throw new IllegalArgumentException(
098: "invalid parameters passed to EAX");
099: }
100:
101: byte[] tag = new byte[blockSize];
102:
103: mac.init(keyParam);
104: tag[blockSize - 1] = hTAG;
105: mac.update(tag, 0, blockSize);
106: mac.update(associatedText, 0, associatedText.length);
107: mac.doFinal(associatedTextMac, 0);
108:
109: tag[blockSize - 1] = nTAG;
110: mac.update(tag, 0, blockSize);
111: mac.update(nonce, 0, nonce.length);
112: mac.doFinal(nonceMac, 0);
113:
114: tag[blockSize - 1] = cTAG;
115: mac.update(tag, 0, blockSize);
116:
117: cipher.init(true, new ParametersWithIV(keyParam, nonceMac));
118: }
119:
120: private void calculateMac() {
121: byte[] outC = new byte[blockSize];
122: mac.doFinal(outC, 0);
123:
124: for (int i = 0; i < macBlock.length; i++) {
125: macBlock[i] = (byte) (nonceMac[i] ^ associatedTextMac[i] ^ outC[i]);
126: }
127: }
128:
129: public void reset() {
130: cipher.reset();
131: mac.reset();
132:
133: bufOff = 0;
134: }
135:
136: public int processByte(byte in, byte[] out, int outOff)
137: throws DataLengthException {
138: return process(in, out, outOff);
139: }
140:
141: public int processBytes(byte[] in, int inOff, int len, byte[] out,
142: int outOff) throws DataLengthException {
143: int resultLen = 0;
144:
145: for (int i = 0; i != len; i++) {
146: resultLen += process(in[inOff + i], out, outOff + resultLen);
147: }
148:
149: return resultLen;
150: }
151:
152: public int doFinal(byte[] out, int outOff)
153: throws IllegalStateException, InvalidCipherTextException {
154: int extra = bufOff;
155: byte[] tmp = new byte[bufBlock.length];
156:
157: bufOff = 0;
158:
159: if (forEncryption) {
160: cipher.processBlock(bufBlock, 0, tmp, 0);
161: cipher.processBlock(bufBlock, blockSize, tmp, blockSize);
162:
163: System.arraycopy(tmp, 0, out, outOff, extra);
164:
165: mac.update(tmp, 0, extra);
166:
167: calculateMac();
168:
169: System.arraycopy(macBlock, 0, out, outOff + extra, macSize);
170:
171: return extra + macSize;
172: } else {
173: if (extra > macSize) {
174: mac.update(bufBlock, 0, extra - macSize);
175:
176: cipher.processBlock(bufBlock, 0, tmp, 0);
177: cipher
178: .processBlock(bufBlock, blockSize, tmp,
179: blockSize);
180:
181: System.arraycopy(tmp, 0, out, outOff, extra - macSize);
182: }
183:
184: calculateMac();
185:
186: if (!verifyMac(bufBlock, extra - macSize)) {
187: throw new InvalidCipherTextException(
188: "mac check in EAX failed");
189: }
190:
191: return extra - macSize;
192: }
193: }
194:
195: public byte[] getMac() {
196: byte[] mac = new byte[macSize];
197:
198: System.arraycopy(macBlock, 0, mac, 0, macSize);
199:
200: return mac;
201: }
202:
203: public int getUpdateOutputSize(int len) {
204: return ((len + bufOff) / blockSize) * blockSize;
205: }
206:
207: public int getOutputSize(int len) {
208: if (forEncryption) {
209: return len + bufOff + macSize;
210: } else {
211: return len + bufOff - macSize;
212: }
213: }
214:
215: private int process(byte b, byte[] out, int outOff) {
216: bufBlock[bufOff++] = b;
217:
218: if (bufOff == bufBlock.length) {
219: int size;
220:
221: if (forEncryption) {
222: size = cipher.processBlock(bufBlock, 0, out, outOff);
223:
224: mac.update(out, 0, blockSize);
225: } else {
226: mac.update(bufBlock, 0, blockSize);
227:
228: size = cipher.processBlock(bufBlock, 0, out, outOff);
229: }
230:
231: bufOff = blockSize;
232: System.arraycopy(bufBlock, blockSize, bufBlock, 0,
233: blockSize);
234:
235: return size;
236: }
237:
238: return 0;
239: }
240:
241: private boolean verifyMac(byte[] mac, int off) {
242: for (int i = 0; i < macSize; i++) {
243: if (macBlock[i] != mac[off + i]) {
244: return false;
245: }
246: }
247:
248: return true;
249: }
250: }
|