CtrCryptoOutputStream.java

  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. import java.io.IOException;
  20. import java.io.OutputStream;
  21. import java.nio.ByteBuffer;
  22. import java.nio.channels.WritableByteChannel;
  23. import java.security.GeneralSecurityException;
  24. import java.util.Properties;

  25. import javax.crypto.Cipher;
  26. import javax.crypto.spec.IvParameterSpec;

  27. import org.apache.commons.crypto.cipher.CryptoCipher;
  28. import org.apache.commons.crypto.stream.output.ChannelOutput;
  29. import org.apache.commons.crypto.stream.output.Output;
  30. import org.apache.commons.crypto.stream.output.StreamOutput;
  31. import org.apache.commons.crypto.utils.AES;
  32. import org.apache.commons.crypto.utils.Utils;

  33. /**
  34.  * <p>
  35.  * CtrCryptoOutputStream encrypts data. It is not thread-safe. AES CTR mode is
  36.  * required in order to ensure that the plain text and cipher text have a 1:1
  37.  * mapping. The encryption is buffer based. The key points of the encryption are
  38.  * (1) calculating counter and (2) padding through stream position.
  39.  * </p>
  40.  * <p>
  41.  * counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
  42.  * blocksize);
  43.  * </p>
  44.  * <p>
  45.  * The underlying stream offset is maintained as state.
  46.  * </p>
  47.  * <p>
  48.  * This class should only be used with blocking sinks. Using this class to wrap
  49.  * a non-blocking sink may lead to high CPU usage.
  50.  * </p>
  51.  */
  52. public class CtrCryptoOutputStream extends CryptoOutputStream {
  53.     /**
  54.      * Underlying stream offset.
  55.      */
  56.     private long streamOffset;

  57.     /**
  58.      * The initial IV.
  59.      */
  60.     private final byte[] initIV;

  61.     /**
  62.      * Initialization vector for the cipher.
  63.      */
  64.     private final byte[] iv;

  65.     /**
  66.      * Padding = pos%(algorithm blocksize); Padding is put into
  67.      * {@link #inBuffer} before any other data goes in. The purpose of padding
  68.      * is to put input data at proper position.
  69.      */
  70.     private byte padding;

  71.     /**
  72.      * Flag to mark whether the cipher has been reset
  73.      */
  74.     private boolean cipherReset;

  75.     /**
  76.      * Constructs a {@link CtrCryptoOutputStream}.
  77.      *
  78.      * @param output the Output instance.
  79.      * @param cipher the CryptoCipher instance.
  80.      * @param bufferSize the bufferSize.
  81.      * @param key crypto key for the cipher.
  82.      * @param iv Initialization vector for the cipher.
  83.      * @throws IOException if an I/O error occurs.
  84.      */
  85.     protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
  86.             final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
  87.         this(output, cipher, bufferSize, key, iv, 0);
  88.     }

  89.     /**
  90.      * Constructs a {@link CtrCryptoOutputStream}.
  91.      *
  92.      * @param output the output stream.
  93.      * @param cipher the CryptoCipher instance.
  94.      * @param bufferSize the bufferSize.
  95.      * @param key crypto key for the cipher.
  96.      * @param iv Initialization vector for the cipher.
  97.      * @param streamOffset the start offset in the data.
  98.      * @throws IOException if an I/O error occurs.
  99.      */
  100.     protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
  101.             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
  102.             throws IOException {
  103.         super(output, cipher, bufferSize, AES.newSecretKeySpec(key),
  104.                 new IvParameterSpec(iv));

  105.         CryptoInputStream.checkStreamCipher(cipher);
  106.         this.streamOffset = streamOffset;
  107.         this.initIV = iv.clone();
  108.         this.iv = iv.clone();

  109.         resetCipher();
  110.     }

  111.     /**
  112.      * Constructs a {@link CtrCryptoOutputStream}.
  113.      *
  114.      * @param out the output stream.
  115.      * @param cipher the CryptoCipher instance.
  116.      * @param bufferSize the bufferSize.
  117.      * @param key crypto key for the cipher.
  118.      * @param iv Initialization vector for the cipher.
  119.      * @throws IOException if an I/O error occurs.
  120.      */
  121.     protected CtrCryptoOutputStream(final OutputStream out, final CryptoCipher cipher,
  122.             final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
  123.         this(out, cipher, bufferSize, key, iv, 0);
  124.     }

  125.     /**
  126.      * Constructs a {@link CtrCryptoOutputStream}.
  127.      *
  128.      * @param outputStream the output stream.
  129.      * @param cipher the CryptoCipher instance.
  130.      * @param bufferSize the bufferSize.
  131.      * @param key crypto key for the cipher.
  132.      * @param iv Initialization vector for the cipher.
  133.      * @param streamOffset the start offset in the data.
  134.      * @throws IOException if an I/O error occurs.
  135.      */
  136.     @SuppressWarnings("resource") // Closing the instance closes the StreamOutput
  137.     protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
  138.             final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
  139.             throws IOException {
  140.         this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, streamOffset);
  141.     }

  142.     /**
  143.      * Constructs a {@link CtrCryptoOutputStream}.
  144.      *
  145.      * @param props The {@code Properties} class represents a set of
  146.      *        properties.
  147.      * @param out the output stream.
  148.      * @param key crypto key for the cipher.
  149.      * @param iv Initialization vector for the cipher.
  150.      * @throws IOException if an I/O error occurs.
  151.      */
  152.     public CtrCryptoOutputStream(final Properties props, final OutputStream out,
  153.             final byte[] key, final byte[] iv) throws IOException {
  154.         this(props, out, key, iv, 0);
  155.     }

  156.     /**
  157.      * Constructs a {@link CtrCryptoOutputStream}.
  158.      *
  159.      * @param properties The {@code Properties} class represents a set of
  160.      *        properties.
  161.      * @param outputStream the output stream.
  162.      * @param key crypto key for the cipher.
  163.      * @param iv Initialization vector for the cipher.
  164.      * @param streamOffset the start offset in the data.
  165.      * @throws IOException if an I/O error occurs.
  166.      */
  167.     @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream.
  168.     public CtrCryptoOutputStream(final Properties properties, final OutputStream outputStream,
  169.             final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
  170.         this(outputStream, Utils.getCipherInstance(
  171.                 AES.CTR_NO_PADDING, properties),
  172.                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
  173.     }

  174.     /**
  175.      * Constructs a {@link CtrCryptoOutputStream}.
  176.      *
  177.      * @param props The {@code Properties} class represents a set of
  178.      *        properties.
  179.      * @param out the WritableByteChannel instance.
  180.      * @param key crypto key for the cipher.
  181.      * @param iv Initialization vector for the cipher.
  182.      * @throws IOException if an I/O error occurs.
  183.      */
  184.     public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out,
  185.             final byte[] key, final byte[] iv) throws IOException {
  186.         this(props, out, key, iv, 0);
  187.     }

  188.     /**
  189.      * Constructs a {@link CtrCryptoOutputStream}.
  190.      *
  191.      * @param properties The {@code Properties} class represents a set of
  192.      *        properties.
  193.      * @param channel the WritableByteChannel instance.
  194.      * @param key crypto key for the cipher.
  195.      * @param iv Initialization vector for the cipher.
  196.      * @param streamOffset the start offset in the data.
  197.      * @throws IOException if an I/O error occurs.
  198.      */
  199.     @SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream.
  200.     public CtrCryptoOutputStream(final Properties properties, final WritableByteChannel channel,
  201.             final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
  202.         this(channel, Utils.getCipherInstance(
  203.                 AES.CTR_NO_PADDING, properties),
  204.                 CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
  205.     }

  206.     /**
  207.      * Constructs a {@link CtrCryptoOutputStream}.
  208.      *
  209.      * @param channel the WritableByteChannel instance.
  210.      * @param cipher the CryptoCipher instance.
  211.      * @param bufferSize the bufferSize.
  212.      * @param key crypto key for the cipher.
  213.      * @param iv Initialization vector for the cipher.
  214.      * @throws IOException if an I/O error occurs.
  215.      */
  216.     protected CtrCryptoOutputStream(final WritableByteChannel channel,
  217.             final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv)
  218.             throws IOException {
  219.         this(channel, cipher, bufferSize, key, iv, 0);
  220.     }

  221.     /**
  222.      * Constructs a {@link CtrCryptoOutputStream}.
  223.      *
  224.      * @param channel the WritableByteChannel instance.
  225.      * @param cipher the CryptoCipher instance.
  226.      * @param bufferSize the bufferSize.
  227.      * @param key crypto key for the cipher.
  228.      * @param iv Initialization vector for the cipher.
  229.      * @param streamOffset the start offset in the data.
  230.      * @throws IOException if an I/O error occurs.
  231.      */
  232.    @SuppressWarnings("resource") // Closing the instance closes the ChannelOutput
  233.    protected CtrCryptoOutputStream(final WritableByteChannel channel,
  234.             final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv,
  235.             final long streamOffset) throws IOException {
  236.        this(new ChannelOutput(channel), cipher, bufferSize, key, iv, streamOffset);
  237.     }

  238.     /**
  239.      * Does the encryption, input is {@link #inBuffer} and output is
  240.      * {@link #outBuffer}.
  241.      *
  242.      * @throws IOException if an I/O error occurs.
  243.      */
  244.     @Override
  245.     protected void encrypt() throws IOException {
  246.         Utils.checkState(inBuffer.position() >= padding);
  247.         if (inBuffer.position() == padding) {
  248.             // There is no real data in the inBuffer.
  249.             return;
  250.         }

  251.         inBuffer.flip();
  252.         outBuffer.clear();
  253.         encryptBuffer(outBuffer);
  254.         inBuffer.clear();
  255.         outBuffer.flip();

  256.         if (padding > 0) {
  257.             /*
  258.              * The plain text and cipher text have a 1:1 mapping, they start at
  259.              * the same position.
  260.              */
  261.             outBuffer.position(padding);
  262.             padding = 0;
  263.         }

  264.         final int len = output.write(outBuffer);
  265.         streamOffset += len;
  266.         if (cipherReset) {
  267.             /*
  268.              * This code is generally not executed since the encryptor usually
  269.              * maintains encryption context (e.g. the counter) internally.
  270.              * However, some implementations can't maintain context so a re-init
  271.              * is necessary after each encryption call.
  272.              */
  273.             resetCipher();
  274.         }
  275.     }

  276.     /**
  277.      * Does the encryption if the ByteBuffer data.
  278.      *
  279.      * @param out the output ByteBuffer.
  280.      * @throws IOException if an I/O error occurs.
  281.      */
  282.     private void encryptBuffer(final ByteBuffer out) throws IOException {
  283.         final int inputSize = inBuffer.remaining();
  284.         try {
  285.             final int n = cipher.update(inBuffer, out);
  286.             if (n < inputSize) {
  287.                 /**
  288.                  * Typically code will not get here. CryptoCipher#update will
  289.                  * consume all input data and put result in outBuffer.
  290.                  * CryptoCipher#doFinal will reset the cipher context.
  291.                  */
  292.                 cipher.doFinal(inBuffer, out);
  293.                 cipherReset = true;
  294.             }
  295.         } catch (final GeneralSecurityException e) {
  296.             throw new IOException(e);
  297.         }
  298.     }

  299.     /**
  300.      * Does final encryption of the last data.
  301.      *
  302.      * @throws IOException if an I/O error occurs.
  303.      */
  304.     @Override
  305.     protected void encryptFinal() throws IOException {
  306.         // The same as the normal encryption for Counter mode
  307.         encrypt();
  308.     }

  309.     /**
  310.      * Get the underlying stream offset
  311.      *
  312.      * @return the underlying stream offset
  313.      */
  314.     protected long getStreamOffset() {
  315.         return streamOffset;
  316.     }

  317.     /**
  318.      * Overrides the {@link CryptoOutputStream#initCipher()}. Initializes the
  319.      * cipher.
  320.      */
  321.     @Override
  322.     protected void initCipher() {
  323.         // Do nothing for initCipher
  324.         // Will reset the cipher considering the stream offset
  325.     }

  326.     /**
  327.      * Resets the {@link #cipher}: calculate counter and {@link #padding}.
  328.      *
  329.      * @throws IOException if an I/O error occurs.
  330.      */
  331.     private void resetCipher() throws IOException {
  332.         final long counter = streamOffset
  333.                 / cipher.getBlockSize();
  334.         padding = (byte) (streamOffset % cipher.getBlockSize());
  335.         inBuffer.position(padding); // Set proper position for input data.

  336.         CtrCryptoInputStream.calculateIV(initIV, counter, iv);
  337.         try {
  338.             cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
  339.         } catch (final GeneralSecurityException e) {
  340.             throw new IOException(e);
  341.         }
  342.         cipherReset = false;
  343.     }

  344.     /**
  345.      * Set the underlying stream offset
  346.      *
  347.      * @param streamOffset the underlying stream offset
  348.      */
  349.     protected void setStreamOffset(final long streamOffset) {
  350.         this.streamOffset = streamOffset;
  351.     }
  352. }