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