Home | History | Annotate | Download | only in paddings
      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      */
     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      */
     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      */
     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      */
     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      */
    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      */
    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      */
    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      */
    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