Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/quite/humla-spongycastle.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto/modes')
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java126
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java253
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java378
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java258
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java265
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java368
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java574
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java234
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java581
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java187
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java312
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java450
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java253
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java113
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java36
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java18
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java7
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java7
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java260
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java57
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java73
-rw-r--r--core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java90
22 files changed, 4900 insertions, 0 deletions
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java
new file mode 100644
index 00000000..71b75954
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/AEADBlockCipher.java
@@ -0,0 +1,126 @@
+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;
+
+/**
+ * A block cipher mode that includes authenticated encryption with a streaming mode and optional associated data.
+ * @see org.bouncycastle.crypto.params.AEADParameters
+ */
+public interface AEADBlockCipher
+{
+ /**
+ * initialise the underlying cipher. Parameter can either be an AEADParameters or a ParametersWithIV object.
+ *
+ * @param forEncryption true if we are setting up for encryption, false otherwise.
+ * @param params the necessary parameters for the underlying cipher to be initialised.
+ * @exception IllegalArgumentException if the params argument is inappropriate.
+ */
+ public void init(boolean forEncryption, CipherParameters params)
+ throws IllegalArgumentException;
+
+ /**
+ * Return the name of the algorithm.
+ *
+ * @return the algorithm name.
+ */
+ public String getAlgorithmName();
+
+ /**
+ * return the cipher this object wraps.
+ *
+ * @return the cipher this object wraps.
+ */
+ public BlockCipher getUnderlyingCipher();
+
+ /**
+ * Add a single byte to the associated data check.
+ * <br>If the implementation supports it, this will be an online operation and will not retain the associated data.
+ *
+ * @param in the byte to be processed.
+ */
+ public void processAADByte(byte in);
+
+ /**
+ * Add a sequence of bytes to the associated data check.
+ * <br>If the implementation supports it, this will be an online operation and will not retain the associated data.
+ *
+ * @param in the input byte array.
+ * @param inOff the offset into the in array where the data to be processed starts.
+ * @param len the number of bytes to be processed.
+ */
+ public void processAADBytes(byte[] in, int inOff, int len);
+
+ /**
+ * encrypt/decrypt a single byte.
+ *
+ * @param in the byte to be processed.
+ * @param out the output buffer the processed byte goes into.
+ * @param outOff the offset into the output byte array the processed data starts at.
+ * @return the number of bytes written to out.
+ * @exception DataLengthException if the output buffer is too small.
+ */
+ public int processByte(byte in, byte[] out, int outOff)
+ throws DataLengthException;
+
+ /**
+ * process a block of bytes from in putting the result into out.
+ *
+ * @param in the input byte array.
+ * @param inOff the offset into the in array where the data to be processed starts.
+ * @param len the number of bytes to be processed.
+ * @param out the output buffer the processed bytes go into.
+ * @param outOff the offset into the output byte array the processed data starts at.
+ * @return the number of bytes written to out.
+ * @exception DataLengthException if the output buffer is too small.
+ */
+ public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+ throws DataLengthException;
+
+ /**
+ * Finish the operation either appending or verifying the MAC at the end of the data.
+ *
+ * @param out space for any resulting output data.
+ * @param outOff offset into out to start copying the data at.
+ * @return number of bytes written into out.
+ * @throws IllegalStateException if the cipher is in an inappropriate state.
+ * @throws org.bouncycastle.crypto.InvalidCipherTextException if the MAC fails to match.
+ */
+ public int doFinal(byte[] out, int outOff)
+ throws IllegalStateException, InvalidCipherTextException;
+
+ /**
+ * Return the value of the MAC associated with the last stream processed.
+ *
+ * @return MAC for plaintext data.
+ */
+ public byte[] getMac();
+
+ /**
+ * return the size of the output buffer required for a processBytes
+ * an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to processBytes
+ * with len bytes of input.
+ */
+ public int getUpdateOutputSize(int len);
+
+ /**
+ * return the size of the output buffer required for a processBytes plus a
+ * doFinal with an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to processBytes and doFinal
+ * with len bytes of input.
+ */
+ public int getOutputSize(int len);
+
+ /**
+ * Reset the cipher. After resetting the cipher is in the same state
+ * as it was after the last init (if there was one).
+ */
+ public void reset();
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java
new file mode 100644
index 00000000..d4800e62
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/CBCBlockCipher.java
@@ -0,0 +1,253 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * implements Cipher-Block-Chaining (CBC) mode on top of a simple cipher.
+ */
+public class CBCBlockCipher
+ implements BlockCipher
+{
+ private byte[] IV;
+ private byte[] cbcV;
+ private byte[] cbcNextV;
+
+ private int blockSize;
+ private BlockCipher cipher = null;
+ private boolean encrypting;
+
+ /**
+ * Basic constructor.
+ *
+ * @param cipher the block cipher to be used as the basis of chaining.
+ */
+ public CBCBlockCipher(
+ BlockCipher cipher)
+ {
+ this.cipher = cipher;
+ this.blockSize = cipher.getBlockSize();
+
+ this.IV = new byte[blockSize];
+ this.cbcV = new byte[blockSize];
+ this.cbcNextV = new byte[blockSize];
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ /**
+ * Initialise the cipher and, possibly, the initialisation vector (IV).
+ * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+ *
+ * @param encrypting if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean encrypting,
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ boolean oldEncrypting = this.encrypting;
+
+ this.encrypting = encrypting;
+
+ if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV ivParam = (ParametersWithIV)params;
+ byte[] iv = ivParam.getIV();
+
+ if (iv.length != blockSize)
+ {
+ throw new IllegalArgumentException("initialisation vector must be the same length as block size");
+ }
+
+ System.arraycopy(iv, 0, IV, 0, iv.length);
+
+ reset();
+
+ // if null it's an IV changed only.
+ if (ivParam.getParameters() != null)
+ {
+ cipher.init(encrypting, ivParam.getParameters());
+ }
+ else if (oldEncrypting != encrypting)
+ {
+ throw new IllegalArgumentException("cannot change encrypting state without providing key.");
+ }
+ }
+ else
+ {
+ reset();
+
+ // if it's null, key is to be reused.
+ if (params != null)
+ {
+ cipher.init(encrypting, params);
+ }
+ else if (oldEncrypting != encrypting)
+ {
+ throw new IllegalArgumentException("cannot change encrypting state without providing key.");
+ }
+ }
+ }
+
+ /**
+ * return the algorithm name and mode.
+ *
+ * @return the name of the underlying algorithm followed by "/CBC".
+ */
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/CBC";
+ }
+
+ /**
+ * return the block size of the underlying cipher.
+ *
+ * @return the block size of the underlying cipher.
+ */
+ public int getBlockSize()
+ {
+ return cipher.getBlockSize();
+ }
+
+ /**
+ * Process one block of input from the array in and write it to
+ * the out array.
+ *
+ * @param in the array containing the input data.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the output data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int processBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
+ }
+
+ /**
+ * reset the chaining vector back to the IV and reset the underlying
+ * cipher.
+ */
+ public void reset()
+ {
+ System.arraycopy(IV, 0, cbcV, 0, IV.length);
+ Arrays.fill(cbcNextV, (byte)0);
+
+ cipher.reset();
+ }
+
+ /**
+ * Do the appropriate chaining step for CBC mode encryption.
+ *
+ * @param in the array containing the data to be encrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int encryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ /*
+ * XOR the cbcV and the input,
+ * then encrypt the cbcV
+ */
+ for (int i = 0; i < blockSize; i++)
+ {
+ cbcV[i] ^= in[inOff + i];
+ }
+
+ int length = cipher.processBlock(cbcV, 0, out, outOff);
+
+ /*
+ * copy ciphertext to cbcV
+ */
+ System.arraycopy(out, outOff, cbcV, 0, cbcV.length);
+
+ return length;
+ }
+
+ /**
+ * Do the appropriate chaining step for CBC mode decryption.
+ *
+ * @param in the array containing the data to be decrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the decrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int decryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ System.arraycopy(in, inOff, cbcNextV, 0, blockSize);
+
+ int length = cipher.processBlock(in, inOff, out, outOff);
+
+ /*
+ * XOR the cbcV and the output
+ */
+ for (int i = 0; i < blockSize; i++)
+ {
+ out[outOff + i] ^= cbcV[i];
+ }
+
+ /*
+ * swap the back up buffer into next position
+ */
+ byte[] tmp;
+
+ tmp = cbcV;
+ cbcV = cbcNextV;
+ cbcNextV = tmp;
+
+ return length;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java
new file mode 100644
index 00000000..9a6e2e0c
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/CCMBlockCipher.java
@@ -0,0 +1,378 @@
+package org.bouncycastle.crypto.modes;
+
+import java.io.ByteArrayOutputStream;
+
+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.CBCBlockCipherMac;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Implements the Counter with Cipher Block Chaining mode (CCM) detailed in
+ * NIST Special Publication 800-38C.
+ * <p>
+ * <b>Note</b>: this mode is a packet mode - it needs all the data up front.
+ */
+public class CCMBlockCipher
+ implements AEADBlockCipher
+{
+ private BlockCipher cipher;
+ private int blockSize;
+ private boolean forEncryption;
+ private byte[] nonce;
+ private byte[] initialAssociatedText;
+ private int macSize;
+ private CipherParameters keyParam;
+ private byte[] macBlock;
+ private ByteArrayOutputStream associatedText = new ByteArrayOutputStream();
+ private ByteArrayOutputStream data = new ByteArrayOutputStream();
+
+ /**
+ * Basic constructor.
+ *
+ * @param c the block cipher to be used.
+ */
+ public CCMBlockCipher(BlockCipher c)
+ {
+ this.cipher = c;
+ this.blockSize = c.getBlockSize();
+ this.macBlock = new byte[blockSize];
+
+ if (blockSize != 16)
+ {
+ throw new IllegalArgumentException("cipher required with a block size of 16.");
+ }
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+
+ public void init(boolean forEncryption, CipherParameters params)
+ throws IllegalArgumentException
+ {
+ this.forEncryption = forEncryption;
+
+ if (params instanceof AEADParameters)
+ {
+ AEADParameters param = (AEADParameters)params;
+
+ nonce = param.getNonce();
+ initialAssociatedText = param.getAssociatedText();
+ macSize = param.getMacSize() / 8;
+ keyParam = param.getKey();
+ }
+ else if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV param = (ParametersWithIV)params;
+
+ nonce = param.getIV();
+ initialAssociatedText = null;
+ macSize = macBlock.length / 2;
+ keyParam = param.getParameters();
+ }
+ else
+ {
+ throw new IllegalArgumentException("invalid parameters passed to CCM");
+ }
+
+ if (nonce == null || nonce.length < 7 || nonce.length > 13)
+ {
+ throw new IllegalArgumentException("nonce must have length from 7 to 13 octets");
+ }
+ }
+
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/CCM";
+ }
+
+ public void processAADByte(byte in)
+ {
+ associatedText.write(in);
+ }
+
+ public void processAADBytes(byte[] in, int inOff, int len)
+ {
+ // TODO: Process AAD online
+ associatedText.write(in, inOff, len);
+ }
+
+ public int processByte(byte in, byte[] out, int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ data.write(in);
+
+ return 0;
+ }
+
+ public int processBytes(byte[] in, int inOff, int inLen, byte[] out, int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ data.write(in, inOff, inLen);
+
+ return 0;
+ }
+
+ public int doFinal(byte[] out, int outOff)
+ throws IllegalStateException, InvalidCipherTextException
+ {
+ byte[] text = data.toByteArray();
+ byte[] enc = processPacket(text, 0, text.length);
+
+ System.arraycopy(enc, 0, out, outOff, enc.length);
+
+ reset();
+
+ return enc.length;
+ }
+
+ public void reset()
+ {
+ cipher.reset();
+ associatedText.reset();
+ data.reset();
+ }
+
+ /**
+ * Returns a byte array containing the mac calculated as part of the
+ * last encrypt or decrypt operation.
+ *
+ * @return the last mac calculated.
+ */
+ public byte[] getMac()
+ {
+ byte[] mac = new byte[macSize];
+
+ System.arraycopy(macBlock, 0, mac, 0, mac.length);
+
+ return mac;
+ }
+
+ public int getUpdateOutputSize(int len)
+ {
+ return 0;
+ }
+
+ public int getOutputSize(int len)
+ {
+ int totalData = len + data.size();
+
+ if (forEncryption)
+ {
+ return totalData + macSize;
+ }
+
+ return totalData < macSize ? 0 : totalData - macSize;
+ }
+
+ public byte[] processPacket(byte[] in, int inOff, int inLen)
+ throws IllegalStateException, InvalidCipherTextException
+ {
+ // TODO: handle null keyParam (e.g. via RepeatedKeySpec)
+ // Need to keep the CTR and CBC Mac parts around and reset
+ if (keyParam == null)
+ {
+ throw new IllegalStateException("CCM cipher unitialized.");
+ }
+
+ int n = nonce.length;
+ int q = 15 - n;
+ if (q < 4)
+ {
+ int limitLen = 1 << (8 * q);
+ if (inLen >= limitLen)
+ {
+ throw new IllegalStateException("CCM packet too large for choice of q.");
+ }
+ }
+
+ byte[] iv = new byte[blockSize];
+ iv[0] = (byte)((q - 1) & 0x7);
+ System.arraycopy(nonce, 0, iv, 1, nonce.length);
+
+ BlockCipher ctrCipher = new SICBlockCipher(cipher);
+ ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv));
+
+ int index = inOff;
+ int outOff = 0;
+ byte[] output;
+
+ if (forEncryption)
+ {
+ output = new byte[inLen + macSize];
+
+ calculateMac(in, inOff, inLen, macBlock);
+
+ ctrCipher.processBlock(macBlock, 0, macBlock, 0); // S0
+
+ while (index < inLen - blockSize) // S1...
+ {
+ ctrCipher.processBlock(in, index, output, outOff);
+ outOff += blockSize;
+ index += blockSize;
+ }
+
+ byte[] block = new byte[blockSize];
+
+ System.arraycopy(in, index, block, 0, inLen - index);
+
+ ctrCipher.processBlock(block, 0, block, 0);
+
+ System.arraycopy(block, 0, output, outOff, inLen - index);
+
+ outOff += inLen - index;
+
+ System.arraycopy(macBlock, 0, output, outOff, output.length - outOff);
+ }
+ else
+ {
+ output = new byte[inLen - macSize];
+
+ System.arraycopy(in, inOff + inLen - macSize, macBlock, 0, macSize);
+
+ ctrCipher.processBlock(macBlock, 0, macBlock, 0);
+
+ for (int i = macSize; i != macBlock.length; i++)
+ {
+ macBlock[i] = 0;
+ }
+
+ while (outOff < output.length - blockSize)
+ {
+ ctrCipher.processBlock(in, index, output, outOff);
+ outOff += blockSize;
+ index += blockSize;
+ }
+
+ byte[] block = new byte[blockSize];
+
+ System.arraycopy(in, index, block, 0, output.length - outOff);
+
+ ctrCipher.processBlock(block, 0, block, 0);
+
+ System.arraycopy(block, 0, output, outOff, output.length - outOff);
+
+ byte[] calculatedMacBlock = new byte[blockSize];
+
+ calculateMac(output, 0, output.length, calculatedMacBlock);
+
+ if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock))
+ {
+ throw new InvalidCipherTextException("mac check in CCM failed");
+ }
+ }
+
+ return output;
+ }
+
+ private int calculateMac(byte[] data, int dataOff, int dataLen, byte[] macBlock)
+ {
+ Mac cMac = new CBCBlockCipherMac(cipher, macSize * 8);
+
+ cMac.init(keyParam);
+
+ //
+ // build b0
+ //
+ byte[] b0 = new byte[16];
+
+ if (hasAssociatedText())
+ {
+ b0[0] |= 0x40;
+ }
+
+ b0[0] |= (((cMac.getMacSize() - 2) / 2) & 0x7) << 3;
+
+ b0[0] |= ((15 - nonce.length) - 1) & 0x7;
+
+ System.arraycopy(nonce, 0, b0, 1, nonce.length);
+
+ int q = dataLen;
+ int count = 1;
+ while (q > 0)
+ {
+ b0[b0.length - count] = (byte)(q & 0xff);
+ q >>>= 8;
+ count++;
+ }
+
+ cMac.update(b0, 0, b0.length);
+
+ //
+ // process associated text
+ //
+ if (hasAssociatedText())
+ {
+ int extra;
+
+ int textLength = getAssociatedTextLength();
+ if (textLength < ((1 << 16) - (1 << 8)))
+ {
+ cMac.update((byte)(textLength >> 8));
+ cMac.update((byte)textLength);
+
+ extra = 2;
+ }
+ else // can't go any higher than 2^32
+ {
+ cMac.update((byte)0xff);
+ cMac.update((byte)0xfe);
+ cMac.update((byte)(textLength >> 24));
+ cMac.update((byte)(textLength >> 16));
+ cMac.update((byte)(textLength >> 8));
+ cMac.update((byte)textLength);
+
+ extra = 6;
+ }
+
+ if (initialAssociatedText != null)
+ {
+ cMac.update(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+ if (associatedText.size() > 0)
+ {
+ byte[] tmp = associatedText.toByteArray();
+ cMac.update(tmp, 0, tmp.length);
+ }
+
+ extra = (extra + textLength) % 16;
+ if (extra != 0)
+ {
+ for (int i = extra; i != 16; i++)
+ {
+ cMac.update((byte)0x00);
+ }
+ }
+ }
+
+ //
+ // add the text
+ //
+ cMac.update(data, dataOff, dataLen);
+
+ return cMac.doFinal(macBlock, 0);
+ }
+
+ private int getAssociatedTextLength()
+ {
+ return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length);
+ }
+
+ private boolean hasAssociatedText()
+ {
+ return getAssociatedTextLength() > 0;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java
new file mode 100644
index 00000000..d0fb9bb6
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/CFBBlockCipher.java
@@ -0,0 +1,258 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * implements a Cipher-FeedBack (CFB) mode on top of a simple cipher.
+ */
+public class CFBBlockCipher
+ implements BlockCipher
+{
+ private byte[] IV;
+ private byte[] cfbV;
+ private byte[] cfbOutV;
+
+ private int blockSize;
+ private BlockCipher cipher = null;
+ private boolean encrypting;
+
+ /**
+ * Basic constructor.
+ *
+ * @param cipher the block cipher to be used as the basis of the
+ * feedback mode.
+ * @param bitBlockSize the block size in bits (note: a multiple of 8)
+ */
+ public CFBBlockCipher(
+ BlockCipher cipher,
+ int bitBlockSize)
+ {
+ this.cipher = cipher;
+ this.blockSize = bitBlockSize / 8;
+
+ this.IV = new byte[cipher.getBlockSize()];
+ this.cfbV = new byte[cipher.getBlockSize()];
+ this.cfbOutV = new byte[cipher.getBlockSize()];
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ /**
+ * Initialise the cipher and, possibly, the initialisation vector (IV).
+ * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+ * An IV which is too short is handled in FIPS compliant fashion.
+ *
+ * @param encrypting if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean encrypting,
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ this.encrypting = encrypting;
+
+ if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV ivParam = (ParametersWithIV)params;
+ byte[] iv = ivParam.getIV();
+
+ if (iv.length < IV.length)
+ {
+ // prepend the supplied IV with zeros (per FIPS PUB 81)
+ System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
+ for (int i = 0; i < IV.length - iv.length; i++)
+ {
+ IV[i] = 0;
+ }
+ }
+ else
+ {
+ System.arraycopy(iv, 0, IV, 0, IV.length);
+ }
+
+ reset();
+
+ // if null it's an IV changed only.
+ if (ivParam.getParameters() != null)
+ {
+ cipher.init(true, ivParam.getParameters());
+ }
+ }
+ else
+ {
+ reset();
+
+ // if it's null, key is to be reused.
+ if (params != null)
+ {
+ cipher.init(true, params);
+ }
+ }
+ }
+
+ /**
+ * return the algorithm name and mode.
+ *
+ * @return the name of the underlying algorithm followed by "/CFB"
+ * and the block size in bits.
+ */
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/CFB" + (blockSize * 8);
+ }
+
+ /**
+ * return the block size we are operating at.
+ *
+ * @return the block size we are operating at (in bytes).
+ */
+ public int getBlockSize()
+ {
+ return blockSize;
+ }
+
+ /**
+ * Process one block of input from the array in and write it to
+ * the out array.
+ *
+ * @param in the array containing the input data.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the output data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int processBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ return (encrypting) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
+ }
+
+ /**
+ * Do the appropriate processing for CFB mode encryption.
+ *
+ * @param in the array containing the data to be encrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int encryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ cipher.processBlock(cfbV, 0, cfbOutV, 0);
+
+ //
+ // XOR the cfbV with the plaintext producing the ciphertext
+ //
+ for (int i = 0; i < blockSize; i++)
+ {
+ out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]);
+ }
+
+ //
+ // change over the input block.
+ //
+ System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize);
+ System.arraycopy(out, outOff, cfbV, cfbV.length - blockSize, blockSize);
+
+ return blockSize;
+ }
+
+ /**
+ * Do the appropriate processing for CFB mode decryption.
+ *
+ * @param in the array containing the data to be decrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int decryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ cipher.processBlock(cfbV, 0, cfbOutV, 0);
+
+ //
+ // change over the input block.
+ //
+ System.arraycopy(cfbV, blockSize, cfbV, 0, cfbV.length - blockSize);
+ System.arraycopy(in, inOff, cfbV, cfbV.length - blockSize, blockSize);
+
+ //
+ // XOR the cfbV with the ciphertext producing the plaintext
+ //
+ for (int i = 0; i < blockSize; i++)
+ {
+ out[outOff + i] = (byte)(cfbOutV[i] ^ in[inOff + i]);
+ }
+
+ return blockSize;
+ }
+
+ /**
+ * reset the chaining vector back to the IV and reset the underlying
+ * cipher.
+ */
+ public void reset()
+ {
+ System.arraycopy(IV, 0, cfbV, 0, IV.length);
+
+ cipher.reset();
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java
new file mode 100644
index 00000000..b8e5b610
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/CTSBlockCipher.java
@@ -0,0 +1,265 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+
+/**
+ * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to
+ * be used to produce cipher text which is the same length as the plain text.
+ */
+public class CTSBlockCipher
+ extends BufferedBlockCipher
+{
+ private int blockSize;
+
+ /**
+ * Create a buffered block cipher that uses Cipher Text Stealing
+ *
+ * @param cipher the underlying block cipher this buffering object wraps.
+ */
+ public CTSBlockCipher(
+ BlockCipher cipher)
+ {
+ if ((cipher instanceof OFBBlockCipher) || (cipher instanceof CFBBlockCipher))
+ {
+ throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers");
+ }
+
+ this.cipher = cipher;
+
+ blockSize = cipher.getBlockSize();
+
+ buf = new byte[blockSize * 2];
+ bufOff = 0;
+ }
+
+ /**
+ * return the size of the output buffer required for an update
+ * an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to update
+ * with len bytes of input.
+ */
+ public int getUpdateOutputSize(
+ int len)
+ {
+ int total = len + bufOff;
+ int leftOver = total % buf.length;
+
+ if (leftOver == 0)
+ {
+ return total - buf.length;
+ }
+
+ return total - leftOver;
+ }
+
+ /**
+ * return the size of the output buffer required for an update plus a
+ * doFinal with an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to update and doFinal
+ * with len bytes of input.
+ */
+ public int getOutputSize(
+ int len)
+ {
+ return len + bufOff;
+ }
+
+ /**
+ * process a single byte, producing an output block if neccessary.
+ *
+ * @param in the input byte.
+ * @param out the space for any output that might be produced.
+ * @param outOff the offset from which the output will be copied.
+ * @return the number of output bytes copied to out.
+ * @exception DataLengthException if there isn't enough space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ */
+ public int processByte(
+ byte in,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ int resultLen = 0;
+
+ if (bufOff == buf.length)
+ {
+ resultLen = cipher.processBlock(buf, 0, out, outOff);
+ System.arraycopy(buf, blockSize, buf, 0, blockSize);
+
+ bufOff = blockSize;
+ }
+
+ buf[bufOff++] = in;
+
+ return resultLen;
+ }
+
+ /**
+ * process an array of bytes, producing output if necessary.
+ *
+ * @param in the input byte array.
+ * @param inOff the offset at which the input data starts.
+ * @param len the number of bytes to be copied out of the input array.
+ * @param out the space for any output that might be produced.
+ * @param outOff the offset from which the output will be copied.
+ * @return the number of output bytes copied to out.
+ * @exception DataLengthException if there isn't enough space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ */
+ public int processBytes(
+ byte[] in,
+ int inOff,
+ int len,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if (len < 0)
+ {
+ throw new IllegalArgumentException("Can't have a negative input length!");
+ }
+
+ int blockSize = getBlockSize();
+ int length = getUpdateOutputSize(len);
+
+ if (length > 0)
+ {
+ if ((outOff + length) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+ }
+
+ int resultLen = 0;
+ int gapLen = buf.length - bufOff;
+
+ if (len > gapLen)
+ {
+ System.arraycopy(in, inOff, buf, bufOff, gapLen);
+
+ resultLen += cipher.processBlock(buf, 0, out, outOff);
+ System.arraycopy(buf, blockSize, buf, 0, blockSize);
+
+ bufOff = blockSize;
+
+ len -= gapLen;
+ inOff += gapLen;
+
+ while (len > blockSize)
+ {
+ System.arraycopy(in, inOff, buf, bufOff, blockSize);
+ resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
+ System.arraycopy(buf, blockSize, buf, 0, blockSize);
+
+ len -= blockSize;
+ inOff += blockSize;
+ }
+ }
+
+ System.arraycopy(in, inOff, buf, bufOff, len);
+
+ bufOff += len;
+
+ return resultLen;
+ }
+
+ /**
+ * Process the last block in the buffer.
+ *
+ * @param out the array the block currently being held is copied into.
+ * @param outOff the offset at which the copying starts.
+ * @return the number of output bytes copied to out.
+ * @exception DataLengthException if there is insufficient space in out for
+ * the output.
+ * @exception IllegalStateException if the underlying cipher is not
+ * initialised.
+ * @exception InvalidCipherTextException if cipher text decrypts wrongly (in
+ * case the exception will never get thrown).
+ */
+ public int doFinal(
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException, InvalidCipherTextException
+ {
+ if (bufOff + outOff > out.length)
+ {
+ throw new DataLengthException("output buffer to small in doFinal");
+ }
+
+ int blockSize = cipher.getBlockSize();
+ int len = bufOff - blockSize;
+ byte[] block = new byte[blockSize];
+
+ if (forEncryption)
+ {
+ cipher.processBlock(buf, 0, block, 0);
+
+ if (bufOff < blockSize)
+ {
+ throw new DataLengthException("need at least one block of input for CTS");
+ }
+
+ for (int i = bufOff; i != buf.length; i++)
+ {
+ buf[i] = block[i - blockSize];
+ }
+
+ for (int i = blockSize; i != bufOff; i++)
+ {
+ buf[i] ^= block[i - blockSize];
+ }
+
+ if (cipher instanceof CBCBlockCipher)
+ {
+ BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
+
+ c.processBlock(buf, blockSize, out, outOff);
+ }
+ else
+ {
+ cipher.processBlock(buf, blockSize, out, outOff);
+ }
+
+ System.arraycopy(block, 0, out, outOff + blockSize, len);
+ }
+ else
+ {
+ byte[] lastBlock = new byte[blockSize];
+
+ if (cipher instanceof CBCBlockCipher)
+ {
+ BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
+
+ c.processBlock(buf, 0, block, 0);
+ }
+ else
+ {
+ cipher.processBlock(buf, 0, block, 0);
+ }
+
+ for (int i = blockSize; i != bufOff; i++)
+ {
+ lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]);
+ }
+
+ System.arraycopy(buf, blockSize, block, 0, len);
+
+ cipher.processBlock(block, 0, out, outOff);
+ System.arraycopy(lastBlock, 0, out, outOff + blockSize, len);
+ }
+
+ int offset = bufOff;
+
+ reset();
+
+ return offset;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java
new file mode 100644
index 00000000..4999caae
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/EAXBlockCipher.java
@@ -0,0 +1,368 @@
+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;
+
+ private boolean cipherInitialized;
+ private byte[] initialAssociatedText;
+
+ /**
+ * 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;
+ CipherParameters keyParam;
+
+ if (params instanceof AEADParameters)
+ {
+ AEADParameters param = (AEADParameters)params;
+
+ nonce = param.getNonce();
+ initialAssociatedText = param.getAssociatedText();
+ macSize = param.getMacSize() / 8;
+ keyParam = param.getKey();
+ }
+ else if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV param = (ParametersWithIV)params;
+
+ nonce = param.getIV();
+ initialAssociatedText = null;
+ macSize = mac.getMacSize() / 2;
+ keyParam = param.getParameters();
+ }
+ else
+ {
+ throw new IllegalArgumentException("invalid parameters passed to EAX");
+ }
+
+ byte[] tag = new byte[blockSize];
+
+ // Key reuse implemented in CBC mode of underlying CMac
+ mac.init(keyParam);
+
+ tag[blockSize - 1] = nTAG;
+ mac.update(tag, 0, blockSize);
+ mac.update(nonce, 0, nonce.length);
+ mac.doFinal(nonceMac, 0);
+
+ tag[blockSize - 1] = hTAG;
+ mac.update(tag, 0, blockSize);
+
+ if (initialAssociatedText != null)
+ {
+ processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+
+ // Same BlockCipher underlies this and the mac, so reuse last key on cipher
+ cipher.init(true, new ParametersWithIV(null, nonceMac));
+ }
+
+ private void initCipher()
+ {
+ if (cipherInitialized)
+ {
+ return;
+ }
+
+ cipherInitialized = true;
+
+ mac.doFinal(associatedTextMac, 0);
+
+ byte[] tag = new byte[blockSize];
+ tag[blockSize - 1] = cTAG;
+ mac.update(tag, 0, blockSize);
+ }
+
+ 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(); // TODO Redundant since the mac will reset it?
+ mac.reset();
+
+ bufOff = 0;
+ Arrays.fill(bufBlock, (byte)0);
+
+ if (clearMac)
+ {
+ Arrays.fill(macBlock, (byte)0);
+ }
+
+ byte[] tag = new byte[blockSize];
+ tag[blockSize - 1] = hTAG;
+ mac.update(tag, 0, blockSize);
+
+ cipherInitialized = false;
+
+ if (initialAssociatedText != null)
+ {
+ processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+ }
+
+ public void processAADByte(byte in)
+ {
+ if (cipherInitialized)
+ {
+ throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun.");
+ }
+ mac.update(in);
+ }
+
+ public void processAADBytes(byte[] in, int inOff, int len)
+ {
+ if (cipherInitialized)
+ {
+ throw new IllegalStateException("AAD data cannot be added after encryption/decription processing has begun.");
+ }
+ mac.update(in, inOff, len);
+ }
+
+ public int processByte(byte in, byte[] out, int outOff)
+ throws DataLengthException
+ {
+ initCipher();
+
+ return process(in, out, outOff);
+ }
+
+ public int processBytes(byte[] in, int inOff, int len, byte[] out, int outOff)
+ throws DataLengthException
+ {
+ initCipher();
+
+ 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
+ {
+ initCipher();
+
+ 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)
+ {
+ int totalData = len + bufOff;
+ if (!forEncryption)
+ {
+ if (totalData < macSize)
+ {
+ return 0;
+ }
+ totalData -= macSize;
+ }
+ return totalData - totalData % blockSize;
+ }
+
+ public int getOutputSize(int len)
+ {
+ int totalData = len + bufOff;
+
+ if (forEncryption)
+ {
+ return totalData + macSize;
+ }
+
+ return totalData < macSize ? 0 : totalData - macSize;
+ }
+
+ private int process(byte b, byte[] out, int outOff)
+ {
+ bufBlock[bufOff++] = b;
+
+ if (bufOff == bufBlock.length)
+ {
+ // TODO Could move the processByte(s) calls to here
+// initCipher();
+
+ 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)
+ {
+ int nonEqual = 0;
+
+ for (int i = 0; i < macSize; i++)
+ {
+ nonEqual |= (macBlock[i] ^ mac[off + i]);
+ }
+
+ return nonEqual == 0;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java
new file mode 100644
index 00000000..9e617ec9
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/GCMBlockCipher.java
@@ -0,0 +1,574 @@
+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.modes.gcm.GCMExponentiator;
+import org.bouncycastle.crypto.modes.gcm.GCMMultiplier;
+import org.bouncycastle.crypto.modes.gcm.Tables1kGCMExponentiator;
+import org.bouncycastle.crypto.modes.gcm.Tables8kGCMMultiplier;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * Implements the Galois/Counter mode (GCM) detailed in
+ * NIST Special Publication 800-38D.
+ */
+public class GCMBlockCipher
+ implements AEADBlockCipher
+{
+ private static final int BLOCK_SIZE = 16;
+
+ // not final due to a compiler bug
+ private BlockCipher cipher;
+ private GCMMultiplier multiplier;
+ private GCMExponentiator exp;
+
+ // These fields are set by init and not modified by processing
+ private boolean forEncryption;
+ private int macSize;
+ private byte[] nonce;
+ private byte[] initialAssociatedText;
+ private byte[] H;
+ private byte[] J0;
+
+ // These fields are modified during processing
+ private byte[] bufBlock;
+ private byte[] macBlock;
+ private byte[] S, S_at, S_atPre;
+ private byte[] counter;
+ private int bufOff;
+ private long totalLength;
+ private byte[] atBlock;
+ private int atBlockPos;
+ private long atLength;
+ private long atLengthPre;
+
+ public GCMBlockCipher(BlockCipher c)
+ {
+ this(c, null);
+ }
+
+ public GCMBlockCipher(BlockCipher c, GCMMultiplier m)
+ {
+ if (c.getBlockSize() != BLOCK_SIZE)
+ {
+ throw new IllegalArgumentException(
+ "cipher required with a block size of " + BLOCK_SIZE + ".");
+ }
+
+ if (m == null)
+ {
+ // TODO Consider a static property specifying default multiplier
+ m = new Tables8kGCMMultiplier();
+ }
+
+ this.cipher = c;
+ this.multiplier = m;
+ }
+
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/GCM";
+ }
+
+ public void init(boolean forEncryption, CipherParameters params)
+ throws IllegalArgumentException
+ {
+ this.forEncryption = forEncryption;
+ this.macBlock = null;
+
+ KeyParameter keyParam;
+
+ if (params instanceof AEADParameters)
+ {
+ AEADParameters param = (AEADParameters)params;
+
+ nonce = param.getNonce();
+ initialAssociatedText = param.getAssociatedText();
+
+ int macSizeBits = param.getMacSize();
+ if (macSizeBits < 96 || macSizeBits > 128 || macSizeBits % 8 != 0)
+ {
+ throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits);
+ }
+
+ macSize = macSizeBits / 8;
+ keyParam = param.getKey();
+ }
+ else if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV param = (ParametersWithIV)params;
+
+ nonce = param.getIV();
+ initialAssociatedText = null;
+ macSize = 16;
+ keyParam = (KeyParameter)param.getParameters();
+ }
+ else
+ {
+ throw new IllegalArgumentException("invalid parameters passed to GCM");
+ }
+
+ int bufLength = forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize);
+ this.bufBlock = new byte[bufLength];
+
+ if (nonce == null || nonce.length < 1)
+ {
+ throw new IllegalArgumentException("IV must be at least 1 byte");
+ }
+
+ // TODO This should be configurable by init parameters
+ // (but must be 16 if nonce length not 12) (BLOCK_SIZE?)
+// this.tagLength = 16;
+
+ // Cipher always used in forward mode
+ // if keyParam is null we're reusing the last key.
+ if (keyParam != null)
+ {
+ cipher.init(true, keyParam);
+
+ this.H = new byte[BLOCK_SIZE];
+ cipher.processBlock(H, 0, H, 0);
+
+ // GCMMultiplier tables don't change unless the key changes (and are expensive to init)
+ multiplier.init(H);
+ exp = null;
+ }
+
+ this.J0 = new byte[BLOCK_SIZE];
+
+ if (nonce.length == 12)
+ {
+ System.arraycopy(nonce, 0, J0, 0, nonce.length);
+ this.J0[BLOCK_SIZE - 1] = 0x01;
+ }
+ else
+ {
+ gHASH(J0, nonce, nonce.length);
+ byte[] X = new byte[BLOCK_SIZE];
+ Pack.longToBigEndian((long)nonce.length * 8, X, 8);
+ gHASHBlock(J0, X);
+ }
+
+ this.S = new byte[BLOCK_SIZE];
+ this.S_at = new byte[BLOCK_SIZE];
+ this.S_atPre = new byte[BLOCK_SIZE];
+ this.atBlock = new byte[BLOCK_SIZE];
+ this.atBlockPos = 0;
+ this.atLength = 0;
+ this.atLengthPre = 0;
+ this.counter = Arrays.clone(J0);
+ this.bufOff = 0;
+ this.totalLength = 0;
+
+ if (initialAssociatedText != null)
+ {
+ processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+ }
+
+ public byte[] getMac()
+ {
+ return Arrays.clone(macBlock);
+ }
+
+ public int getOutputSize(int len)
+ {
+ int totalData = len + bufOff;
+
+ if (forEncryption)
+ {
+ return totalData + macSize;
+ }
+
+ return totalData < macSize ? 0 : totalData - macSize;
+ }
+
+ public int getUpdateOutputSize(int len)
+ {
+ int totalData = len + bufOff;
+ if (!forEncryption)
+ {
+ if (totalData < macSize)
+ {
+ return 0;
+ }
+ totalData -= macSize;
+ }
+ return totalData - totalData % BLOCK_SIZE;
+ }
+
+ public void processAADByte(byte in)
+ {
+ atBlock[atBlockPos] = in;
+ if (++atBlockPos == BLOCK_SIZE)
+ {
+ // Hash each block as it fills
+ gHASHBlock(S_at, atBlock);
+ atBlockPos = 0;
+ atLength += BLOCK_SIZE;
+ }
+ }
+
+ public void processAADBytes(byte[] in, int inOff, int len)
+ {
+ for (int i = 0; i < len; ++i)
+ {
+ atBlock[atBlockPos] = in[inOff + i];
+ if (++atBlockPos == BLOCK_SIZE)
+ {
+ // Hash each block as it fills
+ gHASHBlock(S_at, atBlock);
+ atBlockPos = 0;
+ atLength += BLOCK_SIZE;
+ }
+ }
+ }
+
+ private void initCipher()
+ {
+ if (atLength > 0)
+ {
+ System.arraycopy(S_at, 0, S_atPre, 0, BLOCK_SIZE);
+ atLengthPre = atLength;
+ }
+
+ // Finish hash for partial AAD block
+ if (atBlockPos > 0)
+ {
+ gHASHPartial(S_atPre, atBlock, 0, atBlockPos);
+ atLengthPre += atBlockPos;
+ }
+
+ if (atLengthPre > 0)
+ {
+ System.arraycopy(S_atPre, 0, S, 0, BLOCK_SIZE);
+ }
+ }
+
+ public int processByte(byte in, byte[] out, int outOff)
+ throws DataLengthException
+ {
+ bufBlock[bufOff] = in;
+ if (++bufOff == bufBlock.length)
+ {
+ outputBlock(out, outOff);
+ return BLOCK_SIZE;
+ }
+ return 0;
+ }
+
+ 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)
+ {
+ bufBlock[bufOff] = in[inOff + i];
+ if (++bufOff == bufBlock.length)
+ {
+ outputBlock(out, outOff + resultLen);
+ resultLen += BLOCK_SIZE;
+ }
+ }
+
+ return resultLen;
+ }
+
+ private void outputBlock(byte[] output, int offset)
+ {
+ if (totalLength == 0)
+ {
+ initCipher();
+ }
+ gCTRBlock(bufBlock, output, offset);
+ if (forEncryption)
+ {
+ bufOff = 0;
+ }
+ else
+ {
+ System.arraycopy(bufBlock, BLOCK_SIZE, bufBlock, 0, macSize);
+ bufOff = macSize;
+ }
+ }
+
+ public int doFinal(byte[] out, int outOff)
+ throws IllegalStateException, InvalidCipherTextException
+ {
+ if (totalLength == 0)
+ {
+ initCipher();
+ }
+
+ int extra = bufOff;
+ if (!forEncryption)
+ {
+ if (extra < macSize)
+ {
+ throw new InvalidCipherTextException("data too short");
+ }
+ extra -= macSize;
+ }
+
+ if (extra > 0)
+ {
+ gCTRPartial(bufBlock, 0, extra, out, outOff);
+ }
+
+ atLength += atBlockPos;
+
+ if (atLength > atLengthPre)
+ {
+ /*
+ * Some AAD was sent after the cipher started. We determine the difference b/w the hash value
+ * we actually used when the cipher started (S_atPre) and the final hash value calculated (S_at).
+ * Then we carry this difference forward by multiplying by H^c, where c is the number of (full or
+ * partial) cipher-text blocks produced, and adjust the current hash.
+ */
+
+ // Finish hash for partial AAD block
+ if (atBlockPos > 0)
+ {
+ gHASHPartial(S_at, atBlock, 0, atBlockPos);
+ }
+
+ // Find the difference between the AAD hashes
+ if (atLengthPre > 0)
+ {
+ xor(S_at, S_atPre);
+ }
+
+ // Number of cipher-text blocks produced
+ long c = ((totalLength * 8) + 127) >>> 7;
+
+ // Calculate the adjustment factor
+ byte[] H_c = new byte[16];
+ if (exp == null)
+ {
+ exp = new Tables1kGCMExponentiator();
+ exp.init(H);
+ }
+ exp.exponentiateX(c, H_c);
+
+ // Carry the difference forward
+ multiply(S_at, H_c);
+
+ // Adjust the current hash
+ xor(S, S_at);
+ }
+
+ // Final gHASH
+ byte[] X = new byte[BLOCK_SIZE];
+ Pack.longToBigEndian(atLength * 8, X, 0);
+ Pack.longToBigEndian(totalLength * 8, X, 8);
+
+ gHASHBlock(S, X);
+
+ // TODO Fix this if tagLength becomes configurable
+ // T = MSBt(GCTRk(J0,S))
+ byte[] tag = new byte[BLOCK_SIZE];
+ cipher.processBlock(J0, 0, tag, 0);
+ xor(tag, S);
+
+ int resultLen = extra;
+
+ // We place into macBlock our calculated value for T
+ this.macBlock = new byte[macSize];
+ System.arraycopy(tag, 0, macBlock, 0, macSize);
+
+ if (forEncryption)
+ {
+ // Append T to the message
+ System.arraycopy(macBlock, 0, out, outOff + bufOff, macSize);
+ resultLen += macSize;
+ }
+ else
+ {
+ // Retrieve the T value from the message and compare to calculated one
+ byte[] msgMac = new byte[macSize];
+ System.arraycopy(bufBlock, extra, msgMac, 0, macSize);
+ if (!Arrays.constantTimeAreEqual(this.macBlock, msgMac))
+ {
+ throw new InvalidCipherTextException("mac check in GCM failed");
+ }
+ }
+
+ reset(false);
+
+ return resultLen;
+ }
+
+ public void reset()
+ {
+ reset(true);
+ }
+
+ private void reset(
+ boolean clearMac)
+ {
+ cipher.reset();
+
+ S = new byte[BLOCK_SIZE];
+ S_at = new byte[BLOCK_SIZE];
+ S_atPre = new byte[BLOCK_SIZE];
+ atBlock = new byte[BLOCK_SIZE];
+ atBlockPos = 0;
+ atLength = 0;
+ atLengthPre = 0;
+ counter = Arrays.clone(J0);
+ bufOff = 0;
+ totalLength = 0;
+
+ if (bufBlock != null)
+ {
+ Arrays.fill(bufBlock, (byte)0);
+ }
+
+ if (clearMac)
+ {
+ macBlock = null;
+ }
+
+ if (initialAssociatedText != null)
+ {
+ processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+ }
+
+ private void gCTRBlock(byte[] block, byte[] out, int outOff)
+ {
+ byte[] tmp = getNextCounterBlock();
+
+ xor(tmp, block);
+ System.arraycopy(tmp, 0, out, outOff, BLOCK_SIZE);
+
+ gHASHBlock(S, forEncryption ? tmp : block);
+
+ totalLength += BLOCK_SIZE;
+ }
+
+ private void gCTRPartial(byte[] buf, int off, int len, byte[] out, int outOff)
+ {
+ byte[] tmp = getNextCounterBlock();
+
+ xor(tmp, buf, off, len);
+ System.arraycopy(tmp, 0, out, outOff, len);
+
+ gHASHPartial(S, forEncryption ? tmp : buf, 0, len);
+
+ totalLength += len;
+ }
+
+ private void gHASH(byte[] Y, byte[] b, int len)
+ {
+ for (int pos = 0; pos < len; pos += BLOCK_SIZE)
+ {
+ int num = Math.min(len - pos, BLOCK_SIZE);
+ gHASHPartial(Y, b, pos, num);
+ }
+ }
+
+ private void gHASHBlock(byte[] Y, byte[] b)
+ {
+ xor(Y, b);
+ multiplier.multiplyH(Y);
+ }
+
+ private void gHASHPartial(byte[] Y, byte[] b, int off, int len)
+ {
+ xor(Y, b, off, len);
+ multiplier.multiplyH(Y);
+ }
+
+ private byte[] getNextCounterBlock()
+ {
+ for (int i = 15; i >= 12; --i)
+ {
+ byte b = (byte)((counter[i] + 1) & 0xff);
+ counter[i] = b;
+
+ if (b != 0)
+ {
+ break;
+ }
+ }
+
+ byte[] tmp = new byte[BLOCK_SIZE];
+ // TODO Sure would be nice if ciphers could operate on int[]
+ cipher.processBlock(counter, 0, tmp, 0);
+ return tmp;
+ }
+
+ private static void multiply(byte[] block, byte[] val)
+ {
+ byte[] tmp = Arrays.clone(block);
+ byte[] c = new byte[16];
+
+ for (int i = 0; i < 16; ++i)
+ {
+ byte bits = val[i];
+ for (int j = 7; j >= 0; --j)
+ {
+ if ((bits & (1 << j)) != 0)
+ {
+ xor(c, tmp);
+ }
+
+ boolean lsb = (tmp[15] & 1) != 0;
+ shiftRight(tmp);
+ if (lsb)
+ {
+ // R = new byte[]{ 0xe1, ... };
+// xor(v, R);
+ tmp[0] ^= (byte)0xe1;
+ }
+ }
+ }
+
+ System.arraycopy(c, 0, block, 0, 16);
+ }
+
+ private static void shiftRight(byte[] block)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i] & 0xff;
+ block[i] = (byte) ((b >>> 1) | bit);
+ if (++i == 16)
+ {
+ break;
+ }
+ bit = (b & 1) << 7;
+ }
+ }
+
+ private static void xor(byte[] block, byte[] val)
+ {
+ for (int i = 15; i >= 0; --i)
+ {
+ block[i] ^= val[i];
+ }
+ }
+
+ private static void xor(byte[] block, byte[] val, int off, int len)
+ {
+ while (len-- > 0)
+ {
+ block[len] ^= val[off + len];
+ }
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java
new file mode 100644
index 00000000..1178974f
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/GOFBBlockCipher.java
@@ -0,0 +1,234 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * implements the GOST 28147 OFB counter mode (GCTR).
+ */
+public class GOFBBlockCipher
+ implements BlockCipher
+{
+ private byte[] IV;
+ private byte[] ofbV;
+ private byte[] ofbOutV;
+
+ private final int blockSize;
+ private final BlockCipher cipher;
+
+ boolean firstStep = true;
+ int N3;
+ int N4;
+ static final int C1 = 16843012; //00000001000000010000000100000100
+ static final int C2 = 16843009; //00000001000000010000000100000001
+
+
+ /**
+ * Basic constructor.
+ *
+ * @param cipher the block cipher to be used as the basis of the
+ * counter mode (must have a 64 bit block size).
+ */
+ public GOFBBlockCipher(
+ BlockCipher cipher)
+ {
+ this.cipher = cipher;
+ this.blockSize = cipher.getBlockSize();
+
+ if (blockSize != 8)
+ {
+ throw new IllegalArgumentException("GCTR only for 64 bit block ciphers");
+ }
+
+ this.IV = new byte[cipher.getBlockSize()];
+ this.ofbV = new byte[cipher.getBlockSize()];
+ this.ofbOutV = new byte[cipher.getBlockSize()];
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ /**
+ * Initialise the cipher and, possibly, the initialisation vector (IV).
+ * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+ * An IV which is too short is handled in FIPS compliant fashion.
+ *
+ * @param encrypting if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean encrypting, //ignored by this CTR mode
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ firstStep = true;
+ N3 = 0;
+ N4 = 0;
+
+ if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV ivParam = (ParametersWithIV)params;
+ byte[] iv = ivParam.getIV();
+
+ if (iv.length < IV.length)
+ {
+ // prepend the supplied IV with zeros (per FIPS PUB 81)
+ System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
+ for (int i = 0; i < IV.length - iv.length; i++)
+ {
+ IV[i] = 0;
+ }
+ }
+ else
+ {
+ System.arraycopy(iv, 0, IV, 0, IV.length);
+ }
+
+ reset();
+
+ // if params is null we reuse the current working key.
+ if (ivParam.getParameters() != null)
+ {
+ cipher.init(true, ivParam.getParameters());
+ }
+ }
+ else
+ {
+ reset();
+
+ // if params is null we reuse the current working key.
+ if (params != null)
+ {
+ cipher.init(true, params);
+ }
+ }
+ }
+
+ /**
+ * return the algorithm name and mode.
+ *
+ * @return the name of the underlying algorithm followed by "/GCTR"
+ * and the block size in bits
+ */
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/GCTR";
+ }
+
+
+ /**
+ * return the block size we are operating at (in bytes).
+ *
+ * @return the block size we are operating at (in bytes).
+ */
+ public int getBlockSize()
+ {
+ return blockSize;
+ }
+
+ /**
+ * Process one block of input from the array in and write it to
+ * the out array.
+ *
+ * @param in the array containing the input data.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the output data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int processBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ if (firstStep)
+ {
+ firstStep = false;
+ cipher.processBlock(ofbV, 0, ofbOutV, 0);
+ N3 = bytesToint(ofbOutV, 0);
+ N4 = bytesToint(ofbOutV, 4);
+ }
+ N3 += C2;
+ N4 += C1;
+ intTobytes(N3, ofbV, 0);
+ intTobytes(N4, ofbV, 4);
+
+ cipher.processBlock(ofbV, 0, ofbOutV, 0);
+
+ //
+ // XOR the ofbV with the plaintext producing the cipher text (and
+ // the next input block).
+ //
+ for (int i = 0; i < blockSize; i++)
+ {
+ out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]);
+ }
+
+ //
+ // change over the input block.
+ //
+ System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize);
+ System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize);
+
+ return blockSize;
+ }
+
+ /**
+ * reset the feedback vector back to the IV and reset the underlying
+ * cipher.
+ */
+ public void reset()
+ {
+ System.arraycopy(IV, 0, ofbV, 0, IV.length);
+
+ cipher.reset();
+ }
+
+ //array of bytes to type int
+ private int bytesToint(
+ byte[] in,
+ int inOff)
+ {
+ return ((in[inOff + 3] << 24) & 0xff000000) + ((in[inOff + 2] << 16) & 0xff0000) +
+ ((in[inOff + 1] << 8) & 0xff00) + (in[inOff] & 0xff);
+ }
+
+ //int to array of bytes
+ private void intTobytes(
+ int num,
+ byte[] out,
+ int outOff)
+ {
+ out[outOff + 3] = (byte)(num >>> 24);
+ out[outOff + 2] = (byte)(num >>> 16);
+ out[outOff + 1] = (byte)(num >>> 8);
+ out[outOff] = (byte)num;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
new file mode 100644
index 00000000..d4d29104
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/OCBBlockCipher.java
@@ -0,0 +1,581 @@
+package org.bouncycastle.crypto.modes;
+
+import java.util.Vector;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+import org.bouncycastle.crypto.params.AEADParameters;
+import org.bouncycastle.crypto.params.KeyParameter;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+import org.bouncycastle.util.Arrays;
+
+/**
+ * An implementation of the "work in progress" Internet-Draft <a
+ * href="http://tools.ietf.org/html/draft-irtf-cfrg-ocb-00">The OCB Authenticated-Encryption
+ * Algorithm</a>, licensed per:
+ * <p/>
+ * <blockquote> <a href="http://www.cs.ucdavis.edu/~rogaway/ocb/license1.pdf">License for
+ * Open-Source Software Implementations of OCB</a> (Jan 9, 2013) &mdash; &ldquo;License 1&rdquo; <br>
+ * Under this license, you are authorized to make, use, and distribute open-source software
+ * implementations of OCB. This license terminates for you if you sue someone over their open-source
+ * software implementation of OCB claiming that you have a patent covering their implementation.
+ * <p/>
+ * This is a non-binding summary of a legal document (the link above). The parameters of the license
+ * are specified in the license document and that document is controlling. </blockquote>
+ */
+public class OCBBlockCipher
+ implements AEADBlockCipher
+{
+
+ private static final int BLOCK_SIZE = 16;
+
+ private BlockCipher hashCipher;
+ private BlockCipher mainCipher;
+
+ /*
+ * CONFIGURATION
+ */
+ private boolean forEncryption;
+ private int macSize;
+ private byte[] initialAssociatedText;
+
+ /*
+ * KEY-DEPENDENT
+ */
+ // NOTE: elements are lazily calculated
+ private Vector L;
+ private byte[] L_Asterisk, L_Dollar;
+
+ /*
+ * NONCE-DEPENDENT
+ */
+ private byte[] OffsetMAIN_0;
+
+ /*
+ * PER-ENCRYPTION/DECRYPTION
+ */
+ private byte[] hashBlock, mainBlock;
+ private int hashBlockPos, mainBlockPos;
+ private long hashBlockCount, mainBlockCount;
+ private byte[] OffsetHASH;
+ private byte[] Sum;
+ private byte[] OffsetMAIN;
+ private byte[] Checksum;
+
+ // NOTE: The MAC value is preserved after doFinal
+ private byte[] macBlock;
+
+ public OCBBlockCipher(BlockCipher hashCipher, BlockCipher mainCipher)
+ {
+ if (hashCipher == null)
+ {
+ throw new IllegalArgumentException("'hashCipher' cannot be null");
+ }
+ if (hashCipher.getBlockSize() != BLOCK_SIZE)
+ {
+ throw new IllegalArgumentException("'hashCipher' must have a block size of "
+ + BLOCK_SIZE);
+ }
+ if (mainCipher == null)
+ {
+ throw new IllegalArgumentException("'mainCipher' cannot be null");
+ }
+ if (mainCipher.getBlockSize() != BLOCK_SIZE)
+ {
+ throw new IllegalArgumentException("'mainCipher' must have a block size of "
+ + BLOCK_SIZE);
+ }
+
+ if (!hashCipher.getAlgorithmName().equals(mainCipher.getAlgorithmName()))
+ {
+ throw new IllegalArgumentException(
+ "'hashCipher' and 'mainCipher' must be the same algorithm");
+ }
+
+ this.hashCipher = hashCipher;
+ this.mainCipher = mainCipher;
+ }
+
+ public BlockCipher getUnderlyingCipher()
+ {
+ return mainCipher;
+ }
+
+ public String getAlgorithmName()
+ {
+ return mainCipher.getAlgorithmName() + "/OCB";
+ }
+
+ public void init(boolean forEncryption, CipherParameters parameters)
+ throws IllegalArgumentException
+ {
+
+ this.forEncryption = forEncryption;
+ this.macBlock = null;
+
+ KeyParameter keyParameter;
+
+ byte[] N;
+ if (parameters instanceof AEADParameters)
+ {
+ AEADParameters aeadParameters = (AEADParameters)parameters;
+
+ N = aeadParameters.getNonce();
+ initialAssociatedText = aeadParameters.getAssociatedText();
+
+ int macSizeBits = aeadParameters.getMacSize();
+ if (macSizeBits < 64 || macSizeBits > 128 || macSizeBits % 8 != 0)
+ {
+ throw new IllegalArgumentException("Invalid value for MAC size: " + macSizeBits);
+ }
+
+ macSize = macSizeBits / 8;
+ keyParameter = aeadParameters.getKey();
+ }
+ else if (parameters instanceof ParametersWithIV)
+ {
+ ParametersWithIV parametersWithIV = (ParametersWithIV)parameters;
+
+ N = parametersWithIV.getIV();
+ initialAssociatedText = null;
+ macSize = 16;
+ keyParameter = (KeyParameter)parametersWithIV.getParameters();
+ }
+ else
+ {
+ throw new IllegalArgumentException("invalid parameters passed to OCB");
+ }
+
+ this.hashBlock = new byte[16];
+ this.mainBlock = new byte[forEncryption ? BLOCK_SIZE : (BLOCK_SIZE + macSize)];
+
+ if (N == null)
+ {
+ N = new byte[0];
+ }
+
+ if (N.length > 16 || (N.length == 16 && (N[0] & 0x80) != 0))
+ {
+ /*
+ * NOTE: We don't just ignore bit 128 because it would hide from the caller the fact
+ * that two nonces differing only in bit 128 are not different.
+ */
+ throw new IllegalArgumentException("IV must be no more than 127 bits");
+ }
+
+ /*
+ * KEY-DEPENDENT INITIALISATION
+ */
+
+ // if keyParam is null we're reusing the last key.
+ if (keyParameter != null)
+ {
+ // TODO
+ }
+
+ // hashCipher always used in forward mode
+ hashCipher.init(true, keyParameter);
+ mainCipher.init(forEncryption, keyParameter);
+
+ this.L_Asterisk = new byte[16];
+ hashCipher.processBlock(L_Asterisk, 0, L_Asterisk, 0);
+
+ this.L_Dollar = OCB_double(L_Asterisk);
+
+ this.L = new Vector();
+ this.L.addElement(OCB_double(L_Dollar));
+
+ /*
+ * NONCE-DEPENDENT AND PER-ENCRYPTION/DECRYPTION INITIALISATION
+ */
+
+ byte[] nonce = new byte[16];
+ System.arraycopy(N, 0, nonce, nonce.length - N.length, N.length);
+ if (N.length == 16)
+ {
+ nonce[0] &= 0x80;
+ }
+ else
+ {
+ nonce[15 - N.length] = 1;
+ }
+
+ int bottom = nonce[15] & 0x3F;
+ // System.out.println("bottom: " + bottom);
+
+ byte[] Ktop = new byte[16];
+ nonce[15] &= 0xC0;
+ hashCipher.processBlock(nonce, 0, Ktop, 0);
+
+ byte[] Stretch = new byte[24];
+ System.arraycopy(Ktop, 0, Stretch, 0, 16);
+ for (int i = 0; i < 8; ++i)
+ {
+ Stretch[16 + i] = (byte)(Ktop[i] ^ Ktop[i + 1]);
+ }
+
+ this.OffsetMAIN_0 = new byte[16];
+ int bits = bottom % 8, bytes = bottom / 8;
+ if (bits == 0)
+ {
+ System.arraycopy(Stretch, bytes, OffsetMAIN_0, 0, 16);
+ }
+ else
+ {
+ for (int i = 0; i < 16; ++i)
+ {
+ int b1 = Stretch[bytes] & 0xff;
+ int b2 = Stretch[++bytes] & 0xff;
+ this.OffsetMAIN_0[i] = (byte)((b1 << bits) | (b2 >>> (8 - bits)));
+ }
+ }
+
+ this.hashBlockPos = 0;
+ this.mainBlockPos = 0;
+
+ this.hashBlockCount = 0;
+ this.mainBlockCount = 0;
+
+ this.OffsetHASH = new byte[16];
+ this.Sum = new byte[16];
+ this.OffsetMAIN = Arrays.clone(this.OffsetMAIN_0);
+ this.Checksum = new byte[16];
+
+ if (initialAssociatedText != null)
+ {
+ processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+ }
+
+ public byte[] getMac()
+ {
+ return Arrays.clone(macBlock);
+ }
+
+ public int getOutputSize(int len)
+ {
+ int totalData = len + mainBlockPos;
+ if (forEncryption)
+ {
+ return totalData + macSize;
+ }
+ return totalData < macSize ? 0 : totalData - macSize;
+ }
+
+ public int getUpdateOutputSize(int len)
+ {
+ int totalData = len + mainBlockPos;
+ if (!forEncryption)
+ {
+ if (totalData < macSize)
+ {
+ return 0;
+ }
+ totalData -= macSize;
+ }
+ return totalData - totalData % BLOCK_SIZE;
+ }
+
+ public void processAADByte(byte input)
+ {
+ hashBlock[hashBlockPos] = input;
+ if (++hashBlockPos == hashBlock.length)
+ {
+ processHashBlock();
+ }
+ }
+
+ public void processAADBytes(byte[] input, int off, int len)
+ {
+ for (int i = 0; i < len; ++i)
+ {
+ hashBlock[hashBlockPos] = input[off + i];
+ if (++hashBlockPos == hashBlock.length)
+ {
+ processHashBlock();
+ }
+ }
+ }
+
+ public int processByte(byte input, byte[] output, int outOff)
+ throws DataLengthException
+ {
+ mainBlock[mainBlockPos] = input;
+ if (++mainBlockPos == mainBlock.length)
+ {
+ processMainBlock(output, outOff);
+ return BLOCK_SIZE;
+ }
+ return 0;
+ }
+
+ public int processBytes(byte[] input, int inOff, int len, byte[] output, int outOff)
+ throws DataLengthException
+ {
+
+ int resultLen = 0;
+
+ for (int i = 0; i < len; ++i)
+ {
+ mainBlock[mainBlockPos] = input[inOff + i];
+ if (++mainBlockPos == mainBlock.length)
+ {
+ processMainBlock(output, outOff + resultLen);
+ resultLen += BLOCK_SIZE;
+ }
+ }
+
+ return resultLen;
+ }
+
+ public int doFinal(byte[] output, int outOff)
+ throws IllegalStateException,
+ InvalidCipherTextException
+ {
+
+ /*
+ * For decryption, get the tag from the end of the message
+ */
+ byte[] tag = null;
+ if (!forEncryption)
+ {
+ if (mainBlockPos < macSize)
+ {
+ throw new InvalidCipherTextException("data too short");
+ }
+ mainBlockPos -= macSize;
+ tag = new byte[macSize];
+ System.arraycopy(mainBlock, mainBlockPos, tag, 0, macSize);
+ }
+
+ /*
+ * HASH: Process any final partial block; compute final hash value
+ */
+ if (hashBlockPos > 0)
+ {
+ OCB_extend(hashBlock, hashBlockPos);
+ updateHASH(L_Asterisk);
+ }
+
+ /*
+ * OCB-ENCRYPT/OCB-DECRYPT: Process any final partial block
+ */
+ if (mainBlockPos > 0)
+ {
+ if (forEncryption)
+ {
+ OCB_extend(mainBlock, mainBlockPos);
+ xor(Checksum, mainBlock);
+ }
+
+ xor(OffsetMAIN, L_Asterisk);
+
+ byte[] Pad = new byte[16];
+ hashCipher.processBlock(OffsetMAIN, 0, Pad, 0);
+
+ xor(mainBlock, Pad);
+
+ System.arraycopy(mainBlock, 0, output, outOff, mainBlockPos);
+
+ if (!forEncryption)
+ {
+ OCB_extend(mainBlock, mainBlockPos);
+ xor(Checksum, mainBlock);
+ }
+ }
+
+ /*
+ * OCB-ENCRYPT/OCB-DECRYPT: Compute raw tag
+ */
+ xor(Checksum, OffsetMAIN);
+ xor(Checksum, L_Dollar);
+ hashCipher.processBlock(Checksum, 0, Checksum, 0);
+ xor(Checksum, Sum);
+
+ this.macBlock = new byte[macSize];
+ System.arraycopy(Checksum, 0, macBlock, 0, macSize);
+
+ /*
+ * Validate or append tag and reset this cipher for the next run
+ */
+ int resultLen = mainBlockPos;
+
+ if (forEncryption)
+ {
+ // Append tag to the message
+ System.arraycopy(macBlock, 0, output, outOff + resultLen, macSize);
+ resultLen += macSize;
+ }
+ else
+ {
+ // Compare the tag from the message with the calculated one
+ if (!Arrays.constantTimeAreEqual(macBlock, tag))
+ {
+ throw new InvalidCipherTextException("mac check in OCB failed");
+ }
+ }
+
+ reset(false);
+
+ return resultLen;
+ }
+
+ public void reset()
+ {
+ reset(true);
+ }
+
+ protected void clear(byte[] bs)
+ {
+ if (bs != null)
+ {
+ Arrays.fill(bs, (byte)0);
+ }
+ }
+
+ protected byte[] getLSub(int n)
+ {
+ while (n >= L.size())
+ {
+ L.addElement(OCB_double((byte[])L.lastElement()));
+ }
+ return (byte[])L.elementAt(n);
+ }
+
+ protected void processHashBlock()
+ {
+ /*
+ * HASH: Process any whole blocks
+ */
+ updateHASH(getLSub(OCB_ntz(++hashBlockCount)));
+ hashBlockPos = 0;
+ }
+
+ protected void processMainBlock(byte[] output, int outOff)
+ {
+ /*
+ * OCB-ENCRYPT/OCB-DECRYPT: Process any whole blocks
+ */
+
+ if (forEncryption)
+ {
+ xor(Checksum, mainBlock);
+ mainBlockPos = 0;
+ }
+
+ xor(OffsetMAIN, getLSub(OCB_ntz(++mainBlockCount)));
+
+ xor(mainBlock, OffsetMAIN);
+ mainCipher.processBlock(mainBlock, 0, mainBlock, 0);
+ xor(mainBlock, OffsetMAIN);
+
+ System.arraycopy(mainBlock, 0, output, outOff, 16);
+
+ if (!forEncryption)
+ {
+ xor(Checksum, mainBlock);
+ System.arraycopy(mainBlock, BLOCK_SIZE, mainBlock, 0, macSize);
+ mainBlockPos = macSize;
+ }
+ }
+
+ protected void reset(boolean clearMac)
+ {
+
+ hashCipher.reset();
+ mainCipher.reset();
+
+ clear(hashBlock);
+ clear(mainBlock);
+
+ hashBlockPos = 0;
+ mainBlockPos = 0;
+
+ hashBlockCount = 0;
+ mainBlockCount = 0;
+
+ clear(OffsetHASH);
+ clear(Sum);
+ System.arraycopy(OffsetMAIN_0, 0, OffsetMAIN, 0, 16);
+ clear(Checksum);
+
+ if (clearMac)
+ {
+ macBlock = null;
+ }
+
+ if (initialAssociatedText != null)
+ {
+ processAADBytes(initialAssociatedText, 0, initialAssociatedText.length);
+ }
+ }
+
+ protected void updateHASH(byte[] LSub)
+ {
+ xor(OffsetHASH, LSub);
+ xor(hashBlock, OffsetHASH);
+ hashCipher.processBlock(hashBlock, 0, hashBlock, 0);
+ xor(Sum, hashBlock);
+ }
+
+ protected static byte[] OCB_double(byte[] block)
+ {
+ byte[] result = new byte[16];
+ int carry = shiftLeft(block, result);
+
+ /*
+ * NOTE: This construction is an attempt at a constant-time implementation.
+ */
+ result[15] ^= (0x87 >>> ((1 - carry) << 3));
+
+ return result;
+ }
+
+ protected static void OCB_extend(byte[] block, int pos)
+ {
+ block[pos] = (byte)0x80;
+ while (++pos < 16)
+ {
+ block[pos] = 0;
+ }
+ }
+
+ protected static int OCB_ntz(long x)
+ {
+ if (x == 0)
+ {
+ return 64;
+ }
+
+ int n = 0;
+ while ((x & 1L) == 0L)
+ {
+ ++n;
+ x >>= 1;
+ }
+ return n;
+ }
+
+ protected static int shiftLeft(byte[] block, byte[] output)
+ {
+ int i = 16;
+ int bit = 0;
+ while (--i >= 0)
+ {
+ int b = block[i] & 0xff;
+ output[i] = (byte)((b << 1) | bit);
+ bit = (b >>> 7) & 1;
+ }
+ return bit;
+ }
+
+ protected static void xor(byte[] block, byte[] val)
+ {
+ for (int i = 15; i >= 0; --i)
+ {
+ block[i] ^= val[i];
+ }
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java
new file mode 100644
index 00000000..5297698f
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/OFBBlockCipher.java
@@ -0,0 +1,187 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * implements a Output-FeedBack (OFB) mode on top of a simple cipher.
+ */
+public class OFBBlockCipher
+ implements BlockCipher
+{
+ private byte[] IV;
+ private byte[] ofbV;
+ private byte[] ofbOutV;
+
+ private final int blockSize;
+ private final BlockCipher cipher;
+
+ /**
+ * Basic constructor.
+ *
+ * @param cipher the block cipher to be used as the basis of the
+ * feedback mode.
+ * @param blockSize the block size in bits (note: a multiple of 8)
+ */
+ public OFBBlockCipher(
+ BlockCipher cipher,
+ int blockSize)
+ {
+ this.cipher = cipher;
+ this.blockSize = blockSize / 8;
+
+ this.IV = new byte[cipher.getBlockSize()];
+ this.ofbV = new byte[cipher.getBlockSize()];
+ this.ofbOutV = new byte[cipher.getBlockSize()];
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ /**
+ * Initialise the cipher and, possibly, the initialisation vector (IV).
+ * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+ * An IV which is too short is handled in FIPS compliant fashion.
+ *
+ * @param encrypting if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean encrypting, //ignored by this OFB mode
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV ivParam = (ParametersWithIV)params;
+ byte[] iv = ivParam.getIV();
+
+ if (iv.length < IV.length)
+ {
+ // prepend the supplied IV with zeros (per FIPS PUB 81)
+ System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
+ for (int i = 0; i < IV.length - iv.length; i++)
+ {
+ IV[i] = 0;
+ }
+ }
+ else
+ {
+ System.arraycopy(iv, 0, IV, 0, IV.length);
+ }
+
+ reset();
+
+ // if null it's an IV changed only.
+ if (ivParam.getParameters() != null)
+ {
+ cipher.init(true, ivParam.getParameters());
+ }
+ }
+ else
+ {
+ reset();
+
+ // if it's null, key is to be reused.
+ if (params != null)
+ {
+ cipher.init(true, params);
+ }
+ }
+ }
+
+ /**
+ * return the algorithm name and mode.
+ *
+ * @return the name of the underlying algorithm followed by "/OFB"
+ * and the block size in bits
+ */
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/OFB" + (blockSize * 8);
+ }
+
+
+ /**
+ * return the block size we are operating at (in bytes).
+ *
+ * @return the block size we are operating at (in bytes).
+ */
+ public int getBlockSize()
+ {
+ return blockSize;
+ }
+
+ /**
+ * Process one block of input from the array in and write it to
+ * the out array.
+ *
+ * @param in the array containing the input data.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the output data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int processBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ cipher.processBlock(ofbV, 0, ofbOutV, 0);
+
+ //
+ // XOR the ofbV with the plaintext producing the cipher text (and
+ // the next input block).
+ //
+ for (int i = 0; i < blockSize; i++)
+ {
+ out[outOff + i] = (byte)(ofbOutV[i] ^ in[inOff + i]);
+ }
+
+ //
+ // change over the input block.
+ //
+ System.arraycopy(ofbV, blockSize, ofbV, 0, ofbV.length - blockSize);
+ System.arraycopy(ofbOutV, 0, ofbV, ofbV.length - blockSize, blockSize);
+
+ return blockSize;
+ }
+
+ /**
+ * reset the feedback vector back to the IV and reset the underlying
+ * cipher.
+ */
+ public void reset()
+ {
+ System.arraycopy(IV, 0, ofbV, 0, IV.length);
+
+ cipher.reset();
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java
new file mode 100644
index 00000000..e48731b3
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/OpenPGPCFBBlockCipher.java
@@ -0,0 +1,312 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+
+/**
+ * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode
+ * on top of a simple cipher. This class assumes the IV has been prepended
+ * to the data stream already, and just accomodates the reset after
+ * (blockSize + 2) bytes have been read.
+ * <p>
+ * For further info see <a href="http://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>.
+ */
+public class OpenPGPCFBBlockCipher
+ implements BlockCipher
+{
+ private byte[] IV;
+ private byte[] FR;
+ private byte[] FRE;
+
+ private BlockCipher cipher;
+
+ private int count;
+ private int blockSize;
+ private boolean forEncryption;
+
+ /**
+ * Basic constructor.
+ *
+ * @param cipher the block cipher to be used as the basis of the
+ * feedback mode.
+ */
+ public OpenPGPCFBBlockCipher(
+ BlockCipher cipher)
+ {
+ this.cipher = cipher;
+
+ this.blockSize = cipher.getBlockSize();
+ this.IV = new byte[blockSize];
+ this.FR = new byte[blockSize];
+ this.FRE = new byte[blockSize];
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ /**
+ * return the algorithm name and mode.
+ *
+ * @return the name of the underlying algorithm followed by "/PGPCFB"
+ * and the block size in bits.
+ */
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/OpenPGPCFB";
+ }
+
+ /**
+ * return the block size we are operating at.
+ *
+ * @return the block size we are operating at (in bytes).
+ */
+ public int getBlockSize()
+ {
+ return cipher.getBlockSize();
+ }
+
+ /**
+ * Process one block of input from the array in and write it to
+ * the out array.
+ *
+ * @param in the array containing the input data.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the output data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int processBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ return (forEncryption) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
+ }
+
+ /**
+ * reset the chaining vector back to the IV and reset the underlying
+ * cipher.
+ */
+ public void reset()
+ {
+ count = 0;
+
+ System.arraycopy(IV, 0, FR, 0, FR.length);
+
+ cipher.reset();
+ }
+
+ /**
+ * Initialise the cipher and, possibly, the initialisation vector (IV).
+ * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+ * An IV which is too short is handled in FIPS compliant fashion.
+ *
+ * @param forEncryption if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean forEncryption,
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ this.forEncryption = forEncryption;
+
+ reset();
+
+ cipher.init(true, params);
+ }
+
+ /**
+ * Encrypt one byte of data according to CFB mode.
+ * @param data the byte to encrypt
+ * @param blockOff offset in the current block
+ * @return the encrypted byte
+ */
+ private byte encryptByte(byte data, int blockOff)
+ {
+ return (byte)(FRE[blockOff] ^ data);
+ }
+
+ /**
+ * Do the appropriate processing for CFB IV mode encryption.
+ *
+ * @param in the array containing the data to be encrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int encryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ if (count > blockSize)
+ {
+ FR[blockSize - 2] = out[outOff] = encryptByte(in[inOff], blockSize - 2);
+ FR[blockSize - 1] = out[outOff + 1] = encryptByte(in[inOff + 1], blockSize - 1);
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 2; n < blockSize; n++)
+ {
+ FR[n - 2] = out[outOff + n] = encryptByte(in[inOff + n], n - 2);
+ }
+ }
+ else if (count == 0)
+ {
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ FR[n] = out[outOff + n] = encryptByte(in[inOff + n], n);
+ }
+
+ count += blockSize;
+ }
+ else if (count == blockSize)
+ {
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ out[outOff] = encryptByte(in[inOff], 0);
+ out[outOff + 1] = encryptByte(in[inOff + 1], 1);
+
+ //
+ // do reset
+ //
+ System.arraycopy(FR, 2, FR, 0, blockSize - 2);
+ System.arraycopy(out, outOff, FR, blockSize - 2, 2);
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 2; n < blockSize; n++)
+ {
+ FR[n - 2] = out[outOff + n] = encryptByte(in[inOff + n], n - 2);
+ }
+
+ count += blockSize;
+ }
+
+ return blockSize;
+ }
+
+ /**
+ * Do the appropriate processing for CFB IV mode decryption.
+ *
+ * @param in the array containing the data to be decrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int decryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ if (count > blockSize)
+ {
+ byte inVal = in[inOff];
+ FR[blockSize - 2] = inVal;
+ out[outOff] = encryptByte(inVal, blockSize - 2);
+
+ inVal = in[inOff + 1];
+ FR[blockSize - 1] = inVal;
+ out[outOff + 1] = encryptByte(inVal, blockSize - 1);
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 2; n < blockSize; n++)
+ {
+ inVal = in[inOff + n];
+ FR[n - 2] = inVal;
+ out[outOff + n] = encryptByte(inVal, n - 2);
+ }
+ }
+ else if (count == 0)
+ {
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ FR[n] = in[inOff + n];
+ out[n] = encryptByte(in[inOff + n], n);
+ }
+
+ count += blockSize;
+ }
+ else if (count == blockSize)
+ {
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ byte inVal1 = in[inOff];
+ byte inVal2 = in[inOff + 1];
+ out[outOff ] = encryptByte(inVal1, 0);
+ out[outOff + 1] = encryptByte(inVal2, 1);
+
+ System.arraycopy(FR, 2, FR, 0, blockSize - 2);
+
+ FR[blockSize - 2] = inVal1;
+ FR[blockSize - 1] = inVal2;
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 2; n < blockSize; n++)
+ {
+ byte inVal = in[inOff + n];
+ FR[n - 2] = inVal;
+ out[outOff + n] = encryptByte(inVal, n - 2);
+ }
+
+ count += blockSize;
+ }
+
+ return blockSize;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
new file mode 100644
index 00000000..18e612b3
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/PGPCFBBlockCipher.java
@@ -0,0 +1,450 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * Implements OpenPGP's rather strange version of Cipher-FeedBack (CFB) mode on top of a simple cipher. For further info see <a href="http://www.ietf.org/rfc/rfc2440.html">RFC 2440</a>.
+ */
+public class PGPCFBBlockCipher
+ implements BlockCipher
+{
+ private byte[] IV;
+ private byte[] FR;
+ private byte[] FRE;
+ private byte[] tmp;
+
+ private BlockCipher cipher;
+
+ private int count;
+ private int blockSize;
+ private boolean forEncryption;
+
+ private boolean inlineIv; // if false we don't need to prepend an IV
+
+ /**
+ * Basic constructor.
+ *
+ * @param cipher the block cipher to be used as the basis of the
+ * feedback mode.
+ * @param inlineIv if true this is for PGP CFB with a prepended iv.
+ */
+ public PGPCFBBlockCipher(
+ BlockCipher cipher,
+ boolean inlineIv)
+ {
+ this.cipher = cipher;
+ this.inlineIv = inlineIv;
+
+ this.blockSize = cipher.getBlockSize();
+ this.IV = new byte[blockSize];
+ this.FR = new byte[blockSize];
+ this.FRE = new byte[blockSize];
+ this.tmp = new byte[blockSize];
+ }
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+ /**
+ * return the algorithm name and mode.
+ *
+ * @return the name of the underlying algorithm followed by "/PGPCFB"
+ * and the block size in bits.
+ */
+ public String getAlgorithmName()
+ {
+ if (inlineIv)
+ {
+ return cipher.getAlgorithmName() + "/PGPCFBwithIV";
+ }
+ else
+ {
+ return cipher.getAlgorithmName() + "/PGPCFB";
+ }
+ }
+
+ /**
+ * return the block size we are operating at.
+ *
+ * @return the block size we are operating at (in bytes).
+ */
+ public int getBlockSize()
+ {
+ return cipher.getBlockSize();
+ }
+
+ /**
+ * Process one block of input from the array in and write it to
+ * the out array.
+ *
+ * @param in the array containing the input data.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the output data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ public int processBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if (inlineIv)
+ {
+ return (forEncryption) ? encryptBlockWithIV(in, inOff, out, outOff) : decryptBlockWithIV(in, inOff, out, outOff);
+ }
+ else
+ {
+ return (forEncryption) ? encryptBlock(in, inOff, out, outOff) : decryptBlock(in, inOff, out, outOff);
+ }
+ }
+
+ /**
+ * reset the chaining vector back to the IV and reset the underlying
+ * cipher.
+ */
+ public void reset()
+ {
+ count = 0;
+
+ for (int i = 0; i != FR.length; i++)
+ {
+ if (inlineIv)
+ {
+ FR[i] = 0;
+ }
+ else
+ {
+ FR[i] = IV[i]; // if simple mode, key is IV (even if this is zero)
+ }
+ }
+
+ cipher.reset();
+ }
+
+ /**
+ * Initialise the cipher and, possibly, the initialisation vector (IV).
+ * If an IV isn't passed as part of the parameter, the IV will be all zeros.
+ * An IV which is too short is handled in FIPS compliant fashion.
+ *
+ * @param forEncryption if true the cipher is initialised for
+ * encryption, if false for decryption.
+ * @param params the key and other data required by the cipher.
+ * @exception IllegalArgumentException if the params argument is
+ * inappropriate.
+ */
+ public void init(
+ boolean forEncryption,
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ this.forEncryption = forEncryption;
+
+ if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV ivParam = (ParametersWithIV)params;
+ byte[] iv = ivParam.getIV();
+
+ if (iv.length < IV.length)
+ {
+ // prepend the supplied IV with zeros (per FIPS PUB 81)
+ System.arraycopy(iv, 0, IV, IV.length - iv.length, iv.length);
+ for (int i = 0; i < IV.length - iv.length; i++)
+ {
+ IV[i] = 0;
+ }
+ }
+ else
+ {
+ System.arraycopy(iv, 0, IV, 0, IV.length);
+ }
+
+ reset();
+
+ cipher.init(true, ivParam.getParameters());
+ }
+ else
+ {
+ reset();
+
+ cipher.init(true, params);
+ }
+ }
+
+ /**
+ * Encrypt one byte of data according to CFB mode.
+ * @param data the byte to encrypt
+ * @param blockOff where am i in the current block, determines when to resync the block
+ * @returns the encrypted byte
+ */
+ private byte encryptByte(byte data, int blockOff)
+ {
+ return (byte)(FRE[blockOff] ^ data);
+ }
+
+ /**
+ * Do the appropriate processing for CFB IV mode encryption.
+ *
+ * @param in the array containing the data to be encrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int encryptBlockWithIV(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ if (count == 0)
+ {
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ out[outOff + n] = encryptByte(IV[n], n);
+ }
+
+ System.arraycopy(out, outOff, FR, 0, blockSize);
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ out[outOff + blockSize] = encryptByte(IV[blockSize - 2], 0);
+ out[outOff + blockSize + 1] = encryptByte(IV[blockSize - 1], 1);
+
+ System.arraycopy(out, outOff + 2, FR, 0, blockSize);
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ out[outOff + blockSize + 2 + n] = encryptByte(in[inOff + n], n);
+ }
+
+ System.arraycopy(out, outOff + blockSize + 2, FR, 0, blockSize);
+
+ count += 2 * blockSize + 2;
+
+ return 2 * blockSize + 2;
+ }
+ else if (count >= blockSize + 2)
+ {
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ out[outOff + n] = encryptByte(in[inOff + n], n);
+ }
+
+ System.arraycopy(out, outOff, FR, 0, blockSize);
+ }
+
+ return blockSize;
+ }
+
+ /**
+ * Do the appropriate processing for CFB IV mode decryption.
+ *
+ * @param in the array containing the data to be decrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int decryptBlockWithIV(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ if (count == 0)
+ {
+ for (int n = 0; n < blockSize; n++)
+ {
+ FR[n] = in[inOff + n];
+ }
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ count += blockSize;
+
+ return 0;
+ }
+ else if (count == blockSize)
+ {
+ // copy in buffer so that this mode works if in and out are the same
+ System.arraycopy(in, inOff, tmp, 0, blockSize);
+
+ System.arraycopy(FR, 2, FR, 0, blockSize - 2);
+
+ FR[blockSize - 2] = tmp[0];
+ FR[blockSize - 1] = tmp[1];
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize - 2; n++)
+ {
+ out[outOff + n] = encryptByte(tmp[n + 2], n);
+ }
+
+ System.arraycopy(tmp, 2, FR, 0, blockSize - 2);
+
+ count += 2;
+
+ return blockSize - 2;
+ }
+ else if (count >= blockSize + 2)
+ {
+ // copy in buffer so that this mode works if in and out are the same
+ System.arraycopy(in, inOff, tmp, 0, blockSize);
+
+ out[outOff + 0] = encryptByte(tmp[0], blockSize - 2);
+ out[outOff + 1] = encryptByte(tmp[1], blockSize - 1);
+
+ System.arraycopy(tmp, 0, FR, blockSize - 2, 2);
+
+ cipher.processBlock(FR, 0, FRE, 0);
+
+ for (int n = 0; n < blockSize - 2; n++)
+ {
+ out[outOff + n + 2] = encryptByte(tmp[n + 2], n);
+ }
+
+ System.arraycopy(tmp, 2, FR, 0, blockSize - 2);
+
+ }
+
+ return blockSize;
+ }
+
+ /**
+ * Do the appropriate processing for CFB mode encryption.
+ *
+ * @param in the array containing the data to be encrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int encryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ cipher.processBlock(FR, 0, FRE, 0);
+ for (int n = 0; n < blockSize; n++)
+ {
+ out[outOff + n] = encryptByte(in[inOff + n], n);
+ }
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ FR[n] = out[outOff + n];
+ }
+
+ return blockSize;
+
+ }
+
+ /**
+ * Do the appropriate processing for CFB mode decryption.
+ *
+ * @param in the array containing the data to be decrypted.
+ * @param inOff offset into the in array the data starts at.
+ * @param out the array the encrypted data will be copied into.
+ * @param outOff the offset into the out array the output will start at.
+ * @exception DataLengthException if there isn't enough data in in, or
+ * space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ * @return the number of bytes processed and produced.
+ */
+ private int decryptBlock(
+ byte[] in,
+ int inOff,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if ((inOff + blockSize) > in.length)
+ {
+ throw new DataLengthException("input buffer too short");
+ }
+
+ if ((outOff + blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ cipher.processBlock(FR, 0, FRE, 0);
+ for (int n = 0; n < blockSize; n++)
+ {
+ out[outOff + n] = encryptByte(in[inOff + n], n);
+ }
+
+ for (int n = 0; n < blockSize; n++)
+ {
+ FR[n] = in[inOff + n];
+ }
+
+ return blockSize;
+
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java
new file mode 100644
index 00000000..f15ed673
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/PaddedBlockCipher.java
@@ -0,0 +1,253 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.BufferedBlockCipher;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.InvalidCipherTextException;
+
+/**
+ * A wrapper class that allows block ciphers to be used to process data in
+ * a piecemeal fashion with PKCS5/PKCS7 padding. The PaddedBlockCipher
+ * outputs a block only when the buffer is full and more data is being added,
+ * or on a doFinal (unless the current block in the buffer is a pad block).
+ * The padding mechanism used is the one outlined in PKCS5/PKCS7.
+ *
+ * @deprecated use org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher instead.
+ */
+public class PaddedBlockCipher
+ extends BufferedBlockCipher
+{
+ /**
+ * Create a buffered block cipher with, or without, padding.
+ *
+ * @param cipher the underlying block cipher this buffering object wraps.
+ */
+ public PaddedBlockCipher(
+ BlockCipher cipher)
+ {
+ this.cipher = cipher;
+
+ buf = new byte[cipher.getBlockSize()];
+ bufOff = 0;
+ }
+
+ /**
+ * return the size of the output buffer required for an update plus a
+ * doFinal with an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to update and doFinal
+ * with len bytes of input.
+ */
+ public int getOutputSize(
+ int len)
+ {
+ int total = len + bufOff;
+ int leftOver = total % buf.length;
+
+ if (leftOver == 0)
+ {
+ if (forEncryption)
+ {
+ return total + buf.length;
+ }
+
+ return total;
+ }
+
+ return total - leftOver + buf.length;
+ }
+
+ /**
+ * return the size of the output buffer required for an update
+ * an input of len bytes.
+ *
+ * @param len the length of the input.
+ * @return the space required to accommodate a call to update
+ * with len bytes of input.
+ */
+ public int getUpdateOutputSize(
+ int len)
+ {
+ int total = len + bufOff;
+ int leftOver = total % buf.length;
+
+ if (leftOver == 0)
+ {
+ return total - buf.length;
+ }
+
+ return total - leftOver;
+ }
+
+ /**
+ * process a single byte, producing an output block if neccessary.
+ *
+ * @param in the input byte.
+ * @param out the space for any output that might be produced.
+ * @param outOff the offset from which the output will be copied.
+ * @exception DataLengthException if there isn't enough space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ */
+ public int processByte(
+ byte in,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ int resultLen = 0;
+
+ if (bufOff == buf.length)
+ {
+ resultLen = cipher.processBlock(buf, 0, out, outOff);
+ bufOff = 0;
+ }
+
+ buf[bufOff++] = in;
+
+ return resultLen;
+ }
+
+ /**
+ * process an array of bytes, producing output if necessary.
+ *
+ * @param in the input byte array.
+ * @param inOff the offset at which the input data starts.
+ * @param len the number of bytes to be copied out of the input array.
+ * @param out the space for any output that might be produced.
+ * @param outOff the offset from which the output will be copied.
+ * @exception DataLengthException if there isn't enough space in out.
+ * @exception IllegalStateException if the cipher isn't initialised.
+ */
+ public int processBytes(
+ byte[] in,
+ int inOff,
+ int len,
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ if (len < 0)
+ {
+ throw new IllegalArgumentException("Can't have a negative input length!");
+ }
+
+ int blockSize = getBlockSize();
+ int length = getUpdateOutputSize(len);
+
+ if (length > 0)
+ {
+ if ((outOff + length) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+ }
+
+ int resultLen = 0;
+ int gapLen = buf.length - bufOff;
+
+ if (len > gapLen)
+ {
+ System.arraycopy(in, inOff, buf, bufOff, gapLen);
+
+ resultLen += cipher.processBlock(buf, 0, out, outOff);
+
+ bufOff = 0;
+ len -= gapLen;
+ inOff += gapLen;
+
+ while (len > buf.length)
+ {
+ resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen);
+
+ len -= blockSize;
+ inOff += blockSize;
+ }
+ }
+
+ System.arraycopy(in, inOff, buf, bufOff, len);
+
+ bufOff += len;
+
+ return resultLen;
+ }
+
+ /**
+ * Process the last block in the buffer. If the buffer is currently
+ * full and padding needs to be added a call to doFinal will produce
+ * 2 * getBlockSize() bytes.
+ *
+ * @param out the array the block currently being held is copied into.
+ * @param outOff the offset at which the copying starts.
+ * @exception DataLengthException if there is insufficient space in out for
+ * the output or we are decrypting and the input is not block size aligned.
+ * @exception IllegalStateException if the underlying cipher is not
+ * initialised.
+ * @exception InvalidCipherTextException if padding is expected and not found.
+ */
+ public int doFinal(
+ byte[] out,
+ int outOff)
+ throws DataLengthException, IllegalStateException, InvalidCipherTextException
+ {
+ int blockSize = cipher.getBlockSize();
+ int resultLen = 0;
+
+ if (forEncryption)
+ {
+ if (bufOff == blockSize)
+ {
+ if ((outOff + 2 * blockSize) > out.length)
+ {
+ throw new DataLengthException("output buffer too short");
+ }
+
+ resultLen = cipher.processBlock(buf, 0, out, outOff);
+ bufOff = 0;
+ }
+
+ //
+ // add PKCS7 padding
+ //
+ byte code = (byte)(blockSize - bufOff);
+
+ while (bufOff < blockSize)
+ {
+ buf[bufOff] = code;
+ bufOff++;
+ }
+
+ resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
+ }
+ else
+ {
+ if (bufOff == blockSize)
+ {
+ resultLen = cipher.processBlock(buf, 0, buf, 0);
+ bufOff = 0;
+ }
+ else
+ {
+ throw new DataLengthException("last block incomplete in decryption");
+ }
+
+ //
+ // remove PKCS7 padding
+ //
+ int count = buf[blockSize - 1] & 0xff;
+
+ if ((count < 0) || (count > blockSize))
+ {
+ throw new InvalidCipherTextException("pad block corrupted");
+ }
+
+ resultLen -= count;
+
+ System.arraycopy(buf, 0, out, outOff, resultLen);
+ }
+
+ reset();
+
+ return resultLen;
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java b/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java
new file mode 100644
index 00000000..da8c4ae1
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/SICBlockCipher.java
@@ -0,0 +1,113 @@
+package org.bouncycastle.crypto.modes;
+
+import org.bouncycastle.crypto.BlockCipher;
+import org.bouncycastle.crypto.CipherParameters;
+import org.bouncycastle.crypto.DataLengthException;
+import org.bouncycastle.crypto.params.ParametersWithIV;
+
+/**
+ * Implements the Segmented Integer Counter (SIC) mode on top of a simple
+ * block cipher. This mode is also known as CTR mode.
+ */
+public class SICBlockCipher
+ implements BlockCipher
+{
+ private final BlockCipher cipher;
+ private final int blockSize;
+
+ private byte[] IV;
+ private byte[] counter;
+ private byte[] counterOut;
+
+
+ /**
+ * Basic constructor.
+ *
+ * @param c the block cipher to be used.
+ */
+ public SICBlockCipher(BlockCipher c)
+ {
+ this.cipher = c;
+ this.blockSize = cipher.getBlockSize();
+ this.IV = new byte[blockSize];
+ this.counter = new byte[blockSize];
+ this.counterOut = new byte[blockSize];
+ }
+
+
+ /**
+ * return the underlying block cipher that we are wrapping.
+ *
+ * @return the underlying block cipher that we are wrapping.
+ */
+ public BlockCipher getUnderlyingCipher()
+ {
+ return cipher;
+ }
+
+
+ public void init(
+ boolean forEncryption, //ignored by this CTR mode
+ CipherParameters params)
+ throws IllegalArgumentException
+ {
+ if (params instanceof ParametersWithIV)
+ {
+ ParametersWithIV ivParam = (ParametersWithIV)params;
+ byte[] iv = ivParam.getIV();
+ System.arraycopy(iv, 0, IV, 0, IV.length);
+
+ reset();
+
+ // if null it's an IV changed only.
+ if (ivParam.getParameters() != null)
+ {
+ cipher.init(true, ivParam.getParameters());
+ }
+ }
+ else
+ {
+ throw new IllegalArgumentException("SIC mode requires ParametersWithIV");
+ }
+ }
+
+ public String getAlgorithmName()
+ {
+ return cipher.getAlgorithmName() + "/SIC";
+ }
+
+ public int getBlockSize()
+ {
+ return cipher.getBlockSize();
+ }
+
+
+ public int processBlock(byte[] in, int inOff, byte[] out, int outOff)
+ throws DataLengthException, IllegalStateException
+ {
+ cipher.processBlock(counter, 0, counterOut, 0);
+
+ //
+ // XOR the counterOut with the plaintext producing the cipher text
+ //
+ for (int i = 0; i < counterOut.length; i++)
+ {
+ out[outOff + i] = (byte)(counterOut[i] ^ in[inOff + i]);
+ }
+
+ // increment counter by 1.
+ for (int i = counter.length - 1; i >= 0 && ++counter[i] == 0; i--)
+ {
+ ; // do nothing - pre-increment and test for 0 in counter does the job.
+ }
+
+ return counter.length;
+ }
+
+
+ public void reset()
+ {
+ System.arraycopy(IV, 0, counter, 0, counter.length);
+ cipher.reset();
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java
new file mode 100644
index 00000000..f2be2fc4
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMExponentiator.java
@@ -0,0 +1,36 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import org.bouncycastle.util.Arrays;
+
+public class BasicGCMExponentiator implements GCMExponentiator
+{
+ private byte[] x;
+
+ public void init(byte[] x)
+ {
+ this.x = Arrays.clone(x);
+ }
+
+ public void exponentiateX(long pow, byte[] output)
+ {
+ // Initial value is little-endian 1
+ byte[] y = GCMUtil.oneAsBytes();
+
+ if (pow > 0)
+ {
+ byte[] powX = Arrays.clone(x);
+ do
+ {
+ if ((pow & 1L) != 0)
+ {
+ GCMUtil.multiply(y, powX);
+ }
+ GCMUtil.multiply(powX, powX);
+ pow >>>= 1;
+ }
+ while (pow > 0);
+ }
+
+ System.arraycopy(y, 0, output, 0, 16);
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java
new file mode 100644
index 00000000..a98d5b2a
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/BasicGCMMultiplier.java
@@ -0,0 +1,18 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import org.bouncycastle.util.Arrays;
+
+public class BasicGCMMultiplier implements GCMMultiplier
+{
+ private byte[] H;
+
+ public void init(byte[] H)
+ {
+ this.H = Arrays.clone(H);
+ }
+
+ public void multiplyH(byte[] x)
+ {
+ GCMUtil.multiply(x, H);
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java
new file mode 100644
index 00000000..e1cc5c76
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMExponentiator.java
@@ -0,0 +1,7 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+public interface GCMExponentiator
+{
+ void init(byte[] x);
+ void exponentiateX(long pow, byte[] output);
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java
new file mode 100644
index 00000000..f52f6105
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMMultiplier.java
@@ -0,0 +1,7 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+public interface GCMMultiplier
+{
+ void init(byte[] H);
+ void multiplyH(byte[] x);
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
new file mode 100644
index 00000000..48753011
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/GCMUtil.java
@@ -0,0 +1,260 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+
+abstract class GCMUtil
+{
+ static byte[] oneAsBytes()
+ {
+ byte[] tmp = new byte[16];
+ tmp[0] = (byte)0x80;
+ return tmp;
+ }
+
+ static int[] oneAsInts()
+ {
+ int[] tmp = new int[4];
+ tmp[0] = 0x80000000;
+ return tmp;
+ }
+
+ static byte[] asBytes(int[] ns)
+ {
+ byte[] output = new byte[16];
+ Pack.intToBigEndian(ns, output, 0);
+ return output;
+ }
+
+ static int[] asInts(byte[] bs)
+ {
+ int[] output = new int[4];
+ Pack.bigEndianToInt(bs, 0, output);
+ return output;
+ }
+
+ static void asInts(byte[] bs, int[] output)
+ {
+ Pack.bigEndianToInt(bs, 0, output);
+ }
+
+ static void multiply(byte[] block, byte[] val)
+ {
+ byte[] tmp = Arrays.clone(block);
+ byte[] c = new byte[16];
+
+ for (int i = 0; i < 16; ++i)
+ {
+ byte bits = val[i];
+ for (int j = 7; j >= 0; --j)
+ {
+ if ((bits & (1 << j)) != 0)
+ {
+ xor(c, tmp);
+ }
+
+ boolean lsb = (tmp[15] & 1) != 0;
+ shiftRight(tmp);
+ if (lsb)
+ {
+ // R = new byte[]{ 0xe1, ... };
+// GCMUtil.xor(v, R);
+ tmp[0] ^= (byte)0xe1;
+ }
+ }
+ }
+
+ System.arraycopy(c, 0, block, 0, 16);
+ }
+
+ // P is the value with only bit i=1 set
+ static void multiplyP(int[] x)
+ {
+ boolean lsb = (x[3] & 1) != 0;
+ shiftRight(x);
+ if (lsb)
+ {
+ // R = new int[]{ 0xe1000000, 0, 0, 0 };
+// xor(v, R);
+ x[0] ^= 0xe1000000;
+ }
+ }
+
+ static void multiplyP(int[] x, int[] output)
+ {
+ boolean lsb = (x[3] & 1) != 0;
+ shiftRight(x, output);
+ if (lsb)
+ {
+ output[0] ^= 0xe1000000;
+ }
+ }
+
+ // P is the value with only bit i=1 set
+ static void multiplyP8(int[] x)
+ {
+// for (int i = 8; i != 0; --i)
+// {
+// multiplyP(x);
+// }
+
+ int lsw = x[3];
+ shiftRightN(x, 8);
+ for (int i = 7; i >= 0; --i)
+ {
+ if ((lsw & (1 << i)) != 0)
+ {
+ x[0] ^= (0xe1000000 >>> (7 - i));
+ }
+ }
+ }
+
+ static void multiplyP8(int[] x, int[] output)
+ {
+ int lsw = x[3];
+ shiftRightN(x, 8, output);
+ for (int i = 7; i >= 0; --i)
+ {
+ if ((lsw & (1 << i)) != 0)
+ {
+ output[0] ^= (0xe1000000 >>> (7 - i));
+ }
+ }
+ }
+
+ static void shiftRight(byte[] block)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i] & 0xff;
+ block[i] = (byte) ((b >>> 1) | bit);
+ if (++i == 16)
+ {
+ break;
+ }
+ bit = (b & 1) << 7;
+ }
+ }
+
+ static void shiftRight(byte[] block, byte[] output)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i] & 0xff;
+ output[i] = (byte) ((b >>> 1) | bit);
+ if (++i == 16)
+ {
+ break;
+ }
+ bit = (b & 1) << 7;
+ }
+ }
+
+ static void shiftRight(int[] block)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i];
+ block[i] = (b >>> 1) | bit;
+ if (++i == 4)
+ {
+ break;
+ }
+ bit = b << 31;
+ }
+ }
+
+ static void shiftRight(int[] block, int[] output)
+ {
+ int i = 0;
+ int bit = 0;
+ for (;;)
+ {
+ int b = block[i];
+ output[i] = (b >>> 1) | bit;
+ if (++i == 4)
+ {
+ break;
+ }
+ bit = b << 31;
+ }
+ }
+
+ static void shiftRightN(int[] block, int n)
+ {
+ int i = 0;
+ int bits = 0;
+ for (;;)
+ {
+ int b = block[i];
+ block[i] = (b >>> n) | bits;
+ if (++i == 4)
+ {
+ break;
+ }
+ bits = b << (32 - n);
+ }
+ }
+
+ static void shiftRightN(int[] block, int n, int[] output)
+ {
+ int i = 0;
+ int bits = 0;
+ for (;;)
+ {
+ int b = block[i];
+ output[i] = (b >>> n) | bits;
+ if (++i == 4)
+ {
+ break;
+ }
+ bits = b << (32 - n);
+ }
+ }
+
+ static void xor(byte[] block, byte[] val)
+ {
+ for (int i = 15; i >= 0; --i)
+ {
+ block[i] ^= val[i];
+ }
+ }
+
+ static void xor(byte[] block, byte[] val, int off, int len)
+ {
+ while (len-- > 0)
+ {
+ block[len] ^= val[off + len];
+ }
+ }
+
+ static void xor(byte[] block, byte[] val, byte[] output)
+ {
+ for (int i = 15; i >= 0; --i)
+ {
+ output[i] = (byte)(block[i] ^ val[i]);
+ }
+ }
+
+ static void xor(int[] block, int[] val)
+ {
+ for (int i = 3; i >= 0; --i)
+ {
+ block[i] ^= val[i];
+ }
+ }
+
+ static void xor(int[] block, int[] val, int[] output)
+ {
+ for (int i = 3; i >= 0; --i)
+ {
+ output[i] = block[i] ^ val[i];
+ }
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java
new file mode 100644
index 00000000..a0512086
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables1kGCMExponentiator.java
@@ -0,0 +1,57 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import java.util.Vector;
+
+import org.bouncycastle.util.Arrays;
+
+public class Tables1kGCMExponentiator implements GCMExponentiator
+{
+ // A lookup table of the power-of-two powers of 'x'
+ // - lookupPowX2[i] = x^(2^i)
+ private Vector lookupPowX2;
+
+ public void init(byte[] x)
+ {
+ if (lookupPowX2 != null && Arrays.areEqual(x, (byte[])lookupPowX2.elementAt(0)))
+ {
+ return;
+ }
+
+ lookupPowX2 = new Vector(8);
+ lookupPowX2.addElement(Arrays.clone(x));
+ }
+
+ public void exponentiateX(long pow, byte[] output)
+ {
+ byte[] y = GCMUtil.oneAsBytes();
+ int bit = 0;
+ while (pow > 0)
+ {
+ if ((pow & 1L) != 0)
+ {
+ ensureAvailable(bit);
+ GCMUtil.multiply(y, (byte[])lookupPowX2.elementAt(bit));
+ }
+ ++bit;
+ pow >>>= 1;
+ }
+
+ System.arraycopy(y, 0, output, 0, 16);
+ }
+
+ private void ensureAvailable(int bit)
+ {
+ int count = lookupPowX2.size();
+ if (count <= bit)
+ {
+ byte[] tmp = (byte[])lookupPowX2.elementAt(count - 1);
+ do
+ {
+ tmp = Arrays.clone(tmp);
+ GCMUtil.multiply(tmp, tmp);
+ lookupPowX2.addElement(tmp);
+ }
+ while (++count <= bit);
+ }
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java
new file mode 100644
index 00000000..a34a6ea4
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables64kGCMMultiplier.java
@@ -0,0 +1,73 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+
+public class Tables64kGCMMultiplier implements GCMMultiplier
+{
+ private byte[] H;
+ private int[][][] M;
+
+ public void init(byte[] H)
+ {
+ if (M == null)
+ {
+ M = new int[16][256][4];
+ }
+ else if (Arrays.areEqual(this.H, H))
+ {
+ return;
+ }
+
+ this.H = Arrays.clone(H);
+
+ // M[0][0] is ZEROES;
+ GCMUtil.asInts(H, M[0][128]);
+
+ for (int j = 64; j >= 1; j >>= 1)
+ {
+ GCMUtil.multiplyP(M[0][j + j], M[0][j]);
+ }
+
+ int i = 0;
+ for (;;)
+ {
+ for (int j = 2; j < 256; j += j)
+ {
+ for (int k = 1; k < j; ++k)
+ {
+ GCMUtil.xor(M[i][j], M[i][k], M[i][j + k]);
+ }
+ }
+
+ if (++i == 16)
+ {
+ return;
+ }
+
+ // M[i][0] is ZEROES;
+ for (int j = 128; j > 0; j >>= 1)
+ {
+ GCMUtil.multiplyP8(M[i - 1][j], M[i][j]);
+ }
+ }
+ }
+
+ public void multiplyH(byte[] x)
+ {
+// assert x.Length == 16;
+
+ int[] z = new int[4];
+ for (int i = 15; i >= 0; --i)
+ {
+// GCMUtil.xor(z, M[i][x[i] & 0xff]);
+ int[] m = M[i][x[i] & 0xff];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+ }
+
+ Pack.intToBigEndian(z, x, 0);
+ }
+}
diff --git a/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java
new file mode 100644
index 00000000..8535db5a
--- /dev/null
+++ b/core/src/main/java/org/bouncycastle/crypto/modes/gcm/Tables8kGCMMultiplier.java
@@ -0,0 +1,90 @@
+package org.bouncycastle.crypto.modes.gcm;
+
+import org.bouncycastle.crypto.util.Pack;
+import org.bouncycastle.util.Arrays;
+
+public class Tables8kGCMMultiplier implements GCMMultiplier
+{
+ private byte[] H;
+ private int[][][] M;
+
+ public void init(byte[] H)
+ {
+ if (M == null)
+ {
+ M = new int[32][16][4];
+ }
+ else if (Arrays.areEqual(this.H, H))
+ {
+ return;
+ }
+
+ this.H = Arrays.clone(H);
+
+ // M[0][0] is ZEROES;
+ // M[1][0] is ZEROES;
+ GCMUtil.asInts(H, M[1][8]);
+
+ for (int j = 4; j >= 1; j >>= 1)
+ {
+ GCMUtil.multiplyP(M[1][j + j], M[1][j]);
+ }
+
+ GCMUtil.multiplyP(M[1][1], M[0][8]);
+
+ for (int j = 4; j >= 1; j >>= 1)
+ {
+ GCMUtil.multiplyP(M[0][j + j], M[0][j]);
+ }
+
+ int i = 0;
+ for (;;)
+ {
+ for (int j = 2; j < 16; j += j)
+ {
+ for (int k = 1; k < j; ++k)
+ {
+ GCMUtil.xor(M[i][j], M[i][k], M[i][j + k]);
+ }
+ }
+
+ if (++i == 32)
+ {
+ return;
+ }
+
+ if (i > 1)
+ {
+ // M[i][0] is ZEROES;
+ for(int j = 8; j > 0; j >>= 1)
+ {
+ GCMUtil.multiplyP8(M[i - 2][j], M[i][j]);
+ }
+ }
+ }
+ }
+
+ public void multiplyH(byte[] x)
+ {
+// assert x.Length == 16;
+
+ int[] z = new int[4];
+ for (int i = 15; i >= 0; --i)
+ {
+// GCMUtil.xor(z, M[i + i][x[i] & 0x0f]);
+ int[] m = M[i + i][x[i] & 0x0f];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+// GCMUtil.xor(z, M[i + i + 1][(x[i] & 0xf0) >>> 4]);
+ m = M[i + i + 1][(x[i] & 0xf0) >>> 4];
+ z[0] ^= m[0];
+ z[1] ^= m[1];
+ z[2] ^= m[2];
+ z[3] ^= m[3];
+ }
+
+ Pack.intToBigEndian(z, x, 0);
+ }
+} \ No newline at end of file