| package org.bouncycastle.crypto.modes; |
| |
| import org.bouncycastle.crypto.BlockCipher; |
| import org.bouncycastle.crypto.CipherParameters; |
| import org.bouncycastle.crypto.DataLengthException; |
| import org.bouncycastle.crypto.SkippingStreamCipher; |
| import org.bouncycastle.crypto.StreamBlockCipher; |
| import org.bouncycastle.crypto.params.ParametersWithIV; |
| import org.bouncycastle.util.Arrays; |
| import org.bouncycastle.util.Pack; |
| |
| /** |
| * 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 |
| extends StreamBlockCipher |
| implements SkippingStreamCipher |
| { |
| private final BlockCipher cipher; |
| private final int blockSize; |
| |
| private byte[] IV; |
| private byte[] counter; |
| private byte[] counterOut; |
| private int byteCount; |
| |
| /** |
| * Basic constructor. |
| * |
| * @param c the block cipher to be used. |
| */ |
| public SICBlockCipher(BlockCipher c) |
| { |
| super(c); |
| |
| this.cipher = c; |
| this.blockSize = cipher.getBlockSize(); |
| this.IV = new byte[blockSize]; |
| this.counter = new byte[blockSize]; |
| this.counterOut = new byte[blockSize]; |
| this.byteCount = 0; |
| } |
| |
| public void init( |
| boolean forEncryption, //ignored by this CTR mode |
| CipherParameters params) |
| throws IllegalArgumentException |
| { |
| if (params instanceof ParametersWithIV) |
| { |
| ParametersWithIV ivParam = (ParametersWithIV)params; |
| this.IV = Arrays.clone(ivParam.getIV()); |
| |
| if (blockSize < IV.length) |
| { |
| throw new IllegalArgumentException("CTR/SIC mode requires IV no greater than: " + blockSize + " bytes."); |
| } |
| |
| int maxCounterSize = (8 > blockSize / 2) ? blockSize / 2 : 8; |
| |
| if (blockSize - IV.length > maxCounterSize) |
| { |
| throw new IllegalArgumentException("CTR/SIC mode requires IV of at least: " + (blockSize - maxCounterSize) + " bytes."); |
| } |
| |
| // if null it's an IV changed only. |
| if (ivParam.getParameters() != null) |
| { |
| cipher.init(true, ivParam.getParameters()); |
| } |
| |
| reset(); |
| } |
| else |
| { |
| throw new IllegalArgumentException("CTR/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 |
| { |
| processBytes(in, inOff, blockSize, out, outOff); |
| |
| return blockSize; |
| } |
| |
| protected byte calculateByte(byte in) |
| throws DataLengthException, IllegalStateException |
| { |
| if (byteCount == 0) |
| { |
| cipher.processBlock(counter, 0, counterOut, 0); |
| |
| return (byte)(counterOut[byteCount++] ^ in); |
| } |
| |
| byte rv = (byte)(counterOut[byteCount++] ^ in); |
| |
| if (byteCount == counter.length) |
| { |
| byteCount = 0; |
| |
| incrementCounterAt(0); |
| |
| checkCounter(); |
| } |
| |
| return rv; |
| } |
| |
| private void checkCounter() |
| { |
| // if the IV is the same as the blocksize we assume the user knows what they are doing |
| if (IV.length < blockSize) |
| { |
| for (int i = 0; i != IV.length; i++) |
| { |
| if (counter[i] != IV[i]) |
| { |
| throw new IllegalStateException("Counter in CTR/SIC mode out of range."); |
| } |
| } |
| } |
| } |
| |
| private void incrementCounterAt(int pos) |
| { |
| int i = counter.length - pos; |
| while (--i >= 0) |
| { |
| if (++counter[i] != 0) |
| { |
| break; |
| } |
| } |
| } |
| |
| private void incrementCounter(int offSet) |
| { |
| byte old = counter[counter.length - 1]; |
| |
| counter[counter.length - 1] += offSet; |
| |
| if (old != 0 && counter[counter.length - 1] < old) |
| { |
| incrementCounterAt(1); |
| } |
| } |
| |
| private void decrementCounterAt(int pos) |
| { |
| int i = counter.length - pos; |
| while (--i >= 0) |
| { |
| if (--counter[i] != -1) |
| { |
| return; |
| } |
| } |
| } |
| |
| private void adjustCounter(long n) |
| { |
| if (n >= 0) |
| { |
| long numBlocks = (n + byteCount) / blockSize; |
| |
| long rem = numBlocks; |
| if (rem > 255) |
| { |
| for (int i = 5; i >= 1; i--) |
| { |
| long diff = 1L << (8 * i); |
| while (rem >= diff) |
| { |
| incrementCounterAt(i); |
| rem -= diff; |
| } |
| } |
| } |
| |
| incrementCounter((int)rem); |
| |
| byteCount = (int)((n + byteCount) - (blockSize * numBlocks)); |
| } |
| else |
| { |
| long numBlocks = (-n - byteCount) / blockSize; |
| |
| long rem = numBlocks; |
| if (rem > 255) |
| { |
| for (int i = 5; i >= 1; i--) |
| { |
| long diff = 1L << (8 * i); |
| while (rem > diff) |
| { |
| decrementCounterAt(i); |
| rem -= diff; |
| } |
| } |
| } |
| |
| for (long i = 0; i != rem; i++) |
| { |
| decrementCounterAt(0); |
| } |
| |
| int gap = (int)(byteCount + n + (blockSize * numBlocks)); |
| |
| if (gap >= 0) |
| { |
| byteCount = 0; |
| } |
| else |
| { |
| decrementCounterAt(0); |
| byteCount = blockSize + gap; |
| } |
| } |
| } |
| |
| public void reset() |
| { |
| Arrays.fill(counter, (byte)0); |
| System.arraycopy(IV, 0, counter, 0, IV.length); |
| cipher.reset(); |
| this.byteCount = 0; |
| } |
| |
| public long skip(long numberOfBytes) |
| { |
| adjustCounter(numberOfBytes); |
| |
| checkCounter(); |
| |
| cipher.processBlock(counter, 0, counterOut, 0); |
| |
| return numberOfBytes; |
| } |
| |
| public long seekTo(long position) |
| { |
| reset(); |
| |
| return skip(position); |
| } |
| |
| public long getPosition() |
| { |
| byte[] res = new byte[counter.length]; |
| |
| System.arraycopy(counter, 0, res, 0, res.length); |
| |
| for (int i = res.length - 1; i >= 1; i--) |
| { |
| int v; |
| if (i < IV.length) |
| { |
| v = (res[i] & 0xff) - (IV[i] & 0xff); |
| } |
| else |
| { |
| v = (res[i] & 0xff); |
| } |
| |
| if (v < 0) |
| { |
| res[i - 1]--; |
| v += 256; |
| } |
| |
| res[i] = (byte)v; |
| } |
| |
| return Pack.bigEndianToLong(res, res.length - 8) * blockSize + byteCount; |
| } |
| } |