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 || cipher instanceof StreamCipher) 52 { 53 partialBlockOkay = true; 54 } 55 else 56 { 57 partialBlockOkay = (idx > 0 && (name.startsWith("OpenPGP", 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 if (forEncryption) 119 { 120 leftOver = total % buf.length - (cipher.getBlockSize() + 2); 121 } 122 else 123 { 124 leftOver = total % buf.length; 125 } 126 } 127 else 128 { 129 leftOver = total % buf.length; 130 } 131 132 return total - leftOver; 133 } 134 135 /** 136 * return the size of the output buffer required for an update plus a 137 * doFinal with an input of 'length' bytes. 138 * 139 * @param length the length of the input. 140 * @return the space required to accommodate a call to update and doFinal 141 * with 'length' bytes of input. 142 */ 143 public int getOutputSize( 144 int length) 145 { 146 // Note: Can assume partialBlockOkay is true for purposes of this calculation 147 return length + bufOff; 148 } 149 150 /** 151 * process a single byte, producing an output block if necessary. 152 * 153 * @param in the input byte. 154 * @param out the space for any output that might be produced. 155 * @param outOff the offset from which the output will be copied. 156 * @return the number of output bytes copied to out. 157 * @exception DataLengthException if there isn't enough space in out. 158 * @exception IllegalStateException if the cipher isn't initialised. 159 */ 160 public int processByte( 161 byte in, 162 byte[] out, 163 int outOff) 164 throws DataLengthException, IllegalStateException 165 { 166 int resultLen = 0; 167 168 buf[bufOff++] = in; 169 170 if (bufOff == buf.length) 171 { 172 resultLen = cipher.processBlock(buf, 0, out, outOff); 173 bufOff = 0; 174 } 175 176 return resultLen; 177 } 178 179 /** 180 * process an array of bytes, producing output if necessary. 181 * 182 * @param in the input byte array. 183 * @param inOff the offset at which the input data starts. 184 * @param len the number of bytes to be copied out of the input array. 185 * @param out the space for any output that might be produced. 186 * @param outOff the offset from which the output will be copied. 187 * @return the number of output bytes copied to out. 188 * @exception DataLengthException if there isn't enough space in out. 189 * @exception IllegalStateException if the cipher isn't initialised. 190 */ 191 public int processBytes( 192 byte[] in, 193 int inOff, 194 int len, 195 byte[] out, 196 int outOff) 197 throws DataLengthException, IllegalStateException 198 { 199 if (len < 0) 200 { 201 throw new IllegalArgumentException("Can't have a negative input length!"); 202 } 203 204 int blockSize = getBlockSize(); 205 int length = getUpdateOutputSize(len); 206 207 if (length > 0) 208 { 209 if ((outOff + length) > out.length) 210 { 211 throw new OutputLengthException("output buffer too short"); 212 } 213 } 214 215 int resultLen = 0; 216 int gapLen = buf.length - bufOff; 217 218 if (len > gapLen) 219 { 220 System.arraycopy(in, inOff, buf, bufOff, gapLen); 221 222 resultLen += cipher.processBlock(buf, 0, out, outOff); 223 224 bufOff = 0; 225 len -= gapLen; 226 inOff += gapLen; 227 228 while (len > buf.length) 229 { 230 resultLen += cipher.processBlock(in, inOff, out, outOff + resultLen); 231 232 len -= blockSize; 233 inOff += blockSize; 234 } 235 } 236 237 System.arraycopy(in, inOff, buf, bufOff, len); 238 239 bufOff += len; 240 241 if (bufOff == buf.length) 242 { 243 resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen); 244 bufOff = 0; 245 } 246 247 return resultLen; 248 } 249 250 /** 251 * Process the last block in the buffer. 252 * 253 * @param out the array the block currently being held is copied into. 254 * @param outOff the offset at which the copying starts. 255 * @return the number of output bytes copied to out. 256 * @exception DataLengthException if there is insufficient space in out for 257 * the output, or the input is not block size aligned and should be. 258 * @exception IllegalStateException if the underlying cipher is not 259 * initialised. 260 * @exception InvalidCipherTextException if padding is expected and not found. 261 * @exception DataLengthException if the input is not block size 262 * aligned. 263 */ 264 public int doFinal( 265 byte[] out, 266 int outOff) 267 throws DataLengthException, IllegalStateException, InvalidCipherTextException 268 { 269 try 270 { 271 int resultLen = 0; 272 273 if (outOff + bufOff > out.length) 274 { 275 throw new OutputLengthException("output buffer too short for doFinal()"); 276 } 277 278 if (bufOff != 0) 279 { 280 if (!partialBlockOkay) 281 { 282 throw new DataLengthException("data not block size aligned"); 283 } 284 285 cipher.processBlock(buf, 0, buf, 0); 286 resultLen = bufOff; 287 bufOff = 0; 288 System.arraycopy(buf, 0, out, outOff, resultLen); 289 } 290 291 return resultLen; 292 } 293 finally 294 { 295 reset(); 296 } 297 } 298 299 /** 300 * Reset the buffer and cipher. After resetting the object is in the same 301 * state as it was after the last init (if there was one). 302 */ 303 public void reset() 304 { 305 // 306 // clean the buffer. 307 // 308 for (int i = 0; i < buf.length; i++) 309 { 310 buf[i] = 0; 311 } 312 313 bufOff = 0; 314 315 // 316 // reset the underlying cipher. 317 // 318 cipher.reset(); 319 } 320} 321