Home | History | Annotate | Download | only in modes
      1 package org.bouncycastle.crypto.modes;
      2 
      3 import org.bouncycastle.crypto.BlockCipher;
      4 import org.bouncycastle.crypto.BufferedBlockCipher;
      5 import org.bouncycastle.crypto.DataLengthException;
      6 import org.bouncycastle.crypto.InvalidCipherTextException;
      7 import org.bouncycastle.crypto.StreamBlockCipher;
      8 
      9 /**
     10  * A Cipher Text Stealing (CTS) mode cipher. CTS allows block ciphers to
     11  * be used to produce cipher text which is the same length as the plain text.
     12  */
     13 public class CTSBlockCipher
     14     extends BufferedBlockCipher
     15 {
     16     private int     blockSize;
     17 
     18     /**
     19      * Create a buffered block cipher that uses Cipher Text Stealing
     20      *
     21      * @param cipher the underlying block cipher this buffering object wraps.
     22      */
     23     public CTSBlockCipher(
     24         BlockCipher     cipher)
     25     {
     26         if (cipher instanceof StreamBlockCipher)
     27         {
     28             throw new IllegalArgumentException("CTSBlockCipher can only accept ECB, or CBC ciphers");
     29         }
     30 
     31         this.cipher = cipher;
     32 
     33         blockSize = cipher.getBlockSize();
     34 
     35         buf = new byte[blockSize * 2];
     36         bufOff = 0;
     37     }
     38 
     39     /**
     40      * return the size of the output buffer required for an update
     41      * an input of len bytes.
     42      *
     43      * @param len the length of the input.
     44      * @return the space required to accommodate a call to update
     45      * with len bytes of input.
     46      */
     47     public int getUpdateOutputSize(
     48         int len)
     49     {
     50         int total       = len + bufOff;
     51         int leftOver    = total % buf.length;
     52 
     53         if (leftOver == 0)
     54         {
     55             return total - buf.length;
     56         }
     57 
     58         return total - leftOver;
     59     }
     60 
     61     /**
     62      * return the size of the output buffer required for an update plus a
     63      * doFinal with an input of len bytes.
     64      *
     65      * @param len the length of the input.
     66      * @return the space required to accommodate a call to update and doFinal
     67      * with len bytes of input.
     68      */
     69     public int getOutputSize(
     70         int len)
     71     {
     72         return len + bufOff;
     73     }
     74 
     75     /**
     76      * process a single byte, producing an output block if necessary.
     77      *
     78      * @param in the input byte.
     79      * @param out the space for any output that might be produced.
     80      * @param outOff the offset from which the output will be copied.
     81      * @return the number of output bytes copied to out.
     82      * @exception DataLengthException if there isn't enough space in out.
     83      * @exception IllegalStateException if the cipher isn't initialised.
     84      */
     85     public int processByte(
     86         byte        in,
     87         byte[]      out,
     88         int         outOff)
     89         throws DataLengthException, IllegalStateException
     90     {
     91         int         resultLen = 0;
     92 
     93         if (bufOff == buf.length)
     94         {
     95             resultLen = cipher.processBlock(buf, 0, out, outOff);
     96             System.arraycopy(buf, blockSize, buf, 0, blockSize);
     97 
     98             bufOff = blockSize;
     99         }
    100 
    101         buf[bufOff++] = in;
    102 
    103         return resultLen;
    104     }
    105 
    106     /**
    107      * process an array of bytes, producing output if necessary.
    108      *
    109      * @param in the input byte array.
    110      * @param inOff the offset at which the input data starts.
    111      * @param len the number of bytes to be copied out of the input array.
    112      * @param out the space for any output that might be produced.
    113      * @param outOff the offset from which the output will be copied.
    114      * @return the number of output bytes copied to out.
    115      * @exception DataLengthException if there isn't enough space in out.
    116      * @exception IllegalStateException if the cipher isn't initialised.
    117      */
    118     public int processBytes(
    119         byte[]      in,
    120         int         inOff,
    121         int         len,
    122         byte[]      out,
    123         int         outOff)
    124         throws DataLengthException, IllegalStateException
    125     {
    126         if (len < 0)
    127         {
    128             throw new IllegalArgumentException("Can't have a negative input length!");
    129         }
    130 
    131         int blockSize   = getBlockSize();
    132         int length      = getUpdateOutputSize(len);
    133 
    134         if (length > 0)
    135         {
    136             if ((outOff + length) > out.length)
    137             {
    138                 throw new DataLengthException("output buffer too short");
    139             }
    140         }
    141 
    142         int resultLen = 0;
    143         int gapLen = buf.length - bufOff;
    144 
    145         if (len > gapLen)
    146         {
    147             System.arraycopy(in, inOff, buf, bufOff, gapLen);
    148 
    149             resultLen += cipher.processBlock(buf, 0, out, outOff);
    150             System.arraycopy(buf, blockSize, buf, 0, blockSize);
    151 
    152             bufOff = blockSize;
    153 
    154             len -= gapLen;
    155             inOff += gapLen;
    156 
    157             while (len > blockSize)
    158             {
    159                 System.arraycopy(in, inOff, buf, bufOff, blockSize);
    160                 resultLen += cipher.processBlock(buf, 0, out, outOff + resultLen);
    161                 System.arraycopy(buf, blockSize, buf, 0, blockSize);
    162 
    163                 len -= blockSize;
    164                 inOff += blockSize;
    165             }
    166         }
    167 
    168         System.arraycopy(in, inOff, buf, bufOff, len);
    169 
    170         bufOff += len;
    171 
    172         return resultLen;
    173     }
    174 
    175     /**
    176      * Process the last block in the buffer.
    177      *
    178      * @param out the array the block currently being held is copied into.
    179      * @param outOff the offset at which the copying starts.
    180      * @return the number of output bytes copied to out.
    181      * @exception DataLengthException if there is insufficient space in out for
    182      * the output.
    183      * @exception IllegalStateException if the underlying cipher is not
    184      * initialised.
    185      * @exception InvalidCipherTextException if cipher text decrypts wrongly (in
    186      * case the exception will never get thrown).
    187      */
    188     public int doFinal(
    189         byte[]  out,
    190         int     outOff)
    191         throws DataLengthException, IllegalStateException, InvalidCipherTextException
    192     {
    193         if (bufOff + outOff > out.length)
    194         {
    195             throw new DataLengthException("output buffer to small in doFinal");
    196         }
    197 
    198         int     blockSize = cipher.getBlockSize();
    199         int     len = bufOff - blockSize;
    200         byte[]  block = new byte[blockSize];
    201 
    202         if (forEncryption)
    203         {
    204             if (bufOff < blockSize)
    205             {
    206                 throw new DataLengthException("need at least one block of input for CTS");
    207             }
    208 
    209             cipher.processBlock(buf, 0, block, 0);
    210 
    211             if (bufOff > blockSize)
    212             {
    213                 for (int i = bufOff; i != buf.length; i++)
    214                 {
    215                     buf[i] = block[i - blockSize];
    216                 }
    217 
    218                 for (int i = blockSize; i != bufOff; i++)
    219                 {
    220                     buf[i] ^= block[i - blockSize];
    221                 }
    222 
    223                 if (cipher instanceof CBCBlockCipher)
    224                 {
    225                     BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
    226 
    227                     c.processBlock(buf, blockSize, out, outOff);
    228                 }
    229                 else
    230                 {
    231                     cipher.processBlock(buf, blockSize, out, outOff);
    232                 }
    233 
    234                 System.arraycopy(block, 0, out, outOff + blockSize, len);
    235             }
    236             else
    237             {
    238                 System.arraycopy(block, 0, out, outOff, blockSize);
    239             }
    240         }
    241         else
    242         {
    243             if (bufOff < blockSize)
    244             {
    245                 throw new DataLengthException("need at least one block of input for CTS");
    246             }
    247 
    248             byte[]  lastBlock = new byte[blockSize];
    249 
    250             if (bufOff > blockSize)
    251             {
    252                 if (cipher instanceof CBCBlockCipher)
    253                 {
    254                     BlockCipher c = ((CBCBlockCipher)cipher).getUnderlyingCipher();
    255 
    256                     c.processBlock(buf, 0, block, 0);
    257                 }
    258                 else
    259                 {
    260                     cipher.processBlock(buf, 0, block, 0);
    261                 }
    262 
    263                 for (int i = blockSize; i != bufOff; i++)
    264                 {
    265                     lastBlock[i - blockSize] = (byte)(block[i - blockSize] ^ buf[i]);
    266                 }
    267 
    268                 System.arraycopy(buf, blockSize, block, 0, len);
    269 
    270                 cipher.processBlock(block, 0, out, outOff);
    271                 System.arraycopy(lastBlock, 0, out, outOff + blockSize, len);
    272             }
    273             else
    274             {
    275                 cipher.processBlock(buf, 0, block, 0);
    276 
    277                 System.arraycopy(block, 0, out, outOff, blockSize);
    278             }
    279         }
    280 
    281         int offset = bufOff;
    282 
    283         reset();
    284 
    285         return offset;
    286     }
    287 }
    288