diff options
Diffstat (limited to 'core/src/main/java/org/bouncycastle/crypto/modes')
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) — “License 1” <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 |