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.InputStream; 22 import java.nio.ByteBuffer; 23 import java.nio.channels.ReadableByteChannel; 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.cipher.CryptoCipherFactory; 32 import org.apache.commons.crypto.stream.input.ChannelInput; 33 import org.apache.commons.crypto.stream.input.Input; 34 import org.apache.commons.crypto.stream.input.StreamInput; 35 import org.apache.commons.crypto.utils.AES; 36 import org.apache.commons.crypto.utils.Utils; 37 38 /** 39 * <p> 40 * CtrCryptoInputStream decrypts data. AES CTR mode is required in order to 41 * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto 42 * stream has stream characteristic which is useful for implement features like 43 * random seek. The decryption is buffer based. The key points of the decryption 44 * are (1) calculating the counter and (2) padding through stream position: 45 * </p> 46 * <p> 47 * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm 48 * blocksize); 49 * </p> 50 * The underlying stream offset is maintained as state. It is not thread-safe. 51 */ 52 public class CtrCryptoInputStream extends CryptoInputStream { 53 /** 54 * <p> 55 * This method is only for Counter (CTR) mode. Generally the CryptoCipher 56 * calculates the IV and maintain encryption context internally.For example 57 * a Cipher will maintain its encryption context internally when we do 58 * encryption/decryption using the CryptoCipher#update interface. 59 * </p> 60 * <p> 61 * Encryption/Decryption is not always on the entire file. For example, in 62 * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In 63 * these situations, the counter is derived from the file position. 64 * </p> 65 * The IV can be calculated by combining the initial IV and the counter with 66 * a lossless operation (concatenation, addition, or XOR). 67 * 68 * @see <a 69 * href="http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29"> 70 * http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29</a> 71 * 72 * @param initIV initial IV 73 * @param counter counter for input stream position 74 * @param IV the IV for input stream position 75 */ 76 static void calculateIV(final byte[] initIV, long counter, final byte[] IV) { 77 int i = IV.length; // IV length 78 79 Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE); 80 Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE); 81 82 int j = 0; // counter bytes index 83 int sum = 0; 84 while (i-- > 0) { 85 // (sum >>> Byte.SIZE) is the carry for addition 86 sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD 87 if (j++ < 8) { // Big-endian, and long is 8 bytes length 88 sum += (byte) counter & 0xff; 89 counter >>>= 8; 90 } 91 IV[i] = (byte) sum; 92 } 93 } 94 95 /** 96 * Underlying stream offset 97 */ 98 private long streamOffset; 99 100 /** 101 * The initial IV. 102 */ 103 private final byte[] initIV; 104 105 /** 106 * Initialization vector for the cipher. 107 */ 108 private final byte[] iv; 109 110 /** 111 * Padding = pos%(algorithm blocksize); Padding is put into 112 * {@link #inBuffer} before any other data goes in. The purpose of padding 113 * is to put the input data at proper position. 114 */ 115 private byte padding; 116 117 /** 118 * Flag to mark whether the cipher has been reset 119 */ 120 private boolean cipherReset; 121 122 /** 123 * Constructs a {@link CtrCryptoInputStream}. 124 * 125 * @param input the input data. 126 * @param cipher the CryptoCipher instance. 127 * @param bufferSize the bufferSize. 128 * @param key crypto key for the cipher. 129 * @param iv Initialization vector for the cipher. 130 * @throws IOException if an I/O error occurs. 131 */ 132 protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, 133 final int bufferSize, final byte[] key, final byte[] iv) throws IOException { 134 this(input, cipher, bufferSize, key, iv, 0); 135 } 136 137 /** 138 * Constructs a {@link CtrCryptoInputStream}. 139 * 140 * @param input the input data. 141 * @param cipher the CryptoCipher instance. 142 * @param bufferSize the bufferSize. 143 * @param key crypto key for the cipher. 144 * @param iv Initialization vector for the cipher. 145 * @param streamOffset the start offset in the stream. 146 * @throws IOException if an I/O error occurs. 147 */ 148 protected CtrCryptoInputStream(final Input input, final CryptoCipher cipher, 149 final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) 150 throws IOException { 151 super(input, cipher, bufferSize, AES.newSecretKeySpec(key), 152 new IvParameterSpec(iv)); 153 154 this.initIV = iv.clone(); 155 this.iv = iv.clone(); 156 157 CryptoInputStream.checkStreamCipher(cipher); 158 159 resetStreamOffset(streamOffset); 160 } 161 162 /** 163 * Constructs a {@link CtrCryptoInputStream}. 164 * 165 * @param inputStream the input stream. 166 * @param cipher the CryptoCipher instance. 167 * @param bufferSize the bufferSize. 168 * @param key crypto key for the cipher. 169 * @param iv Initialization vector for the cipher. 170 * @throws IOException if an I/O error occurs. 171 */ 172 protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher, 173 final int bufferSize, final byte[] key, final byte[] iv) throws IOException { 174 this(inputStream, cipher, bufferSize, key, iv, 0); 175 } 176 177 /** 178 * Constructs a {@link CtrCryptoInputStream}. 179 * 180 * @param inputStream the InputStream instance. 181 * @param cipher the CryptoCipher instance. 182 * @param bufferSize the bufferSize. 183 * @param key crypto key for the cipher. 184 * @param iv Initialization vector for the cipher. 185 * @param streamOffset the start offset in the stream. 186 * @throws IOException if an I/O error occurs. 187 */ 188 @SuppressWarnings("resource") // Closing the instance closes the StreamInput 189 protected CtrCryptoInputStream(final InputStream inputStream, final CryptoCipher cipher, 190 final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) 191 throws IOException { 192 this(new StreamInput(inputStream, bufferSize), cipher, bufferSize, key, iv, 193 streamOffset); 194 } 195 196 /** 197 * Constructs a {@link CtrCryptoInputStream}. 198 * 199 * @param properties The {@code Properties} class represents a set of 200 * properties. 201 * @param inputStream the input stream. 202 * @param key crypto key for the cipher. 203 * @param iv Initialization vector for the cipher. 204 * @throws IOException if an I/O error occurs. 205 */ 206 public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key, 207 final byte[] iv) throws IOException { 208 this(properties, inputStream, key, iv, 0); 209 } 210 211 /** 212 * Constructs a {@link CtrCryptoInputStream}. 213 * 214 * @param properties The {@code Properties} class represents a set of 215 * properties. 216 * @param inputStream the InputStream instance. 217 * @param key crypto key for the cipher. 218 * @param iv Initialization vector for the cipher. 219 * @param streamOffset the start offset in the stream. 220 * @throws IOException if an I/O error occurs. 221 */ 222 @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoInputStream. 223 public CtrCryptoInputStream(final Properties properties, final InputStream inputStream, final byte[] key, 224 final byte[] iv, final long streamOffset) throws IOException { 225 this(inputStream, Utils.getCipherInstance( 226 AES.CTR_NO_PADDING, properties), 227 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); 228 } 229 230 /** 231 * Constructs a {@link CtrCryptoInputStream}. 232 * 233 * @param properties The {@code Properties} class represents a set of 234 * properties. 235 * @param channel the ReadableByteChannel instance. 236 * @param key crypto key for the cipher. 237 * @param iv Initialization vector for the cipher. 238 * @throws IOException if an I/O error occurs. 239 */ 240 public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel channel, 241 final byte[] key, final byte[] iv) throws IOException { 242 this(properties, channel, key, iv, 0); 243 } 244 245 /** 246 * Constructs a {@link CtrCryptoInputStream}. 247 * 248 * @param properties The {@code Properties} class represents a set of 249 * properties. 250 * @param in the ReadableByteChannel instance. 251 * @param key crypto key for the cipher. 252 * @param iv Initialization vector for the cipher. 253 * @param streamOffset the start offset in the stream. 254 * @throws IOException if an I/O error occurs. 255 */ 256 @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoInputStream. 257 public CtrCryptoInputStream(final Properties properties, final ReadableByteChannel in, 258 final byte[] key, final byte[] iv, final long streamOffset) throws IOException { 259 this(in, Utils.getCipherInstance( 260 AES.CTR_NO_PADDING, properties), 261 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset); 262 } 263 264 /** 265 * Constructs a {@link CtrCryptoInputStream}. 266 * 267 * @param channel the ReadableByteChannel instance. 268 * @param cipher the cipher instance. 269 * @param bufferSize the bufferSize. 270 * @param key crypto key for the cipher. 271 * @param iv Initialization vector for the cipher. 272 * @throws IOException if an I/O error occurs. 273 */ 274 protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, 275 final int bufferSize, final byte[] key, final byte[] iv) throws IOException { 276 this(channel, cipher, bufferSize, key, iv, 0); 277 } 278 279 /** 280 * Constructs a {@link CtrCryptoInputStream}. 281 * 282 * @param channel the ReadableByteChannel instance. 283 * @param cipher the CryptoCipher instance. 284 * @param bufferSize the bufferSize. 285 * @param key crypto key for the cipher. 286 * @param iv Initialization vector for the cipher. 287 * @param streamOffset the start offset in the stream. 288 * @throws IOException if an I/O error occurs. 289 */ 290 @SuppressWarnings("resource") // Closing the instance closes the ChannelInput 291 protected CtrCryptoInputStream(final ReadableByteChannel channel, final CryptoCipher cipher, 292 final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset) 293 throws IOException { 294 this(new ChannelInput(channel), cipher, bufferSize, key, iv, streamOffset); 295 } 296 297 /** 298 * Does the decryption using inBuffer as input and outBuffer as output. Upon 299 * return, inBuffer is cleared; the decrypted data starts at 300 * outBuffer.position() and ends at outBuffer.limit(). 301 * 302 * @throws IOException if an I/O error occurs. 303 */ 304 @Override 305 protected void decrypt() throws IOException { 306 Utils.checkState(inBuffer.position() >= padding); 307 if (inBuffer.position() == padding) { 308 // There is no real data in inBuffer. 309 return; 310 } 311 312 inBuffer.flip(); 313 outBuffer.clear(); 314 decryptBuffer(outBuffer); 315 inBuffer.clear(); 316 outBuffer.flip(); 317 318 if (padding > 0) { 319 /* 320 * The plain text and cipher text have a 1:1 mapping, they start at 321 * the same position. 322 */ 323 outBuffer.position(padding); 324 } 325 } 326 327 /** 328 * Decrypts all data in buf: total n bytes from given start position. Output 329 * is also buf and same start position. buf.position() and buf.limit() 330 * should be unchanged after decryption. 331 * 332 * @param buf The buffer into which bytes are to be transferred. 333 * @param offset the start offset in the data. 334 * @param len the maximum number of decrypted data bytes to read. 335 * @throws IOException if an I/O error occurs. 336 */ 337 protected void decrypt(final ByteBuffer buf, final int offset, final int len) 338 throws IOException { 339 final int pos = buf.position(); 340 final int limit = buf.limit(); 341 int n = 0; 342 while (n < len) { 343 buf.position(offset + n); 344 buf.limit(offset + n + Math.min(len - n, inBuffer.remaining())); 345 inBuffer.put(buf); 346 // Do decryption 347 try { 348 decrypt(); 349 buf.position(offset + n); 350 buf.limit(limit); 351 n += outBuffer.remaining(); 352 buf.put(outBuffer); 353 } finally { 354 padding = postDecryption(streamOffset - (len - n)); 355 } 356 } 357 buf.position(pos); 358 } 359 360 /** 361 * Does the decryption using out as output. 362 * 363 * @param out the output ByteBuffer. 364 * @throws IOException if an I/O error occurs. 365 */ 366 protected void decryptBuffer(final ByteBuffer out) throws IOException { 367 final int inputSize = inBuffer.remaining(); 368 try { 369 final int n = cipher.update(inBuffer, out); 370 if (n < inputSize) { 371 /** 372 * Typically code will not get here. CryptoCipher#update will 373 * consume all input data and put result in outBuffer. 374 * CryptoCipher#doFinal will reset the cipher context. 375 */ 376 cipher.doFinal(inBuffer, out); 377 cipherReset = true; 378 } 379 } catch (final GeneralSecurityException e) { 380 throw new IOException(e); 381 } 382 } 383 384 /** 385 * Does the decryption using inBuffer as input and buf as output. Upon 386 * return, inBuffer is cleared; the buf's position will be equal to 387 * <i>p</i> {@code +} <i>n</i> where <i>p</i> is the position 388 * before decryption, <i>n</i> is the number of bytes decrypted. The buf's 389 * limit will not have changed. 390 * 391 * @param buf The buffer into which bytes are to be transferred. 392 * @throws IOException if an I/O error occurs. 393 */ 394 protected void decryptInPlace(final ByteBuffer buf) throws IOException { 395 Utils.checkState(inBuffer.position() >= padding); 396 Utils.checkState(buf.isDirect()); 397 Utils.checkState(buf.remaining() >= inBuffer.position()); 398 Utils.checkState(padding == 0); 399 400 if (inBuffer.position() == padding) { 401 // There is no real data in inBuffer. 402 return; 403 } 404 inBuffer.flip(); 405 decryptBuffer(buf); 406 inBuffer.clear(); 407 } 408 409 /** 410 * Decrypts more data by reading the under layer stream. The decrypted data 411 * will be put in the output buffer. 412 * 413 * @return The number of decrypted data. -1 if end of the decrypted stream. 414 * @throws IOException if an I/O error occurs. 415 */ 416 @Override 417 protected int decryptMore() throws IOException { 418 final int n = input.read(inBuffer); 419 if (n <= 0) { 420 return n; 421 } 422 423 streamOffset += n; // Read n bytes 424 decrypt(); 425 padding = postDecryption(streamOffset); 426 return outBuffer.remaining(); 427 } 428 429 /** 430 * Gets the counter for input stream position. 431 * 432 * @param position the given position in the data. 433 * @return the counter for input stream position. 434 */ 435 protected long getCounter(final long position) { 436 return position / cipher.getBlockSize(); 437 } 438 439 /** 440 * Gets the initialization vector. 441 * 442 * @return the initIV. 443 */ 444 protected byte[] getInitIV() { 445 return initIV; 446 } 447 448 /** 449 * Gets the padding for input stream position. 450 * 451 * @param position the given position in the data. 452 * @return the padding for input stream position. 453 */ 454 protected byte getPadding(final long position) { 455 return (byte) (position % cipher.getBlockSize()); 456 } 457 458 /** 459 * Gets the offset of the stream. 460 * 461 * @return the stream offset. 462 */ 463 protected long getStreamOffset() { 464 return streamOffset; 465 } 466 467 /** 468 * Gets the position of the stream. 469 * 470 * @return the position of the stream. 471 */ 472 protected long getStreamPosition() { 473 return streamOffset - outBuffer.remaining(); 474 } 475 476 /** 477 * Overrides the {@link CtrCryptoInputStream#initCipher()}. Initializes the 478 * cipher. 479 */ 480 @Override 481 protected void initCipher() { 482 // Do nothing for initCipher 483 // Will reset the cipher when reset the stream offset 484 } 485 486 /** 487 * This method is executed immediately after decryption. Checks whether 488 * cipher should be updated and recalculate padding if needed. 489 * 490 * @param position the given position in the data.. 491 * @return the byte. 492 * @throws IOException if an I/O error occurs. 493 */ 494 protected byte postDecryption(final long position) throws IOException { 495 byte padding = 0; 496 if (cipherReset) { 497 /* 498 * This code is generally not executed since the cipher usually 499 * maintains cipher context (e.g. the counter) internally. However, 500 * some implementations can't maintain context so a re-init is 501 * necessary after each decryption call. 502 */ 503 resetCipher(position); 504 padding = getPadding(position); 505 inBuffer.position(padding); 506 } 507 return padding; 508 } 509 510 /** 511 * Overrides the {@link CtrCryptoInputStream#read(ByteBuffer)}. Reads a 512 * sequence of bytes from this channel into the given buffer. 513 * 514 * @param buf The buffer into which bytes are to be transferred. 515 * @return The number of bytes read, possibly zero, or {@code -1} if the 516 * channel has reached end-of-stream. 517 * @throws IOException if an I/O error occurs. 518 */ 519 @Override 520 public int read(final ByteBuffer buf) throws IOException { 521 checkStream(); 522 int unread = outBuffer.remaining(); 523 if (unread <= 0) { // Fill the unread decrypted data buffer firstly 524 final int n = input.read(inBuffer); 525 if (n <= 0) { 526 return n; 527 } 528 529 streamOffset += n; // Read n bytes 530 if (buf.isDirect() && buf.remaining() >= inBuffer.position() 531 && padding == 0) { 532 // Use buf as the output buffer directly 533 decryptInPlace(buf); 534 padding = postDecryption(streamOffset); 535 return n; 536 } 537 // Use outBuffer as the output buffer 538 decrypt(); 539 padding = postDecryption(streamOffset); 540 } 541 542 // Copy decrypted data from outBuffer to buf 543 unread = outBuffer.remaining(); 544 final int toRead = buf.remaining(); 545 if (toRead <= unread) { 546 final int limit = outBuffer.limit(); 547 outBuffer.limit(outBuffer.position() + toRead); 548 buf.put(outBuffer); 549 outBuffer.limit(limit); 550 return toRead; 551 } 552 buf.put(outBuffer); 553 return unread; 554 } 555 556 /** 557 * Calculates the counter and iv, resets the cipher. 558 * 559 * @param position the given position in the data. 560 * @throws IOException if an I/O error occurs. 561 */ 562 protected void resetCipher(final long position) throws IOException { 563 final long counter = getCounter(position); 564 CtrCryptoInputStream.calculateIV(initIV, counter, iv); 565 try { 566 cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv)); 567 } catch (final GeneralSecurityException e) { 568 throw new IOException(e); 569 } 570 cipherReset = false; 571 } 572 573 /** 574 * Resets the underlying stream offset; clear {@link #inBuffer} and 575 * {@link #outBuffer}. This Typically happens during {@link #skip(long)}. 576 * 577 * @param offset the offset of the stream. 578 * @throws IOException if an I/O error occurs. 579 */ 580 protected void resetStreamOffset(final long offset) throws IOException { 581 streamOffset = offset; 582 inBuffer.clear(); 583 outBuffer.clear(); 584 outBuffer.limit(0); 585 resetCipher(offset); 586 padding = getPadding(offset); 587 inBuffer.position(padding); // Set proper position for input data. 588 } 589 590 /** 591 * Seeks the stream to a specific position relative to start of the under 592 * layer stream. 593 * 594 * @param position the given position in the data. 595 * @throws IOException if an I/O error occurs. 596 */ 597 public void seek(final long position) throws IOException { 598 Utils.checkArgument(position >= 0, "Cannot seek to negative offset."); 599 checkStream(); 600 /* 601 * If data of target pos in the underlying stream has already been read 602 * and decrypted in outBuffer, we just need to re-position outBuffer. 603 */ 604 if (position >= getStreamPosition() && position <= getStreamOffset()) { 605 final int forward = (int) (position - getStreamPosition()); 606 if (forward > 0) { 607 outBuffer.position(outBuffer.position() + forward); 608 } 609 } else { 610 input.seek(position); 611 resetStreamOffset(position); 612 } 613 } 614 615 /** 616 * Sets the offset of stream. 617 * 618 * @param streamOffset the stream offset. 619 */ 620 protected void setStreamOffset(final long streamOffset) { 621 this.streamOffset = streamOffset; 622 } 623 624 /** 625 * Overrides the {@link CryptoInputStream#skip(long)}. Skips over and 626 * discards {@code n} bytes of data from this input stream. 627 * 628 * @param n the number of bytes to be skipped. 629 * @return the actual number of bytes skipped. 630 * @throws IOException if an I/O error occurs. 631 */ 632 @Override 633 public long skip(long n) throws IOException { 634 Utils.checkArgument(n >= 0, "Negative skip length."); 635 checkStream(); 636 637 if (n == 0) { 638 return 0; 639 } 640 if (n <= outBuffer.remaining()) { 641 final int pos = outBuffer.position() + (int) n; 642 outBuffer.position(pos); 643 return n; 644 } 645 /* 646 * Subtract outBuffer.remaining() to see how many bytes we need to 647 * skip in the underlying stream. Add outBuffer.remaining() to the 648 * actual number of skipped bytes in the underlying stream to get 649 * the number of skipped bytes from the user's point of view. 650 */ 651 n -= outBuffer.remaining(); 652 long skipped = input.skip(n); 653 if (skipped < 0) { 654 skipped = 0; 655 } 656 final long pos = streamOffset + skipped; 657 skipped += outBuffer.remaining(); 658 resetStreamOffset(pos); 659 return skipped; 660 } 661 }