Home | History | Annotate | Download | only in crypto
      1 package 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  */
     12 public 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 OutputLengthException("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 OutputLengthException("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