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.InputStream;
022import java.nio.ByteBuffer;
023import java.nio.channels.ReadableByteChannel;
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.cipher.CryptoCipherFactory;
032import org.apache.commons.crypto.stream.input.ChannelInput;
033import org.apache.commons.crypto.stream.input.Input;
034import org.apache.commons.crypto.stream.input.StreamInput;
035import org.apache.commons.crypto.utils.AES;
036import org.apache.commons.crypto.utils.Utils;
037
038/**
039 * <p>
040 * CtrCryptoInputStream decrypts data. AES CTR mode is required in order to
041 * ensure that the plain text and cipher text have a 1:1 mapping. CTR crypto
042 * stream has stream characteristic which is useful for implement features like
043 * random seek. The decryption is buffer based. The key points of the decryption
044 * are (1) calculating the counter and (2) padding through stream position:
045 * </p>
046 * <p>
047 * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
048 * blocksize);
049 * </p>
050 * The underlying stream offset is maintained as state. It is not thread-safe.
051 */
052public class CtrCryptoInputStream extends CryptoInputStream {
053    /**
054     * <p>
055     * This method is only for Counter (CTR) mode. Generally the CryptoCipher
056     * calculates the IV and maintain encryption context internally.For example
057     * a Cipher will maintain its encryption context internally when we do
058     * encryption/decryption using the CryptoCipher#update interface.
059     * </p>
060     * <p>
061     * Encryption/Decryption is not always on the entire file. For example, in
062     * Hadoop, a node may only decrypt a portion of a file (i.e. a split). In
063     * these situations, the counter is derived from the file position.
064     * </p>
065     * The IV can be calculated by combining the initial IV and the counter with
066     * a lossless operation (concatenation, addition, or XOR).
067     *
068     * @see <a
069     *      href="http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29">
070     *      http://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Counter_.28CTR.29</a>
071     *
072     * @param initIV initial IV
073     * @param counter counter for input stream position
074     * @param IV the IV for input stream position
075     */
076    static void calculateIV(final byte[] initIV, long counter, final byte[] IV) {
077        int i = IV.length; // IV length
078
079        Utils.checkArgument(initIV.length == CryptoCipherFactory.AES_BLOCK_SIZE);
080        Utils.checkArgument(i == CryptoCipherFactory.AES_BLOCK_SIZE);
081
082        int j = 0; // counter bytes index
083        int sum = 0;
084        while (i-- > 0) {
085            // (sum >>> Byte.SIZE) is the carry for addition
086            sum = (initIV[i] & 0xff) + (sum >>> Byte.SIZE); // NOPMD
087            if (j++ < 8) { // Big-endian, and long is 8 bytes length
088                sum += (byte) counter & 0xff;
089                counter >>>= 8;
090            }
091            IV[i] = (byte) sum;
092        }
093    }
094
095    /**
096     * Underlying stream offset
097     */
098    private long streamOffset;
099
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>&nbsp;{@code +}&nbsp;<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}