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
19 package org.apache.commons.crypto.stream;
20
21 import java.io.IOException;
22 import java.io.OutputStream;
23 import java.nio.ByteBuffer;
24 import java.nio.channels.WritableByteChannel;
25 import java.security.GeneralSecurityException;
26 import java.security.Key;
27 import java.security.spec.AlgorithmParameterSpec;
28 import java.util.Objects;
29 import java.util.Properties;
30
31 import javax.crypto.Cipher;
32 import javax.crypto.ShortBufferException;
33 import javax.crypto.spec.IvParameterSpec;
34
35 import org.apache.commons.crypto.cipher.CryptoCipher;
36 import org.apache.commons.crypto.stream.output.ChannelOutput;
37 import org.apache.commons.crypto.stream.output.Output;
38 import org.apache.commons.crypto.stream.output.StreamOutput;
39 import org.apache.commons.crypto.utils.Utils;
40
41 /**
42 * {@link CryptoOutputStream} encrypts data and writes to the under layer
43 * output. It supports any mode of operations such as AES CBC/CTR/GCM mode in
44 * concept. It is not thread-safe.
45 * <p>
46 * This class should only be used with blocking sinks. Using this class to wrap
47 * a non-blocking sink may lead to high CPU usage.
48 * </p>
49 */
50
51 public class CryptoOutputStream extends OutputStream implements
52 WritableByteChannel {
53 private final byte[] oneByteBuf = new byte[1];
54
55 /** The output. */
56 final Output output; // package protected for access by rypto classes; do not expose further
57
58 /** the CryptoCipher instance */
59 final CryptoCipher cipher; // package protected for access by crypto classes; do not expose further
60
61 /** The buffer size. */
62 private final int bufferSize;
63
64 /** Crypto key for the cipher. */
65 final Key key; // package protected for access by crypto classes; do not expose further
66
67 /** the algorithm parameters */
68 private final AlgorithmParameterSpec params;
69
70 /** Flag to mark whether the output stream is closed. */
71 private boolean closed;
72
73 /**
74 * Input data buffer. The data starts at inBuffer.position() and ends at
75 * inBuffer.limit().
76 */
77 ByteBuffer inBuffer; // package protected for access by crypto classes; do not expose further
78
79 /**
80 * Encrypted data buffer. The data starts at outBuffer.position() and ends
81 * at outBuffer.limit().
82 */
83 ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further
84
85 /**
86 * Constructs a {@link CryptoOutputStream}.
87 *
88 * @param output the output stream.
89 * @param cipher the CryptoCipher instance.
90 * @param bufferSize the bufferSize.
91 * @param key crypto key for the cipher.
92 * @param params the algorithm parameters.
93 * @throws IOException if an I/O error occurs.
94 */
95 protected CryptoOutputStream(final Output output, final CryptoCipher cipher,
96 final int bufferSize, final Key key, final AlgorithmParameterSpec params)
97 throws IOException {
98
99 this.output = output;
100 this.bufferSize = CryptoInputStream.checkBufferSize(cipher, bufferSize);
101 this.cipher = cipher;
102
103 this.key = key;
104 this.params = params;
105
106 if (!(params instanceof IvParameterSpec)) {
107 // other AlgorithmParameterSpec such as GCMParameterSpec is not
108 // supported now.
109 throw new IOException("Illegal parameters");
110 }
111
112 inBuffer = ByteBuffer.allocateDirect(this.bufferSize);
113 outBuffer = ByteBuffer.allocateDirect(this.bufferSize
114 + cipher.getBlockSize());
115
116 initCipher();
117 }
118
119 /**
120 * Constructs a {@link CryptoOutputStream}.
121 *
122 * @param outputStream the output stream.
123 * @param cipher the CryptoCipher instance.
124 * @param bufferSize the bufferSize.
125 * @param key crypto key for the cipher.
126 * @param params the algorithm parameters.
127 * @throws IOException if an I/O error occurs.
128 */
129 @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
130 protected CryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
131 final int bufferSize, final Key key, final AlgorithmParameterSpec params)
132 throws IOException {
133 this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, params);
134 }
135
136 /**
137 * Constructs a {@link CryptoOutputStream}.
138 *
139 * @param transformation the name of the transformation, e.g.,
140 * <i>AES/CBC/PKCS5Padding</i>.
141 * See the Java Cryptography Architecture Standard Algorithm Name Documentation
142 * for information about standard transformation names.
143 * @param properties The {@code Properties} class represents a set of
144 * properties.
145 * @param outputStream the output stream.
146 * @param key crypto key for the cipher.
147 * @param params the algorithm parameters.
148 * @throws IOException if an I/O error occurs.
149 */
150 @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CryptoOutputStream.
151 public CryptoOutputStream(final String transformation,
152 final Properties properties, final OutputStream outputStream, final Key key,
153 final AlgorithmParameterSpec params) throws IOException {
154 this(outputStream, Utils.getCipherInstance(transformation, properties),
155 CryptoInputStream.getBufferSize(properties), key, params);
156
157 }
158
159 /**
160 * Constructs a {@link CryptoOutputStream}.
161 *
162 * @param transformation the name of the transformation, e.g.,
163 * <i>AES/CBC/PKCS5Padding</i>.
164 * See the Java Cryptography Architecture Standard Algorithm Name Documentation
165 * for information about standard transformation names.
166 * @param properties The {@code Properties} class represents a set of
167 * properties.
168 * @param out the WritableByteChannel instance.
169 * @param key crypto key for the cipher.
170 * @param params the algorithm parameters.
171 * @throws IOException if an I/O error occurs.
172 */
173 @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CryptoOutputStream.
174 public CryptoOutputStream(final String transformation,
175 final Properties properties, final WritableByteChannel out, final Key key,
176 final AlgorithmParameterSpec params) throws IOException {
177 this(out, Utils.getCipherInstance(transformation, properties), CryptoInputStream
178 .getBufferSize(properties), key, params);
179
180 }
181
182 /**
183 * Constructs a {@link CryptoOutputStream}.
184 *
185 * @param channel the WritableByteChannel instance.
186 * @param cipher the cipher instance.
187 * @param bufferSize the bufferSize.
188 * @param key crypto key for the cipher.
189 * @param params the algorithm parameters.
190 * @throws IOException if an I/O error occurs.
191 */
192 @SuppressWarnings("resource") // Closing the instance closes the ChannelOutput
193 protected CryptoOutputStream(final WritableByteChannel channel, final CryptoCipher cipher,
194 final int bufferSize, final Key key, final AlgorithmParameterSpec params)
195 throws IOException {
196 this(new ChannelOutput(channel), cipher, bufferSize, key, params);
197 }
198
199 /**
200 * Checks whether the stream is closed.
201 *
202 * @throws IOException if an I/O error occurs.
203 */
204 protected void checkStream() throws IOException {
205 if (closed) {
206 throw new IOException("Stream closed");
207 }
208 }
209
210 /**
211 * Overrides the {@link OutputStream#close()}. Closes this output stream and
212 * releases any system resources associated with this stream.
213 *
214 * @throws IOException if an I/O error occurs.
215 */
216 @Override
217 public void close() throws IOException {
218 if (closed) {
219 return;
220 }
221
222 try {
223 encryptFinal();
224 output.close();
225 freeBuffers();
226 cipher.close();
227 super.close();
228 } finally {
229 closed = true;
230 }
231 }
232
233 /**
234 * Does the encryption, input is {@link #inBuffer} and output is
235 * {@link #outBuffer}.
236 *
237 * @throws IOException if an I/O error occurs.
238 */
239 protected void encrypt() throws IOException {
240
241 inBuffer.flip();
242 outBuffer.clear();
243
244 try {
245 cipher.update(inBuffer, outBuffer);
246 } catch (final ShortBufferException e) {
247 throw new IOException(e);
248 }
249
250 inBuffer.clear();
251 outBuffer.flip();
252
253 // write to output
254 while (outBuffer.hasRemaining()) {
255 output.write(outBuffer);
256 }
257 }
258
259 /**
260 * Does final encryption of the last data.
261 *
262 * @throws IOException if an I/O error occurs.
263 */
264 protected void encryptFinal() throws IOException {
265 inBuffer.flip();
266 outBuffer.clear();
267
268 try {
269 cipher.doFinal(inBuffer, outBuffer);
270 } catch (final GeneralSecurityException e) {
271 throw new IOException(e);
272 }
273
274 inBuffer.clear();
275 outBuffer.flip();
276
277 // write to output
278 while (outBuffer.hasRemaining()) {
279 output.write(outBuffer);
280 }
281 }
282
283 /**
284 * Overrides the {@link OutputStream#flush()}. To flush, we need to encrypt
285 * the data in the buffer and write to the underlying stream, then do the
286 * flush.
287 *
288 * @throws IOException if an I/O error occurs.
289 */
290 @Override
291 public void flush() throws IOException {
292 checkStream();
293 encrypt();
294 output.flush();
295 super.flush();
296 }
297
298 /** Forcibly free the direct buffers. */
299 protected void freeBuffers() {
300 CryptoInputStream.freeDirectBuffer(inBuffer);
301 CryptoInputStream.freeDirectBuffer(outBuffer);
302 }
303
304 /**
305 * Gets the buffer size.
306 *
307 * @return the buffer size.
308 */
309 protected int getBufferSize() {
310 return bufferSize;
311 }
312
313 /**
314 * Gets the internal Cipher.
315 *
316 * @return the cipher instance.
317 */
318 protected CryptoCipher getCipher() {
319 return cipher;
320 }
321
322 /**
323 * Gets the inBuffer.
324 *
325 * @return the inBuffer.
326 */
327 protected ByteBuffer getInBuffer() {
328 return inBuffer;
329 }
330
331 /**
332 * Gets the outBuffer.
333 *
334 * @return the outBuffer.
335 */
336 protected ByteBuffer getOutBuffer() {
337 return outBuffer;
338 }
339
340 /**
341 * Initializes the cipher.
342 *
343 * @throws IOException if an I/O error occurs.
344 */
345 protected void initCipher() throws IOException {
346 try {
347 cipher.init(Cipher.ENCRYPT_MODE, key, params);
348 } catch (final GeneralSecurityException e) {
349 throw new IOException(e);
350 }
351 }
352
353 /**
354 * Overrides the {@link java.nio.channels.Channel#isOpen()}. Tells whether or not this channel
355 * is open.
356 *
357 * @return {@code true} if, and only if, this channel is open
358 */
359 @Override
360 public boolean isOpen() {
361 return !closed;
362 }
363
364 /**
365 * Overrides the {@link java.io.OutputStream#write(byte[], int, int)}.
366 * Encryption is buffer based. If there is enough room in {@link #inBuffer},
367 * then write to this buffer. If {@link #inBuffer} is full, then do
368 * encryption and write data to the underlying stream.
369 *
370 * @param array the data.
371 * @param off the start offset in the data.
372 * @param len the number of bytes to write.
373 * @throws IOException if an I/O error occurs.
374 */
375 @Override
376 public void write(final byte[] array, int off, int len) throws IOException {
377 checkStream();
378 Objects.requireNonNull(array, "array");
379 final int arrayLength = array.length;
380 if (off < 0 || len < 0 || off > arrayLength || len > arrayLength - off) {
381 throw new IndexOutOfBoundsException();
382 }
383
384 while (len > 0) {
385 final int remaining = inBuffer.remaining();
386 if (len < remaining) {
387 inBuffer.put(array, off, len);
388 len = 0;
389 } else {
390 inBuffer.put(array, off, remaining);
391 off += remaining;
392 len -= remaining;
393 encrypt();
394 }
395 }
396 }
397
398 /**
399 * Overrides the
400 * {@link java.nio.channels.WritableByteChannel#write(ByteBuffer)}. Writes a
401 * sequence of bytes to this channel from the given buffer.
402 *
403 * @param src The buffer from which bytes are to be retrieved.
404 * @return The number of bytes written, possibly zero.
405 * @throws IOException if an I/O error occurs.
406 */
407 @Override
408 public int write(final ByteBuffer src) throws IOException {
409 checkStream();
410 final int len = src.remaining();
411 int remaining = len;
412 while (remaining > 0) {
413 final int space = inBuffer.remaining();
414 if (remaining < space) {
415 inBuffer.put(src);
416 remaining = 0;
417 } else {
418 // to void copy twice, we set the limit to copy directly
419 final int oldLimit = src.limit();
420 final int newLimit = src.position() + space;
421 src.limit(newLimit);
422
423 inBuffer.put(src);
424
425 // restore the old limit
426 src.limit(oldLimit);
427
428 remaining -= space;
429 encrypt();
430 }
431 }
432
433 return len;
434 }
435
436 /**
437 * Overrides the {@link java.io.OutputStream#write(byte[])}. Writes the
438 * specified byte to this output stream.
439 *
440 * @param b the data.
441 * @throws IOException if an I/O error occurs.
442 */
443 @Override
444 public void write(final int b) throws IOException {
445 oneByteBuf[0] = (byte) (b & 0xff);
446 write(oneByteBuf, 0, oneByteBuf.length);
447 }
448 }