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 }