1 package org.bouncycastle.crypto.modes; 2 3 import org.bouncycastle.crypto.BlockCipher; 4 import org.bouncycastle.crypto.BufferedBlockCipher; 5 import org.bouncycastle.crypto.DataLengthException; 6 import org.bouncycastle.crypto.InvalidCipherTextException; 7 import org.bouncycastle.crypto.StreamBlockCipher; 8 9 /** 10 * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to 11 * be used to produce cipher text which is the same length as the plain text. 12 */ 13 public class CTSBlockCipher 14 extends BufferedBlockCipher 15 { 16 private int blockSize; 17 18 /** 19 * Create a buffered block cipher that uses Cipher Text Stealing 20 * 21 * @param cipher the underlying block cipher this buffering object wraps. 22 */ CTSBlockCipher( BlockCipher cipher)23 public CTSBlockCipher( 24 BlockCipher cipher) 25 { 26 if (cipher instanceof StreamBlockCipher) 27 { 28 throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers"); 29 } 30 31 this.cipher = cipher; 32 33 blockSize = cipher.getBlockSize(); 34 35 buf = new byte[blockSize * 2]; 36 bufOff = 0; 37 } 38 39 /** 40 * return the size of the output buffer required for an update 41 * an input of len bytes. 42 * 43 * @param len the length of the input. 44 * @return the space required to accommodate a call to update 45 * with len bytes of input. 46 */ getUpdateOutputSize( int len)47 public int getUpdateOutputSize( 48 int len) 49 { 50 int total = len + bufOff; 51 int leftOver = total % buf.length; 52 53 if (leftOver == 0) 54 { 55 return total - buf.length; 56 } 57 58 return total - leftOver; 59 } 60 61 /** 62 * return the size of the output buffer required for an update plus a 63 * doFinal with an input of len bytes. 64 * 65 * @param len the length of the input. 66 * @return the space required to accommodate a call to update and doFinal 67 * with len bytes of input. 68 */ getOutputSize( int len)69 public int getOutputSize( 70 int len) 71 { 72 return len + bufOff; 73 } 74 75 /** 76 * process a single byte, producing an output block if necessary. 77 * 78 * @param in the input byte. 79 * @param out the space for any output that might be produced. 80 * @param outOff the offset from which the output will be copied. 81 * @return the number of output bytes copied to out. 82 * @exception DataLengthException if there isn't enough space in out. 83 * @exception IllegalStateException if the cipher isn't initialised. 84 */ processByte( byte in, byte[] out, int outOff)85 public int processByte( 86 byte in, 87 byte[] out, 88 int outOff) 89 throws DataLengthException, IllegalStateException 90 { 91 int resultLen = 0; 92 93 if (bufOff == buf.length) 94 { 95 resultLen = cipher.processBlock(buf, 0, out, outOff); 96 System.arraycopy(buf, blockSize, buf, 0, blockSize); 97 98 bufOff = blockSize; 99 } 100 101 buf[bufOff++] = in; 102 103 return resultLen; 104 } 105 106 /** 107 * process an array of bytes, producing output if necessary. 108 * 109 * @param in the input byte array. 110 * @param inOff the offset at which the input data starts. 111 * @param len the number of bytes to be copied out of the input array. 112 * @param out the space for any output that might be produced. 113 * @param outOff the offset from which the output will be copied. 114 * @return the number of output bytes copied to out. 115 * @exception DataLengthException if there isn't enough space in out. 116 * @exception IllegalStateException if the cipher isn't initialised. 117 */ processBytes( byte[] in, int inOff, int len, byte[] out, int outOff)118 public int processBytes( 119 byte[] in, 120 int inOff, 121 int len, 122 byte[] out, 123 int outOff) 124 throws DataLengthException, IllegalStateException 125 { 126 if (len < 0) 127 { 128 throw new IllegalArgumentException("Can't have a negative input length!"); 129 } 130 131 int blockSize = getBlockSize(); 132 int length = getUpdateOutputSize(len); 133 134 if (length > 0) 135 { 136 if ((outOff + length) > out.length) 137 { 138 throw new DataLengthException("output buffer too short"); 139 } 140 } 141 142 int resultLen = 0; 143 int gapLen = buf.length - bufOff; 144 145 if (len > gapLen) 146 { 147 System.arraycopy(in, inOff, buf, bufOff, gapLen); 148 149 resultLen += cipher.processBlock(buf, 0, out, outOff); 150 System.arraycopy(buf, blockSize, buf, 0, blockSize); 151 152 bufOff = blockSize; 153 154 len -= gapLen; 155 inOff += gapLen; 156 157 while (len > blockSize) 158 { 159 System.arraycopy(in, inOff, buf, bufOff, blockSize); 160 resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); 161 System.arraycopy(buf, blockSize, buf, 0, blockSize); 162 163 len -= blockSize; 164 inOff += blockSize; 165 } 166 } 167 168 System.arraycopy(in, inOff, buf, bufOff, len); 169 170 bufOff += len; 171 172 return resultLen; 173 } 174 175 /** 176 * Process the last block in the buffer. 177 * 178 * @param out the array the block currently being held is copied into. 179 * @param outOff the offset at which the copying starts. 180 * @return the number of output bytes copied to out. 181 * @exception DataLengthException if there is insufficient space in out for 182 * the output. 183 * @exception IllegalStateException if the underlying cipher is not 184 * initialised. 185 * @exception InvalidCipherTextException if cipher text decrypts wrongly (in 186 * case the exception will never get thrown). 187 */ doFinal( byte[] out, int outOff)188 public int doFinal( 189 byte[] out, 190 int outOff) 191 throws DataLengthException, IllegalStateException, InvalidCipherTextException 192 { 193 if (bufOff + outOff > out.length) 194 { 195 throw new DataLengthException("output buffer to small in doFinal"); 196 } 197 198 int blockSize = cipher.getBlockSize(); 199 int len = bufOff - blockSize; 200 byte[] block = new byte[blockSize]; 201 202 if (forEncryption) 203 { 204 if (bufOff < blockSize) 205 { 206 throw new DataLengthException("need at least one block of input for CTS"); 207 } 208 209 cipher.processBlock(buf, 0, block, 0); 210 211 if (bufOff > blockSize) 212 { 213 for (int i = bufOff; i != buf.length; i++) 214 { 215 buf[i] = block[i - blockSize]; 216 } 217 218 for (int i = blockSize; i != bufOff; i++) 219 { 220 buf[i] ^= block[i - blockSize]; 221 } 222 223 if (cipher instanceof CBCBlockCipher) 224 { 225 BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); 226 227 c.processBlock(buf, blockSize, out, outOff); 228 } 229 else 230 { 231 cipher.processBlock(buf, blockSize, out, outOff); 232 } 233 234 System.arraycopy(block, 0, out, outOff + blockSize, len); 235 } 236 else 237 { 238 System.arraycopy(block, 0, out, outOff, blockSize); 239 } 240 } 241 else 242 { 243 if (bufOff < blockSize) 244 { 245 throw new DataLengthException("need at least one block of input for CTS"); 246 } 247 248 byte[] lastBlock = new byte[blockSize]; 249 250 if (bufOff > blockSize) 251 { 252 if (cipher instanceof CBCBlockCipher) 253 { 254 BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher(); 255 256 c.processBlock(buf, 0, block, 0); 257 } 258 else 259 { 260 cipher.processBlock(buf, 0, block, 0); 261 } 262 263 for (int i = blockSize; i != bufOff; i++) 264 { 265 lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]); 266 } 267 268 System.arraycopy(buf, blockSize, block, 0, len); 269 270 cipher.processBlock(block, 0, out, outOff); 271 System.arraycopy(lastBlock, 0, out, outOff + blockSize, len); 272 } 273 else 274 { 275 cipher.processBlock(buf, 0, block, 0); 276 277 System.arraycopy(block, 0, out, outOff, blockSize); 278 } 279 } 280 281 int offset = bufOff; 282 283 reset(); 284 285 return offset; 286 } 287 } 288