
net.java.truevfs.comp.zip.crypto.CipherOutputStream Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of truevfs-comp-zip Show documentation
Show all versions of truevfs-comp-zip Show documentation
Provides basic functionality for reading and writing ZIP files.
/*
* Copyright (C) 2005-2015 Schlichtherle IT Services.
* All rights reserved. Use is subject to license terms.
*/
package net.java.truevfs.comp.zip.crypto;
import edu.umd.cs.findbugs.annotations.CreatesObligation;
import net.java.truecommons.io.DecoratingOutputStream;
import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import javax.annotation.CheckForNull;
import javax.annotation.WillCloseWhenClosed;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Objects;
/**
* Similar to {@code javax.crypto.CipherOutputStream} with some exceptions:
*
* - This implementation is based on Bouncy Castle's lightweight crypto API
* and uses a {@link BufferedBlockCipher} for ciphering.
*
- The {@code flush()} method just flushes the underlying output stream
* and has no effect on the cipher.
*
- For compensation, the {@link #finish()} method finishes the output
* (probably producing padding bytes) without closing the output.
* This can get used in clients to produce a trailer with additional
* information about the ciphered data (e.g. a MAC).
*
*
* @see CipherReadOnlyChannel
* @author Christian Schlichtherle
*/
@NotThreadSafe
public final class CipherOutputStream extends DecoratingOutputStream {
/** The buffered block cipher used for processing the output. */
private @CheckForNull BufferedBlockCipher cipher;
/**
* The cipher output buffer used for processing the output
* to the decorated stream.
* This buffer is autosized to the largest buffer written to this stream.
*/
private byte[] buffer = new byte[0];
/**
* Creates a new cipher output stream.
*
* @param cipher the block cipher.
* @param out the output stream.
*/
@CreatesObligation
public CipherOutputStream(
final BufferedBlockCipher cipher,
final @WillCloseWhenClosed OutputStream out) {
super(Objects.requireNonNull(out));
this.cipher = Objects.requireNonNull(cipher);
}
/**
* Checks that this cipher output stream is in open state, which requires
* that {@link #cipher} is not {@code null}.
*
* @throws IOException If the preconditions do not hold.
*/
private BufferedBlockCipher cipher() throws IOException {
final BufferedBlockCipher cipher = this.cipher;
if (null == cipher)
throw new IOException("Cipher output stream has been closed!");
return cipher;
}
/**
* Ciphers and writes the given byte to the underlying output stream.
*
* @param b The byte to cipher and write.
* @throws IOException If out or cipher aren't properly initialized,
* the stream has been closed or an I/O error occured.
*/
@Override
public void write(final int b)
throws IOException {
final BufferedBlockCipher cipher = cipher();
int cipherLen = cipher.getUpdateOutputSize(1);
byte[] cipherOut = this.buffer;
if (cipherLen > cipherOut.length)
this.buffer = cipherOut = new byte[cipherLen];
cipherLen = cipher.processByte((byte) b, cipherOut, 0);
if (cipherLen > 0)
out.write(cipherOut, 0, cipherLen);
}
/**
* Ciphers and writes the contents of the given byte array to the
* underlying output stream.
*
* @param buf The buffer holding the data to cipher and write.
* @param off The start offset of the data in the buffer.
* @param len The number of bytes to cipher and write.
* @throws IOException If out or cipher aren't properly initialized,
* the stream has been closed or an I/O error occured.
*/
@Override
public void write(final byte[] buf, final int off, final int len)
throws IOException {
final BufferedBlockCipher cipher = cipher();
int cipherLen = cipher.getUpdateOutputSize(len);
byte[] cipherOut = this.buffer;
if (cipherLen > cipherOut.length)
this.buffer = cipherOut = new byte[cipherLen];
cipherLen = cipher.processBytes(buf, off, len, cipherOut, 0);
out.write(cipherOut, 0, cipherLen);
}
/**
* Finishes and voids this cipher output stream.
* Calling this method causes all remaining buffered bytes to get written
* and padded if necessary.
* Afterwards, this stream will behave as if it had been closed, although
* the decorated stream may still be open.
*
* @throws IOException If {@code out} or {@code cipher} aren't properly
* initialized, an I/O error occurs or the cipher
* text is invalid because some required padding is missing.
*/
public void finish() throws IOException {
final BufferedBlockCipher cipher = this.cipher;
if (null == cipher)
return;
this.cipher = null;
int cipherLen = cipher.getOutputSize(0);
byte[] cipherOut = this.buffer;
if (cipherLen > cipherOut.length)
this.buffer = cipherOut = new byte[cipherLen];
try {
cipherLen = cipher.doFinal(cipherOut, 0);
} catch (InvalidCipherTextException ex) {
throw new IOException(ex);
}
out.write(cipherOut, 0, cipherLen);
}
@Override
public void close() throws IOException {
finish();
out.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy