1package org.bouncycastle.crypto; 2 3 4/** 5 * A wrapper class that allows block ciphers to be used to process data in 6 * a piecemeal fashion. The BufferedBlockCipher outputs a block only when the 7 * buffer is full and more data is being added, or on a doFinal. 8 * <p> 9 * Note: in the case where the underlying cipher is either a CFB cipher or an 10 * OFB one the last block may not be a multiple of the block size. 11 */ 12public class BufferedBlockCipher 13{ 14 protected byte[] buf; 15 protected int bufOff; 16 17 protected boolean forEncryption; 18 protected BlockCipher cipher; 19 20 protected boolean partialBlockOkay; 21 protected boolean pgpCFB; 22 23 /** 24 * constructor for subclasses 25 */ 26 protected BufferedBlockCipher() 27 { 28 } 29 30 /** 31 * Create a buffered block cipher without padding. 32 * 33 * @param cipher the underlying block cipher this buffering object wraps. 34 */ 35 public BufferedBlockCipher( 36 BlockCipher cipher) 37 { 38 this.cipher = cipher; 39 40 buf = new byte[cipher.getBlockSize()]; 41 bufOff = 0; 42 43 // 44 // check if we can handle partial blocks on doFinal. 45 // 46 String name = cipher.getAlgorithmName(); 47 int idx = name.indexOf('/') + 1; 48 49 pgpCFB = (idx > 0 && name.startsWith("PGP", idx)); 50 51 if (pgpCFB) 52 { 53 partialBlockOkay = true; 54 } 55 else 56 { 57 partialBlockOkay = (idx > 0 && (name.startsWith("CFB", idx) || name.startsWith("OFB", idx) || name.startsWith("OpenPGP", idx) || name.startsWith("SIC", idx) || name.startsWith("GCTR", idx))); 58 } 59 } 60 61 /** 62 * return the cipher this object wraps. 63 * 64 * @return the cipher this object wraps. 65 */ 66 public BlockCipher getUnderlyingCipher() 67 { 68 return cipher; 69 } 70 71 /** 72 * initialise the cipher. 73 * 74 * @param forEncryption if true the cipher is initialised for 75 * encryption, if false for decryption. 76 * @param params the key and other data required by the cipher. 77 * @exception IllegalArgumentException if the params argument is 78 * inappropriate. 79 */ 80 public void init( 81 boolean forEncryption, 82 CipherParameters params) 83 throws IllegalArgumentException 84 { 85 this.forEncryption = forEncryption; 86 87 reset(); 88 89 cipher.init(forEncryption, params); 90 } 91 92 /** 93 * return the blocksize for the underlying cipher. 94 * 95 * @return the blocksize for the underlying cipher. 96 */ 97 public int getBlockSize() 98 { 99 return cipher.getBlockSize(); 100 } 101 102 /** 103 * return the size of the output buffer required for an update 104 * an input of len bytes. 105 * 106 * @param len the length of the input. 107 * @return the space required to accommodate a call to update 108 * with len bytes of input. 109 */ 110 public int getUpdateOutputSize( 111 int len) 112 { 113 int total = len + bufOff; 114 int leftOver; 115 116 if (pgpCFB) 117 { 118 leftOver = total % buf.length - (cipher.getBlockSize() + 2); 119 } 120 else 121 { 122 leftOver = total % buf.length; 123 } 124 125 return total - leftOver; 126 } 127 128 /** 129 * return the size of the output buffer required for an update plus a 130 * doFinal with an input of 'length' bytes. 131 * 132 * @param length the length of the input. 133 * @return the space required to accommodate a call to update and doFinal 134 * with 'length' bytes of input. 135 */ 136 public int getOutputSize( 137 int length) 138 { 139 // Note: Can assume partialBlockOkay is true for purposes of this calculation 140 return length + bufOff; 141 } 142 143 /** 144 * process a single byte, producing an output block if neccessary. 145 * 146 * @param in the input byte. 147 * @param out the space for any output that might be produced. 148 * @param outOff the offset from which the output will be copied. 149 * @return the number of output bytes copied to out. 150 * @exception DataLengthException if there isn't enough space in out. 151 * @exception IllegalStateException if the cipher isn't initialised. 152 */ 153 public int processByte( 154 byte in, 155 byte[] out, 156 int outOff) 157 throws DataLengthException, IllegalStateException 158 { 159 int resultLen = 0; 160 161 buf[bufOff++] = in; 162 163 if (bufOff == buf.length) 164 { 165 resultLen = cipher.processBlock(buf, 0, out, outOff); 166 bufOff = 0; 167 } 168 169 return resultLen; 170 } 171 172 /** 173 * process an array of bytes, producing output if necessary. 174 * 175 * @param in the input byte array. 176 * @param inOff the offset at which the input data starts. 177 * @param len the number of bytes to be copied out of the input array. 178 * @param out the space for any output that might be produced. 179 * @param outOff the offset from which the output will be copied. 180 * @return the number of output bytes copied to out. 181 * @exception DataLengthException if there isn't enough space in out. 182 * @exception IllegalStateException if the cipher isn't initialised. 183 */ 184 public int processBytes( 185 byte[] in, 186 int inOff, 187 int len, 188 byte[] out, 189 int outOff) 190 throws DataLengthException, IllegalStateException 191 { 192 if (len < 0) 193 { 194 throw new IllegalArgumentException("Can't have a negative input length!"); 195 } 196 197 int blockSize = getBlockSize(); 198 int length = getUpdateOutputSize(len); 199 200 if (length > 0) 201 { 202 if ((outOff + length) > out.length) 203 { 204 throw new DataLengthException("output buffer too short"); 205 } 206 } 207 208 int resultLen = 0; 209 int gapLen = buf.length - bufOff; 210 211 if (len > gapLen) 212 { 213 System.arraycopy(in, inOff, buf, bufOff, gapLen); 214 215 resultLen += cipher.processBlock(buf, 0, out, outOff); 216 217 bufOff = 0; 218 len -= gapLen; 219 inOff += gapLen; 220 221 while (len > buf.length) 222 { 223 resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); 224 225 len -= blockSize; 226 inOff += blockSize; 227 } 228 } 229 230 System.arraycopy(in, inOff, buf, bufOff, len); 231 232 bufOff += len; 233 234 if (bufOff == buf.length) 235 { 236 resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); 237 bufOff = 0; 238 } 239 240 return resultLen; 241 } 242 243 /** 244 * Process the last block in the buffer. 245 * 246 * @param out the array the block currently being held is copied into. 247 * @param outOff the offset at which the copying starts. 248 * @return the number of output bytes copied to out. 249 * @exception DataLengthException if there is insufficient space in out for 250 * the output, or the input is not block size aligned and should be. 251 * @exception IllegalStateException if the underlying cipher is not 252 * initialised. 253 * @exception InvalidCipherTextException if padding is expected and not found. 254 * @exception DataLengthException if the input is not block size 255 * aligned. 256 */ 257 public int doFinal( 258 byte[] out, 259 int outOff) 260 throws DataLengthException, IllegalStateException, InvalidCipherTextException 261 { 262 try 263 { 264 int resultLen = 0; 265 266 if (outOff + bufOff > out.length) 267 { 268 throw new DataLengthException("output buffer too short for doFinal()"); 269 } 270 271 if (bufOff != 0) 272 { 273 if (!partialBlockOkay) 274 { 275 throw new DataLengthException("data not block size aligned"); 276 } 277 278 cipher.processBlock(buf, 0, buf, 0); 279 resultLen = bufOff; 280 bufOff = 0; 281 System.arraycopy(buf, 0, out, outOff, resultLen); 282 } 283 284 return resultLen; 285 } 286 finally 287 { 288 reset(); 289 } 290 } 291 292 /** 293 * Reset the buffer and cipher. After resetting the object is in the same 294 * state as it was after the last init (if there was one). 295 */ 296 public void reset() 297 { 298 // 299 // clean the buffer. 300 // 301 for (int i = 0; i < buf.length; i++) 302 { 303 buf[i] = 0; 304 } 305 306 bufOff = 0; 307 308 // 309 // reset the underlying cipher. 310 // 311 cipher.reset(); 312 } 313} 314