View Javadoc
1    /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.commons.crypto.stream;
19  
20  import java.io.IOException;
21  import java.io.OutputStream;
22  import java.nio.ByteBuffer;
23  import java.nio.channels.WritableByteChannel;
24  import java.security.GeneralSecurityException;
25  import java.util.Properties;
26  
27  import javax.crypto.Cipher;
28  import javax.crypto.spec.IvParameterSpec;
29  
30  import org.apache.commons.crypto.cipher.CryptoCipher;
31  import org.apache.commons.crypto.stream.output.ChannelOutput;
32  import org.apache.commons.crypto.stream.output.Output;
33  import org.apache.commons.crypto.stream.output.StreamOutput;
34  import org.apache.commons.crypto.utils.AES;
35  import org.apache.commons.crypto.utils.Utils;
36  
37  /**
38   * <p>
39   * CtrCryptoOutputStream encrypts data. It is not thread-safe. AES CTR mode is
40   * required in order to ensure that the plain text and cipher text have a 1:1
41   * mapping. The encryption is buffer based. The key points of the encryption are
42   * (1) calculating counter and (2) padding through stream position.
43   * </p>
44   * <p>
45   * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
46   * blocksize);
47   * </p>
48   * <p>
49   * The underlying stream offset is maintained as state.
50   * </p>
51   * <p>
52   * This class should only be used with blocking sinks. Using this class to wrap
53   * a non-blocking sink may lead to high CPU usage.
54   * </p>
55   */
56  public class CtrCryptoOutputStream extends CryptoOutputStream {
57      /**
58       * Underlying stream offset.
59       */
60      private long streamOffset;
61  
62      /**
63       * The initial IV.
64       */
65      private final byte[] initIV;
66  
67      /**
68       * Initialization vector for the cipher.
69       */
70      private final byte[] iv;
71  
72      /**
73       * Padding = pos%(algorithm blocksize); Padding is put into
74       * {@link #inBuffer} before any other data goes in. The purpose of padding
75       * is to put input data at proper position.
76       */
77      private byte padding;
78  
79      /**
80       * Flag to mark whether the cipher has been reset
81       */
82      private boolean cipherReset;
83  
84      /**
85       * Constructs a {@link CtrCryptoOutputStream}.
86       *
87       * @param output the Output instance.
88       * @param cipher the CryptoCipher instance.
89       * @param bufferSize the bufferSize.
90       * @param key crypto key for the cipher.
91       * @param iv Initialization vector for the cipher.
92       * @throws IOException if an I/O error occurs.
93       */
94      protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
95              final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
96          this(output, cipher, bufferSize, key, iv, 0);
97      }
98  
99      /**
100      * Constructs a {@link CtrCryptoOutputStream}.
101      *
102      * @param output the output stream.
103      * @param cipher the CryptoCipher instance.
104      * @param bufferSize the bufferSize.
105      * @param key crypto key for the cipher.
106      * @param iv Initialization vector for the cipher.
107      * @param streamOffset the start offset in the data.
108      * @throws IOException if an I/O error occurs.
109      */
110     protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
111             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
112             throws IOException {
113         super(output, cipher, bufferSize, AES.newSecretKeySpec(key),
114                 new IvParameterSpec(iv));
115 
116         CryptoInputStream.checkStreamCipher(cipher);
117         this.streamOffset = streamOffset;
118         this.initIV = iv.clone();
119         this.iv = iv.clone();
120 
121         resetCipher();
122     }
123 
124     /**
125      * Constructs a {@link CtrCryptoOutputStream}.
126      *
127      * @param out the output stream.
128      * @param cipher the CryptoCipher instance.
129      * @param bufferSize the bufferSize.
130      * @param key crypto key for the cipher.
131      * @param iv Initialization vector for the cipher.
132      * @throws IOException if an I/O error occurs.
133      */
134     protected CtrCryptoOutputStream(final OutputStream out, final CryptoCipher cipher,
135             final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
136         this(out, cipher, bufferSize, key, iv, 0);
137     }
138 
139     /**
140      * Constructs a {@link CtrCryptoOutputStream}.
141      *
142      * @param outputStream the output stream.
143      * @param cipher the CryptoCipher instance.
144      * @param bufferSize the bufferSize.
145      * @param key crypto key for the cipher.
146      * @param iv Initialization vector for the cipher.
147      * @param streamOffset the start offset in the data.
148      * @throws IOException if an I/O error occurs.
149      */
150     @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
151     protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
152             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
153             throws IOException {
154         this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, streamOffset);
155     }
156 
157     /**
158      * Constructs a {@link CtrCryptoOutputStream}.
159      *
160      * @param props The {@code Properties} class represents a set of
161      *        properties.
162      * @param out the output stream.
163      * @param key crypto key for the cipher.
164      * @param iv Initialization vector for the cipher.
165      * @throws IOException if an I/O error occurs.
166      */
167     public CtrCryptoOutputStream(final Properties props, final OutputStream out,
168             final byte[] key, final byte[] iv) throws IOException {
169         this(props, out, key, iv, 0);
170     }
171 
172     /**
173      * Constructs a {@link CtrCryptoOutputStream}.
174      *
175      * @param properties The {@code Properties} class represents a set of
176      *        properties.
177      * @param outputStream the output stream.
178      * @param key crypto key for the cipher.
179      * @param iv Initialization vector for the cipher.
180      * @param streamOffset the start offset in the data.
181      * @throws IOException if an I/O error occurs.
182      */
183     @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream.
184     public CtrCryptoOutputStream(final Properties properties, final OutputStream outputStream,
185             final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
186         this(outputStream, Utils.getCipherInstance(
187                 AES.CTR_NO_PADDING, properties),
188                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
189     }
190 
191     /**
192      * Constructs a {@link CtrCryptoOutputStream}.
193      *
194      * @param props The {@code Properties} class represents a set of
195      *        properties.
196      * @param out the WritableByteChannel instance.
197      * @param key crypto key for the cipher.
198      * @param iv Initialization vector for the cipher.
199      * @throws IOException if an I/O error occurs.
200      */
201     public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out,
202             final byte[] key, final byte[] iv) throws IOException {
203         this(props, out, key, iv, 0);
204     }
205 
206     /**
207      * Constructs a {@link CtrCryptoOutputStream}.
208      *
209      * @param properties The {@code Properties} class represents a set of
210      *        properties.
211      * @param channel the WritableByteChannel instance.
212      * @param key crypto key for the cipher.
213      * @param iv Initialization vector for the cipher.
214      * @param streamOffset the start offset in the data.
215      * @throws IOException if an I/O error occurs.
216      */
217     @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream.
218     public CtrCryptoOutputStream(final Properties properties, final WritableByteChannel channel,
219             final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
220         this(channel, Utils.getCipherInstance(
221                 AES.CTR_NO_PADDING, properties),
222                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
223     }
224 
225     /**
226      * Constructs a {@link CtrCryptoOutputStream}.
227      *
228      * @param channel the WritableByteChannel instance.
229      * @param cipher the CryptoCipher instance.
230      * @param bufferSize the bufferSize.
231      * @param key crypto key for the cipher.
232      * @param iv Initialization vector for the cipher.
233      * @throws IOException if an I/O error occurs.
234      */
235     protected CtrCryptoOutputStream(final WritableByteChannel channel,
236             final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv)
237             throws IOException {
238         this(channel, cipher, bufferSize, key, iv, 0);
239     }
240 
241     /**
242      * Constructs a {@link CtrCryptoOutputStream}.
243      *
244      * @param channel the WritableByteChannel instance.
245      * @param cipher the CryptoCipher instance.
246      * @param bufferSize the bufferSize.
247      * @param key crypto key for the cipher.
248      * @param iv Initialization vector for the cipher.
249      * @param streamOffset the start offset in the data.
250      * @throws IOException if an I/O error occurs.
251      */
252    @SuppressWarnings("resource") // Closing the instance closes the ChannelOutput
253    protected CtrCryptoOutputStream(final WritableByteChannel channel,
254             final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv,
255             final long streamOffset) throws IOException {
256        this(new ChannelOutput(channel), cipher, bufferSize, key, iv, streamOffset);
257     }
258 
259     /**
260      * Does the encryption, input is {@link #inBuffer} and output is
261      * {@link #outBuffer}.
262      *
263      * @throws IOException if an I/O error occurs.
264      */
265     @Override
266     protected void encrypt() throws IOException {
267         Utils.checkState(inBuffer.position() >= padding);
268         if (inBuffer.position() == padding) {
269             // There is no real data in the inBuffer.
270             return;
271         }
272 
273         inBuffer.flip();
274         outBuffer.clear();
275         encryptBuffer(outBuffer);
276         inBuffer.clear();
277         outBuffer.flip();
278 
279         if (padding > 0) {
280             /*
281              * The plain text and cipher text have a 1:1 mapping, they start at
282              * the same position.
283              */
284             outBuffer.position(padding);
285             padding = 0;
286         }
287 
288         final int len = output.write(outBuffer);
289         streamOffset += len;
290         if (cipherReset) {
291             /*
292              * This code is generally not executed since the encryptor usually
293              * maintains encryption context (e.g. the counter) internally.
294              * However, some implementations can't maintain context so a re-init
295              * is necessary after each encryption call.
296              */
297             resetCipher();
298         }
299     }
300 
301     /**
302      * Does the encryption if the ByteBuffer data.
303      *
304      * @param out the output ByteBuffer.
305      * @throws IOException if an I/O error occurs.
306      */
307     private void encryptBuffer(final ByteBuffer out) throws IOException {
308         final int inputSize = inBuffer.remaining();
309         try {
310             final int n = cipher.update(inBuffer, out);
311             if (n < inputSize) {
312                 /**
313                  * Typically code will not get here. CryptoCipher#update will
314                  * consume all input data and put result in outBuffer.
315                  * CryptoCipher#doFinal will reset the cipher context.
316                  */
317                 cipher.doFinal(inBuffer, out);
318                 cipherReset = true;
319             }
320         } catch (final GeneralSecurityException e) {
321             throw new IOException(e);
322         }
323     }
324 
325     /**
326      * Does final encryption of the last data.
327      *
328      * @throws IOException if an I/O error occurs.
329      */
330     @Override
331     protected void encryptFinal() throws IOException {
332         // The same as the normal encryption for Counter mode
333         encrypt();
334     }
335 
336     /**
337      * Get the underlying stream offset
338      *
339      * @return the underlying stream offset
340      */
341     protected long getStreamOffset() {
342         return streamOffset;
343     }
344 
345     /**
346      * Overrides the {@link CryptoOutputStream#initCipher()}. Initializes the
347      * cipher.
348      */
349     @Override
350     protected void initCipher() {
351         // Do nothing for initCipher
352         // Will reset the cipher considering the stream offset
353     }
354 
355     /**
356      * Resets the {@link #cipher}: calculate counter and {@link #padding}.
357      *
358      * @throws IOException if an I/O error occurs.
359      */
360     private void resetCipher() throws IOException {
361         final long counter = streamOffset
362                 / cipher.getBlockSize();
363         padding = (byte) (streamOffset % cipher.getBlockSize());
364         inBuffer.position(padding); // Set proper position for input data.
365 
366         CtrCryptoInputStream.calculateIV(initIV, counter, iv);
367         try {
368             cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
369         } catch (final GeneralSecurityException e) {
370             throw new IOException(e);
371         }
372         cipherReset = false;
373     }
374 
375     /**
376      * Set the underlying stream offset
377      *
378      * @param streamOffset the underlying stream offset
379      */
380     protected void setStreamOffset(final long streamOffset) {
381         this.streamOffset = streamOffset;
382     }
383 }