CtrCryptoOutputStream.java
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.crypto.stream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.WritableByteChannel;
import java.security.GeneralSecurityException;
import java.util.Properties;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import org.apache.commons.crypto.cipher.CryptoCipher;
import org.apache.commons.crypto.stream.output.ChannelOutput;
import org.apache.commons.crypto.stream.output.Output;
import org.apache.commons.crypto.stream.output.StreamOutput;
import org.apache.commons.crypto.utils.AES;
import org.apache.commons.crypto.utils.Utils;
/**
* <p>
* CtrCryptoOutputStream encrypts data. It is not thread-safe. AES CTR mode is
* required in order to ensure that the plain text and cipher text have a 1:1
* mapping. The encryption is buffer based. The key points of the encryption are
* (1) calculating counter and (2) padding through stream position.
* </p>
* <p>
* counter = base + pos/(algorithm blocksize); padding = pos%(algorithm
* blocksize);
* </p>
* <p>
* The underlying stream offset is maintained as state.
* </p>
* <p>
* This class should only be used with blocking sinks. Using this class to wrap
* a non-blocking sink may lead to high CPU usage.
* </p>
*/
public class CtrCryptoOutputStream extends CryptoOutputStream {
/**
* Underlying stream offset.
*/
private long streamOffset;
/**
* The initial IV.
*/
private final byte[] initIV;
/**
* Initialization vector for the cipher.
*/
private final byte[] iv;
/**
* Padding = pos%(algorithm blocksize); Padding is put into
* {@link #inBuffer} before any other data goes in. The purpose of padding
* is to put input data at proper position.
*/
private byte padding;
/**
* Flag to mark whether the cipher has been reset
*/
private boolean cipherReset;
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param output the Output instance.
* @param cipher the CryptoCipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @throws IOException if an I/O error occurs.
*/
protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
this(output, cipher, bufferSize, key, iv, 0);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param output the output stream.
* @param cipher the CryptoCipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @param streamOffset the start offset in the data.
* @throws IOException if an I/O error occurs.
*/
protected CtrCryptoOutputStream(final Output output, final CryptoCipher cipher,
final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
throws IOException {
super(output, cipher, bufferSize, AES.newSecretKeySpec(key),
new IvParameterSpec(iv));
CryptoInputStream.checkStreamCipher(cipher);
this.streamOffset = streamOffset;
this.initIV = iv.clone();
this.iv = iv.clone();
resetCipher();
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param out the output stream.
* @param cipher the CryptoCipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @throws IOException if an I/O error occurs.
*/
protected CtrCryptoOutputStream(final OutputStream out, final CryptoCipher cipher,
final int bufferSize, final byte[] key, final byte[] iv) throws IOException {
this(out, cipher, bufferSize, key, iv, 0);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param outputStream the output stream.
* @param cipher the CryptoCipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @param streamOffset the start offset in the data.
* @throws IOException if an I/O error occurs.
*/
@SuppressWarnings("resource") // Closing the instance closes the StreamOutput
protected CtrCryptoOutputStream(final OutputStream outputStream, final CryptoCipher cipher,
final int bufferSize, final byte[] key, final byte[] iv, final long streamOffset)
throws IOException {
this(new StreamOutput(outputStream, bufferSize), cipher, bufferSize, key, iv, streamOffset);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param props The {@code Properties} class represents a set of
* properties.
* @param out the output stream.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @throws IOException if an I/O error occurs.
*/
public CtrCryptoOutputStream(final Properties props, final OutputStream out,
final byte[] key, final byte[] iv) throws IOException {
this(props, out, key, iv, 0);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param properties The {@code Properties} class represents a set of
* properties.
* @param outputStream the output stream.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @param streamOffset the start offset in the data.
* @throws IOException if an I/O error occurs.
*/
@SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream.
public CtrCryptoOutputStream(final Properties properties, final OutputStream outputStream,
final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
this(outputStream, Utils.getCipherInstance(
AES.CTR_NO_PADDING, properties),
CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param props The {@code Properties} class represents a set of
* properties.
* @param out the WritableByteChannel instance.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @throws IOException if an I/O error occurs.
*/
public CtrCryptoOutputStream(final Properties props, final WritableByteChannel out,
final byte[] key, final byte[] iv) throws IOException {
this(props, out, key, iv, 0);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param properties The {@code Properties} class represents a set of
* properties.
* @param channel the WritableByteChannel instance.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @param streamOffset the start offset in the data.
* @throws IOException if an I/O error occurs.
*/
@SuppressWarnings("resource") // The CryptoCipher returned by getCipherInstance() is closed by CtrCryptoOutputStream.
public CtrCryptoOutputStream(final Properties properties, final WritableByteChannel channel,
final byte[] key, final byte[] iv, final long streamOffset) throws IOException {
this(channel, Utils.getCipherInstance(
AES.CTR_NO_PADDING, properties),
CryptoInputStream.getBufferSize(properties), key, iv, streamOffset);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param channel the WritableByteChannel instance.
* @param cipher the CryptoCipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @throws IOException if an I/O error occurs.
*/
protected CtrCryptoOutputStream(final WritableByteChannel channel,
final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv)
throws IOException {
this(channel, cipher, bufferSize, key, iv, 0);
}
/**
* Constructs a {@link CtrCryptoOutputStream}.
*
* @param channel the WritableByteChannel instance.
* @param cipher the CryptoCipher instance.
* @param bufferSize the bufferSize.
* @param key crypto key for the cipher.
* @param iv Initialization vector for the cipher.
* @param streamOffset the start offset in the data.
* @throws IOException if an I/O error occurs.
*/
@SuppressWarnings("resource") // Closing the instance closes the ChannelOutput
protected CtrCryptoOutputStream(final WritableByteChannel channel,
final CryptoCipher cipher, final int bufferSize, final byte[] key, final byte[] iv,
final long streamOffset) throws IOException {
this(new ChannelOutput(channel), cipher, bufferSize, key, iv, streamOffset);
}
/**
* Does the encryption, input is {@link #inBuffer} and output is
* {@link #outBuffer}.
*
* @throws IOException if an I/O error occurs.
*/
@Override
protected void encrypt() throws IOException {
Utils.checkState(inBuffer.position() >= padding);
if (inBuffer.position() == padding) {
// There is no real data in the inBuffer.
return;
}
inBuffer.flip();
outBuffer.clear();
encryptBuffer(outBuffer);
inBuffer.clear();
outBuffer.flip();
if (padding > 0) {
/*
* The plain text and cipher text have a 1:1 mapping, they start at
* the same position.
*/
outBuffer.position(padding);
padding = 0;
}
final int len = output.write(outBuffer);
streamOffset += len;
if (cipherReset) {
/*
* This code is generally not executed since the encryptor usually
* maintains encryption context (e.g. the counter) internally.
* However, some implementations can't maintain context so a re-init
* is necessary after each encryption call.
*/
resetCipher();
}
}
/**
* Does the encryption if the ByteBuffer data.
*
* @param out the output ByteBuffer.
* @throws IOException if an I/O error occurs.
*/
private void encryptBuffer(final ByteBuffer out) throws IOException {
final int inputSize = inBuffer.remaining();
try {
final int n = cipher.update(inBuffer, out);
if (n < inputSize) {
/**
* Typically code will not get here. CryptoCipher#update will
* consume all input data and put result in outBuffer.
* CryptoCipher#doFinal will reset the cipher context.
*/
cipher.doFinal(inBuffer, out);
cipherReset = true;
}
} catch (final GeneralSecurityException e) {
throw new IOException(e);
}
}
/**
* Does final encryption of the last data.
*
* @throws IOException if an I/O error occurs.
*/
@Override
protected void encryptFinal() throws IOException {
// The same as the normal encryption for Counter mode
encrypt();
}
/**
* Get the underlying stream offset
*
* @return the underlying stream offset
*/
protected long getStreamOffset() {
return streamOffset;
}
/**
* Overrides the {@link CryptoOutputStream#initCipher()}. Initializes the
* cipher.
*/
@Override
protected void initCipher() {
// Do nothing for initCipher
// Will reset the cipher considering the stream offset
}
/**
* Resets the {@link #cipher}: calculate counter and {@link #padding}.
*
* @throws IOException if an I/O error occurs.
*/
private void resetCipher() throws IOException {
final long counter = streamOffset
/ cipher.getBlockSize();
padding = (byte) (streamOffset % cipher.getBlockSize());
inBuffer.position(padding); // Set proper position for input data.
CtrCryptoInputStream.calculateIV(initIV, counter, iv);
try {
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
} catch (final GeneralSecurityException e) {
throw new IOException(e);
}
cipherReset = false;
}
/**
* Set the underlying stream offset
*
* @param streamOffset the underlying stream offset
*/
protected void setStreamOffset(final long streamOffset) {
this.streamOffset = streamOffset;
}
}