001 /*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.commons.crypto.stream;
019
020import java.io.IOException;
021import java.io.OutputStream;
022import java.nio.ByteBuffer;
023import java.nio.channels.WritableByteChannel;
024import java.security.GeneralSecurityException;
025import java.util.Properties;
026
027import javax.crypto.Cipher;
028import javax.crypto.spec.IvParameterSpec;
029
030import org.apache.commons.crypto.cipher.CryptoCipher;
031import org.apache.commons.crypto.stream.output.ChannelOutput;
032import org.apache.commons.crypto.stream.output.Output;
033import org.apache.commons.crypto.stream.output.StreamOutput;
034import org.apache.commons.crypto.utils.AES;
035import org.apache.commons.crypto.utils.Utils;
036
037/**
038 * <p>
039 * CtrCryptoOutputStream encrypts data. It is not thread-safe. AES CTR mode is
040 * required in order to ensure that the plain text and cipher text have a 1:1
041 * mapping. The encryption is buffer based. The key points of the encryption are
042 * (1) calculating counter and (2) padding through stream position.
043 * </p>
044 * <p>
045 * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
046 * blocksize);
047 * </p>
048 * <p>
049 * The underlying stream offset is maintained as state.
050 * </p>
051 * <p>
052 * This class should only be used with blocking sinks. Using this class to wrap
053 * a non-blocking sink may lead to high CPU usage.
054 * </p>
055 */
056public class CtrCryptoOutputStream extends CryptoOutputStream {
057    /**
058     * Underlying stream offset.
059     */
060    private long streamOffset;
061
062    /**
063     * The initial IV.
064     */
065    private final byte[] initIV;
066
067    /**
068     * Initialization vector for the cipher.
069     */
070    private final byte[] iv;
071
072    /**
073     * Padding = pos%(algorithm blocksize); Padding is put into
074     * {@link #inBuffer} before any other data goes in. The purpose of padding
075     * is to put input data at proper position.
076     */
077    private byte padding;
078
079    /**
080     * Flag to mark whether the cipher has been reset
081     */
082    private boolean cipherReset;
083
084    /**
085     * Constructs a {@link CtrCryptoOutputStream}.
086     *
087     * @param output the Output instance.
088     * @param cipher the CryptoCipher instance.
089     * @param bufferSize the bufferSize.
090     * @param key crypto key for the cipher.
091     * @param iv Initialization vector for the cipher.
092     * @throws IOException if an I/O error occurs.
093     */
094    protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
095            final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
096        this(output, cipher, bufferSize, key, iv, 0);
097    }
098
099    /**
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}