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