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 */ 018 019package org.apache.commons.crypto.stream; 020 021import java.io.IOException; 022import java.io.OutputStream; 023import java.nio.ByteBuffer; 024import java.nio.channels.WritableByteChannel; 025import java.security.GeneralSecurityException; 026import java.security.Key; 027import java.security.spec.AlgorithmParameterSpec; 028import java.util.Objects; 029import java.util.Properties; 030 031import javax.crypto.Cipher; 032import javax.crypto.ShortBufferException; 033import javax.crypto.spec.IvParameterSpec; 034 035import org.apache.commons.crypto.cipher.CryptoCipher; 036import org.apache.commons.crypto.stream.output.ChannelOutput; 037import org.apache.commons.crypto.stream.output.Output; 038import org.apache.commons.crypto.stream.output.StreamOutput; 039import org.apache.commons.crypto.utils.Utils; 040 041/** 042 * {@link CryptoOutputStream} encrypts data and writes to the under layer 043 * output. It supports any mode of operations such as AES CBC/CTR/GCM mode in 044 * concept. It is not thread-safe. 045 * <p> 046 * This class should only be used with blocking sinks. Using this class to wrap 047 * a non-blocking sink may lead to high CPU usage. 048 * </p> 049 */ 050 051public class CryptoOutputStream extends OutputStream implements 052 WritableByteChannel { 053 private final byte[] oneByteBuf = new byte[1]; 054 055 /** The output. */ 056 final Output output; // package protected for access by rypto classes; do not expose further 057 058 /** the CryptoCipher instance */ 059 final CryptoCipher cipher; // package protected for access by crypto classes; do not expose further 060 061 /** The buffer size. */ 062 private final int bufferSize; 063 064 /** Crypto key for the cipher. */ 065 final Key key; // package protected for access by crypto classes; do not expose further 066 067 /** the algorithm parameters */ 068 private final AlgorithmParameterSpec params; 069 070 /** Flag to mark whether the output stream is closed. */ 071 private boolean closed; 072 073 /** 074 * Input data buffer. The data starts at inBuffer.position() and ends at 075 * inBuffer.limit(). 076 */ 077 ByteBuffer inBuffer; // package protected for access by crypto classes; do not expose further 078 079 /** 080 * Encrypted data buffer. The data starts at outBuffer.position() and ends 081 * at outBuffer.limit(). 082 */ 083 ByteBuffer outBuffer; // package protected for access by crypto classes; do not expose further 084 085 /** 086 * Constructs a {@link CryptoOutputStream}. 087 * 088 * @param output the output stream. 089 * @param cipher the CryptoCipher instance. 090 * @param bufferSize the bufferSize. 091 * @param key crypto key for the cipher. 092 * @param params the algorithm parameters. 093 * @throws IOException if an I/O error occurs. 094 */ 095 protected CryptoOutputStream(final Output output, final CryptoCipher cipher, 096 final int bufferSize, final Key key, final AlgorithmParameterSpec params) 097 throws IOException { 098 099 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}