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.nio.ByteBuffer; 022import java.security.GeneralSecurityException; 023import java.util.Properties; 024import java.util.Queue; 025import java.util.concurrent.ConcurrentLinkedQueue; 026 027import javax.crypto.Cipher; 028import javax.crypto.spec.IvParameterSpec; 029 030import org.apache.commons.crypto.cipher.CryptoCipher; 031import org.apache.commons.crypto.stream.input.Input; 032import org.apache.commons.crypto.utils.AES; 033import org.apache.commons.crypto.utils.IoUtils; 034import org.apache.commons.crypto.utils.Utils; 035 036/** 037 * PositionedCryptoInputStream provides the capability to decrypt the stream 038 * starting at random position as well as provides the foundation for positioned 039 * read for decrypting. This needs a stream cipher mode such as AES CTR mode. 040 */ 041public class PositionedCryptoInputStream extends CtrCryptoInputStream { 042 043 private static class CipherState { 044 045 private final CryptoCipher cryptoCipher; 046 private boolean reset; 047 048 /** 049 * Constructs a new instance. 050 * 051 * @param cryptoCipher the CryptoCipher instance. 052 */ 053 public CipherState(final CryptoCipher cryptoCipher) { 054 this.cryptoCipher = cryptoCipher; 055 this.reset = false; 056 } 057 058 /** 059 * Gets the CryptoCipher instance. 060 * 061 * @return the cipher. 062 */ 063 public CryptoCipher getCryptoCipher() { 064 return cryptoCipher; 065 } 066 067 /** 068 * Gets the reset. 069 * 070 * @return the value of reset. 071 */ 072 public boolean isReset() { 073 return reset; 074 } 075 076 /** 077 * Sets the value of reset. 078 * 079 * @param reset the reset. 080 */ 081 public void reset(final boolean reset) { 082 this.reset = reset; 083 } 084 } 085 086 /** 087 * DirectBuffer pool 088 */ 089 private final Queue<ByteBuffer> byteBufferPool = new ConcurrentLinkedQueue<>(); 090 091 /** 092 * CryptoCipher pool 093 */ 094 private final Queue<CipherState> cipherStatePool = new ConcurrentLinkedQueue<>(); 095 096 /** 097 * properties for constructing a CryptoCipher 098 */ 099 private final Properties properties; 100 101 /** 102 * Constructs a {@link PositionedCryptoInputStream}. 103 * 104 * @param properties The {@code Properties} class represents a set of 105 * properties. 106 * @param in the input data. 107 * @param key crypto key for the cipher. 108 * @param iv Initialization vector for the cipher. 109 * @param streamOffset the start offset in the data. 110 * @throws IOException if an I/O error occurs. 111 */ 112 @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by PositionedCryptoInputStream. 113 public PositionedCryptoInputStream(final Properties properties, final Input in, final byte[] key, 114 final byte[] iv, final long streamOffset) throws IOException { 115 this(properties, in, Utils.getCipherInstance(AES.CTR_NO_PADDING, properties), 116 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); 117 } 118 119 /** 120 * Constructs a {@link PositionedCryptoInputStream}. 121 * 122 * @param properties the properties of stream 123 * @param input the input data. 124 * @param cipher the CryptoCipher instance. 125 * @param bufferSize the bufferSize. 126 * @param key crypto key for the cipher. 127 * @param iv Initialization vector for the cipher. 128 * @param streamOffset the start offset in the data. 129 * @throws IOException if an I/O error occurs. 130 */ 131 protected PositionedCryptoInputStream(final Properties properties, final Input input, final CryptoCipher cipher, 132 final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) 133 throws IOException { 134 super(input, cipher, bufferSize, key, iv, streamOffset); 135 this.properties = properties; 136 } 137 138 /** Cleans direct buffer pool */ 139 private void cleanByteBufferPool() { 140 ByteBuffer buf; 141 while ((buf = byteBufferPool.poll()) != null) { 142 CryptoInputStream.freeDirectBuffer(buf); 143 } 144 } 145 146 /** Cleans direct buffer pool */ 147 private void cleanCipherStatePool() { 148 CipherState cs; 149 while ((cs = cipherStatePool.poll()) != null) { 150 try { 151 cs.getCryptoCipher().close(); 152 } catch (IOException ignored) { 153 // ignore 154 } 155 } 156 } 157 158 /** 159 * Overrides the {@link CryptoInputStream#close()}. Closes this input stream 160 * and releases any system resources associated with the stream. 161 * 162 * @throws IOException if an I/O error occurs. 163 */ 164 @Override 165 public void close() throws IOException { 166 if (!isOpen()) { 167 return; 168 } 169 170 cleanByteBufferPool(); 171 cleanCipherStatePool(); 172 super.close(); 173 } 174 175 /** 176 * Does the decryption using inBuffer as input and outBuffer as output. Upon 177 * return, inBuffer is cleared; the decrypted data starts at 178 * outBuffer.position() and ends at outBuffer.limit(). 179 * 180 * @param state the CipherState instance. 181 * @param inByteBuffer the input buffer. 182 * @param outByteBuffer the output buffer. 183 * @param padding the padding. 184 * @throws IOException if an I/O error occurs. 185 */ 186 private void decrypt(final CipherState state, final ByteBuffer inByteBuffer, 187 final ByteBuffer outByteBuffer, final byte padding) throws IOException { 188 Utils.checkState(inByteBuffer.position() >= padding); 189 if (inByteBuffer.position() == padding) { 190 // There is no real data in inBuffer. 191 return; 192 } 193 inByteBuffer.flip(); 194 outByteBuffer.clear(); 195 decryptBuffer(state, inByteBuffer, outByteBuffer); 196 inByteBuffer.clear(); 197 outByteBuffer.flip(); 198 if (padding > 0) { 199 /* 200 * The plain text and cipher text have a 1:1 mapping, they start at 201 * the same position. 202 */ 203 outByteBuffer.position(padding); 204 } 205 } 206 207 /** 208 * Decrypts length bytes in buffer starting at offset. Output is also put 209 * into buffer starting at offset. It is thread-safe. 210 * 211 * @param buffer the buffer into which the data is read. 212 * @param offset the start offset in the data. 213 * @param position the offset from the start of the stream. 214 * @param length the maximum number of bytes to read. 215 * @throws IOException if an I/O error occurs. 216 */ 217 protected void decrypt(final long position, final byte[] buffer, final int offset, final int length) 218 throws IOException { 219 final ByteBuffer inByteBuffer = getBuffer(); 220 final ByteBuffer outByteBuffer = getBuffer(); 221 CipherState state = null; 222 try { 223 state = getCipherState(); 224 final byte[] iv = getInitIV().clone(); 225 resetCipher(state, position, iv); 226 byte padding = getPadding(position); 227 inByteBuffer.position(padding); // Set proper position for input data. 228 229 int n = 0; 230 while (n < length) { 231 final int toDecrypt = Math.min(length - n, inByteBuffer.remaining()); 232 inByteBuffer.put(buffer, offset + n, toDecrypt); 233 234 // Do decryption 235 decrypt(state, inByteBuffer, outByteBuffer, padding); 236 237 outByteBuffer.get(buffer, offset + n, toDecrypt); 238 n += toDecrypt; 239 padding = postDecryption(state, inByteBuffer, position + n, iv); 240 } 241 } finally { 242 returnToPool(inByteBuffer); 243 returnToPool(outByteBuffer); 244 returnToPool(state); 245 } 246 } 247 248 /** 249 * Does the decryption using inBuffer as input and outBuffer as output. 250 * 251 * @param state the CipherState instance. 252 * @param inByteBuffer the input buffer. 253 * @param outByteBuffer the output buffer. 254 * @throws IOException if an I/O error occurs. 255 */ 256 @SuppressWarnings("resource") // getCryptoCipher does not allocate 257 private void decryptBuffer(final CipherState state, final ByteBuffer inByteBuffer, 258 final ByteBuffer outByteBuffer) throws IOException { 259 final int inputSize = inByteBuffer.remaining(); 260 try { 261 final int n = state.getCryptoCipher().update(inByteBuffer, outByteBuffer); 262 if (n < inputSize) { 263 /** 264 * Typically code will not get here. CryptoCipher#update will 265 * consume all input data and put result in outBuffer. 266 * CryptoCipher#doFinal will reset the cipher context. 267 */ 268 state.getCryptoCipher().doFinal(inByteBuffer, outByteBuffer); 269 state.reset(true); 270 } 271 } catch (final GeneralSecurityException e) { 272 throw new IOException(e); 273 } 274 } 275 276 /** 277 * Gets direct buffer from pool. Caller MUST also call {@link #returnToPool(ByteBuffer)}. 278 * 279 * @return the buffer. 280 * @see #returnToPool(ByteBuffer) 281 */ 282 private ByteBuffer getBuffer() { 283 final ByteBuffer buffer = byteBufferPool.poll(); 284 return buffer != null ? buffer : ByteBuffer.allocateDirect(getBufferSize()); 285 } 286 287 /** 288 * Gets CryptoCipher from pool. Caller MUST also call {@link #returnToPool(CipherState)}. 289 * 290 * @return the CipherState instance. 291 * @throws IOException if an I/O error occurs. 292 */ 293 @SuppressWarnings("resource") // Caller calls #returnToPool(CipherState) 294 private CipherState getCipherState() throws IOException { 295 final CipherState state = cipherStatePool.poll(); 296 return state != null ? state : new CipherState(Utils.getCipherInstance(AES.CTR_NO_PADDING, properties)); 297 } 298 299 /** 300 * This method is executed immediately after decryption. Check whether 301 * cipher should be updated and recalculate padding if needed. 302 * 303 * @param state the CipherState instance. 304 * @param inByteBuffer the input buffer. 305 * @param position the offset from the start of the stream. 306 * @param iv the iv. 307 * @return the padding. 308 */ 309 private byte postDecryption(final CipherState state, final ByteBuffer inByteBuffer, 310 final long position, final byte[] iv) { 311 byte padding = 0; 312 if (state.isReset()) { 313 /* 314 * This code is generally not executed since the cipher usually 315 * maintains cipher context (e.g. the counter) internally. However, 316 * some implementations can't maintain context so a re-init is 317 * necessary after each decryption call. 318 */ 319 resetCipher(state, position, iv); 320 padding = getPadding(position); 321 inByteBuffer.position(padding); 322 } 323 return padding; 324 } 325 326 /** 327 * Reads up to the specified number of bytes from a given position within a 328 * stream and return the number of bytes read. This does not change the 329 * current offset of the stream, and is thread-safe. 330 * 331 * @param buffer the buffer into which the data is read. 332 * @param length the maximum number of bytes to read. 333 * @param offset the start offset in the data. 334 * @param position the offset from the start of the stream. 335 * @throws IOException if an I/O error occurs. 336 * @return int the total number of decrypted data bytes read into the 337 * buffer. 338 */ 339 public int read(final long position, final byte[] buffer, final int offset, final int length) 340 throws IOException { 341 checkStream(); 342 final int n = input.read(position, buffer, offset, length); 343 if (n > 0) { 344 // This operation does not change the current offset of the file 345 decrypt(position, buffer, offset, n); 346 } 347 return n; 348 } 349 350 /** 351 * Reads the specified number of bytes from a given position within a 352 * stream. This does not change the current offset of the stream and is 353 * thread-safe. 354 * 355 * @param position the offset from the start of the stream. 356 * @param buffer the buffer into which the data is read. 357 * @throws IOException if an I/O error occurs. 358 */ 359 public void readFully(final long position, final byte[] buffer) throws IOException { 360 readFully(position, buffer, 0, buffer.length); 361 } 362 363 /** 364 * Reads the specified number of bytes from a given position within a 365 * stream. This does not change the current offset of the stream and is 366 * thread-safe. 367 * 368 * @param buffer the buffer into which the data is read. 369 * @param length the maximum number of bytes to read. 370 * @param offset the start offset in the data. 371 * @param position the offset from the start of the stream. 372 * @throws IOException if an I/O error occurs. 373 */ 374 public void readFully(final long position, final byte[] buffer, final int offset, final int length) 375 throws IOException { 376 checkStream(); 377 IoUtils.readFully(input, position, buffer, offset, length); 378 if (length > 0) { 379 // This operation does not change the current offset of the file 380 decrypt(position, buffer, offset, length); 381 } 382 } 383 384 /** 385 * Calculates the counter and iv, reset the cipher. 386 * 387 * @param state the CipherState instance. 388 * @param position the offset from the start of the stream. 389 * @param iv the iv. 390 */ 391 @SuppressWarnings("resource") // getCryptoCipher does not allocate 392 private void resetCipher(final CipherState state, final long position, final byte[] iv) { 393 final long counter = getCounter(position); 394 CtrCryptoInputStream.calculateIV(getInitIV(), counter, iv); 395 try { 396 state.getCryptoCipher().init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); 397 } catch (final GeneralSecurityException e) { 398 // Ignore 399 } 400 state.reset(false); 401 } 402 403 /** 404 * Returns direct buffer to pool. 405 * 406 * @param buf the buffer. 407 */ 408 private void returnToPool(final ByteBuffer buf) { 409 if (buf != null) { 410 buf.clear(); 411 byteBufferPool.add(buf); 412 } 413 } 414 415 /** 416 * Returns CryptoCipher to pool. 417 * 418 * @param state the CipherState instance. 419 */ 420 private void returnToPool(final CipherState state) { 421 if (state != null) { 422 cipherStatePool.add(state); 423 } 424 } 425}