1 package org.bouncycastle.crypto.modes; 2 3 import org.bouncycastle.crypto.BlockCipher; 4 import org.bouncycastle.crypto.CipherParameters; 5 import org.bouncycastle.crypto.DataLengthException; 6 import org.bouncycastle.crypto.SkippingStreamCipher; 7 import org.bouncycastle.crypto.StreamBlockCipher; 8 import org.bouncycastle.crypto.params.ParametersWithIV; 9 import org.bouncycastle.util.Arrays; 10 import org.bouncycastle.util.Pack; 11 12 /** 13 * Implements the Segmented Integer Counter (SIC) mode on top of a simple 14 * block cipher. This mode is also known as CTR mode. 15 */ 16 public class SICBlockCipher 17 extends StreamBlockCipher 18 implements SkippingStreamCipher 19 { 20 private final BlockCipher cipher; 21 private final int blockSize; 22 23 private byte[] IV; 24 private byte[] counter; 25 private byte[] counterOut; 26 private int byteCount; 27 28 /** 29 * Basic constructor. 30 * 31 * @param c the block cipher to be used. 32 */ SICBlockCipher(BlockCipher c)33 public SICBlockCipher(BlockCipher c) 34 { 35 super(c); 36 37 this.cipher = c; 38 this.blockSize = cipher.getBlockSize(); 39 this.IV = new byte[blockSize]; 40 this.counter = new byte[blockSize]; 41 this.counterOut = new byte[blockSize]; 42 this.byteCount = 0; 43 } 44 init( boolean forEncryption, CipherParameters params)45 public void init( 46 boolean forEncryption, //ignored by this CTR mode 47 CipherParameters params) 48 throws IllegalArgumentException 49 { 50 if (params instanceof ParametersWithIV) 51 { 52 ParametersWithIV ivParam = (ParametersWithIV)params; 53 this.IV = Arrays.clone(ivParam.getIV()); 54 55 if (blockSize < IV.length) 56 { 57 throw new IllegalArgumentException("CTR/SIC mode requires IV no greater than: " + blockSize + " bytes."); 58 } 59 60 int maxCounterSize = (8 > blockSize / 2) ? blockSize / 2 : 8; 61 62 if (blockSize - IV.length > maxCounterSize) 63 { 64 throw new IllegalArgumentException("CTR/SIC mode requires IV of at least: " + (blockSize - maxCounterSize) + " bytes."); 65 } 66 67 // if null it's an IV changed only. 68 if (ivParam.getParameters() != null) 69 { 70 cipher.init(true, ivParam.getParameters()); 71 } 72 73 reset(); 74 } 75 else 76 { 77 throw new IllegalArgumentException("CTR/SIC mode requires ParametersWithIV"); 78 } 79 } 80 getAlgorithmName()81 public String getAlgorithmName() 82 { 83 return cipher.getAlgorithmName() + "/SIC"; 84 } 85 getBlockSize()86 public int getBlockSize() 87 { 88 return cipher.getBlockSize(); 89 } 90 processBlock(byte[] in, int inOff, byte[] out, int outOff)91 public int processBlock(byte[] in, int inOff, byte[] out, int outOff) 92 throws DataLengthException, IllegalStateException 93 { 94 processBytes(in, inOff, blockSize, out, outOff); 95 96 return blockSize; 97 } 98 calculateByte(byte in)99 protected byte calculateByte(byte in) 100 throws DataLengthException, IllegalStateException 101 { 102 if (byteCount == 0) 103 { 104 cipher.processBlock(counter, 0, counterOut, 0); 105 106 return (byte)(counterOut[byteCount++] ^ in); 107 } 108 109 byte rv = (byte)(counterOut[byteCount++] ^ in); 110 111 if (byteCount == counter.length) 112 { 113 byteCount = 0; 114 115 incrementCounterAt(0); 116 117 checkCounter(); 118 } 119 120 return rv; 121 } 122 checkCounter()123 private void checkCounter() 124 { 125 // if the IV is the same as the blocksize we assume the user knows what they are doing 126 if (IV.length < blockSize) 127 { 128 for (int i = 0; i != IV.length; i++) 129 { 130 if (counter[i] != IV[i]) 131 { 132 throw new IllegalStateException("Counter in CTR/SIC mode out of range."); 133 } 134 } 135 } 136 } 137 incrementCounterAt(int pos)138 private void incrementCounterAt(int pos) 139 { 140 int i = counter.length - pos; 141 while (--i >= 0) 142 { 143 if (++counter[i] != 0) 144 { 145 break; 146 } 147 } 148 } 149 incrementCounter(int offSet)150 private void incrementCounter(int offSet) 151 { 152 byte old = counter[counter.length - 1]; 153 154 counter[counter.length - 1] += offSet; 155 156 if (old != 0 && counter[counter.length - 1] < old) 157 { 158 incrementCounterAt(1); 159 } 160 } 161 decrementCounterAt(int pos)162 private void decrementCounterAt(int pos) 163 { 164 int i = counter.length - pos; 165 while (--i >= 0) 166 { 167 if (--counter[i] != -1) 168 { 169 return; 170 } 171 } 172 } 173 adjustCounter(long n)174 private void adjustCounter(long n) 175 { 176 if (n >= 0) 177 { 178 long numBlocks = (n + byteCount) / blockSize; 179 180 long rem = numBlocks; 181 if (rem > 255) 182 { 183 for (int i = 5; i >= 1; i--) 184 { 185 long diff = 1L << (8 * i); 186 while (rem >= diff) 187 { 188 incrementCounterAt(i); 189 rem -= diff; 190 } 191 } 192 } 193 194 incrementCounter((int)rem); 195 196 byteCount = (int)((n + byteCount) - (blockSize * numBlocks)); 197 } 198 else 199 { 200 long numBlocks = (-n - byteCount) / blockSize; 201 202 long rem = numBlocks; 203 if (rem > 255) 204 { 205 for (int i = 5; i >= 1; i--) 206 { 207 long diff = 1L << (8 * i); 208 while (rem > diff) 209 { 210 decrementCounterAt(i); 211 rem -= diff; 212 } 213 } 214 } 215 216 for (long i = 0; i != rem; i++) 217 { 218 decrementCounterAt(0); 219 } 220 221 int gap = (int)(byteCount + n + (blockSize * numBlocks)); 222 223 if (gap >= 0) 224 { 225 byteCount = 0; 226 } 227 else 228 { 229 decrementCounterAt(0); 230 byteCount = blockSize + gap; 231 } 232 } 233 } 234 reset()235 public void reset() 236 { 237 Arrays.fill(counter, (byte)0); 238 System.arraycopy(IV, 0, counter, 0, IV.length); 239 cipher.reset(); 240 this.byteCount = 0; 241 } 242 skip(long numberOfBytes)243 public long skip(long numberOfBytes) 244 { 245 adjustCounter(numberOfBytes); 246 247 checkCounter(); 248 249 cipher.processBlock(counter, 0, counterOut, 0); 250 251 return numberOfBytes; 252 } 253 seekTo(long position)254 public long seekTo(long position) 255 { 256 reset(); 257 258 return skip(position); 259 } 260 getPosition()261 public long getPosition() 262 { 263 byte[] res = new byte[counter.length]; 264 265 System.arraycopy(counter, 0, res, 0, res.length); 266 267 for (int i = res.length - 1; i >= 1; i--) 268 { 269 int v; 270 if (i < IV.length) 271 { 272 v = (res[i] & 0xff) - (IV[i] & 0xff); 273 } 274 else 275 { 276 v = (res[i] & 0xff); 277 } 278 279 if (v < 0) 280 { 281 res[i - 1]--; 282 v += 256; 283 } 284 285 res[i] = (byte)v; 286 } 287 288 return Pack.bigEndianToLong(res, res.length - 8) * blockSize + byteCount; 289 } 290 } 291