• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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      */
CTSBlockCipher( BlockCipher cipher)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      */
getUpdateOutputSize( int len)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      */
getOutputSize( int len)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      */
processByte( byte in, byte[] out, int outOff)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      */
processBytes( byte[] in, int inOff, int len, byte[] out, int outOff)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      */
doFinal( byte[] out, int outOff)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