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}