All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.crypto.modes.CCMBlockCipher Maven / Gradle / Ivy

Go to download

The Bouncy Castle Crypto package is a Java implementation of cryptographic algorithms. This jar contains JCE provider and lightweight API for the Bouncy Castle Cryptography APIs for Java 1.8 and later with debug enabled.

The newest version!
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.OutputLengthException;
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.
 * 

* Note: this mode is a packet mode - it needs all the data up front. */ public class CCMBlockCipher implements CCMModeCipher { 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 ExposedByteArrayOutputStream associatedText = new ExposedByteArrayOutputStream(); private ExposedByteArrayOutputStream data = new ExposedByteArrayOutputStream(); /** * Return a new CCM mode cipher based on the passed in base cipher * * @param cipher the base cipher for the CCM mode. */ public static CCMModeCipher newInstance(BlockCipher cipher) { return new CCMBlockCipher(cipher); } /** * Basic constructor. * * @param c the block cipher to be used. * @deprecated use the CCMBlockCipher.newInstance() static method. */ 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; CipherParameters cipherParameters; if (params instanceof AEADParameters) { AEADParameters param = (AEADParameters)params; nonce = param.getNonce(); initialAssociatedText = param.getAssociatedText(); macSize = getMacSize(forEncryption, param.getMacSize()); cipherParameters = param.getKey(); } else if (params instanceof ParametersWithIV) { ParametersWithIV param = (ParametersWithIV)params; nonce = param.getIV(); initialAssociatedText = null; macSize = getMacSize(forEncryption, 64); cipherParameters = param.getParameters(); } else { throw new IllegalArgumentException("invalid parameters passed to CCM: " + params.getClass().getName()); } // NOTE: Very basic support for key re-use, but no performance gain from it if (cipherParameters != null) { keyParam = cipherParameters; } if (nonce == null || nonce.length < 7 || nonce.length > 13) { throw new IllegalArgumentException("nonce must have length from 7 to 13 octets"); } reset(); } 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 { if (in.length < (inOff + inLen)) { throw new DataLengthException("Input buffer too short"); } data.write(in, inOff, inLen); return 0; } public int doFinal(byte[] out, int outOff) throws IllegalStateException, InvalidCipherTextException { int len = processPacket(data.getBuffer(), 0, data.size(), out, outOff); reset(); return len; } 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; } /** * Process a packet of data for either CCM decryption or encryption. * * @param in data for processing. * @param inOff offset at which data starts in the input array. * @param inLen length of the data in the input array. * @return a byte array containing the processed input.. * @throws IllegalStateException if the cipher is not appropriately set up. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. */ public byte[] processPacket(byte[] in, int inOff, int inLen) throws IllegalStateException, InvalidCipherTextException { byte[] output; if (forEncryption) { output = new byte[inLen + macSize]; } else { if (inLen < macSize) { throw new InvalidCipherTextException("data too short"); } output = new byte[inLen - macSize]; } processPacket(in, inOff, inLen, output, 0); return output; } /** * Process a packet of data for either CCM decryption or encryption. * * @param in data for processing. * @param inOff offset at which data starts in the input array. * @param inLen length of the data in the input array. * @param output output array. * @param outOff offset into output array to start putting processed bytes. * @return the number of bytes added to output. * @throws IllegalStateException if the cipher is not appropriately set up. * @throws InvalidCipherTextException if the input data is truncated or the mac check fails. * @throws DataLengthException if output buffer too short. */ public int processPacket(byte[] in, int inOff, int inLen, byte[] output, int outOff) throws IllegalStateException, InvalidCipherTextException, DataLengthException { // 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 = SICBlockCipher.newInstance(cipher); ctrCipher.init(forEncryption, new ParametersWithIV(keyParam, iv)); int outputLen; int inIndex = inOff; int outIndex = outOff; if (forEncryption) { outputLen = inLen + macSize; if (output.length < (outputLen + outOff)) { throw new OutputLengthException("Output buffer too short."); } calculateMac(in, inOff, inLen, macBlock); byte[] encMac = new byte[blockSize]; ctrCipher.processBlock(macBlock, 0, encMac, 0); // S0 while (inIndex < (inOff + inLen - blockSize)) // S1... { ctrCipher.processBlock(in, inIndex, output, outIndex); outIndex += blockSize; inIndex += blockSize; } byte[] block = new byte[blockSize]; System.arraycopy(in, inIndex, block, 0, inLen + inOff - inIndex); ctrCipher.processBlock(block, 0, block, 0); System.arraycopy(block, 0, output, outIndex, inLen + inOff - inIndex); System.arraycopy(encMac, 0, output, outOff + inLen, macSize); } else { if (inLen < macSize) { throw new InvalidCipherTextException("data too short"); } outputLen = inLen - macSize; if (output.length < (outputLen + outOff)) { throw new OutputLengthException("Output buffer too short."); } System.arraycopy(in, inOff + outputLen, macBlock, 0, macSize); ctrCipher.processBlock(macBlock, 0, macBlock, 0); for (int i = macSize; i != macBlock.length; i++) { macBlock[i] = 0; } while (inIndex < (inOff + outputLen - blockSize)) { ctrCipher.processBlock(in, inIndex, output, outIndex); outIndex += blockSize; inIndex += blockSize; } byte[] block = new byte[blockSize]; System.arraycopy(in, inIndex, block, 0, outputLen - (inIndex - inOff)); ctrCipher.processBlock(block, 0, block, 0); System.arraycopy(block, 0, output, outIndex, outputLen - (inIndex - inOff)); byte[] calculatedMacBlock = new byte[blockSize]; calculateMac(output, outOff, outputLen, calculatedMacBlock); if (!Arrays.constantTimeAreEqual(macBlock, calculatedMacBlock)) { throw new InvalidCipherTextException("mac check in CCM failed"); } } return outputLen; } 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) { cMac.update(associatedText.getBuffer(), 0, associatedText.size()); } 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 getMacSize(boolean forEncryption, int requestedMacBits) { if (forEncryption && (requestedMacBits < 32 || requestedMacBits > 128 || 0 != (requestedMacBits & 15))) { throw new IllegalArgumentException("tag length in octets must be one of {4,6,8,10,12,14,16}"); } return requestedMacBits >>> 3; } private int getAssociatedTextLength() { return associatedText.size() + ((initialAssociatedText == null) ? 0 : initialAssociatedText.length); } private boolean hasAssociatedText() { return getAssociatedTextLength() > 0; } private static class ExposedByteArrayOutputStream extends ByteArrayOutputStream { public ExposedByteArrayOutputStream() { } public byte[] getBuffer() { return this.buf; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy