/* See LICENSE for licensing and NOTICE for copyright. */ package org.cryptacular.bean; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.KeyStore; import org.cryptacular.CiphertextHeader; import org.cryptacular.StreamException; import org.cryptacular.adapter.BlockCipherAdapter; import org.cryptacular.generator.Nonce; import org.cryptacular.util.StreamUtil; /** * Base class for all cipher beans that use block cipher. * * @author Middleware Services */ public abstract class AbstractBlockCipherBean extends AbstractCipherBean { /** Creates a new instance. */ public AbstractBlockCipherBean() {} /** * Creates a new instance by specifying all properties. * * @param keyStore Key store containing encryption key. * @param keyAlias Name of encryption key entry in key store. * @param keyPassword Password used to decrypt key entry in keystore. * @param nonce Nonce/IV generator. */ public AbstractBlockCipherBean( final KeyStore keyStore, final String keyAlias, final String keyPassword, final Nonce nonce) { super(keyStore, keyAlias, keyPassword, nonce); } @Override protected byte[] process(final CiphertextHeader header, final boolean mode, final byte[] input) { final BlockCipherAdapter cipher = newCipher(header, mode); final byte[] headerBytes = header.encode(); int outOff; final int inOff; final int length; final byte[] output; if (mode) { final int outSize = headerBytes.length + cipher.getOutputSize(input.length); output = new byte[outSize]; System.arraycopy(headerBytes, 0, output, 0, headerBytes.length); inOff = 0; outOff = headerBytes.length; length = input.length; } else { length = input.length - headerBytes.length; final int outSize = cipher.getOutputSize(length); output = new byte[outSize]; inOff = headerBytes.length; outOff = 0; } outOff += cipher.processBytes(input, inOff, length, output, outOff); outOff += cipher.doFinal(output, outOff); if (outOff < output.length) { final byte[] copy = new byte[outOff]; System.arraycopy(output, 0, copy, 0, outOff); return copy; } return output; } @Override protected void process( final CiphertextHeader header, final boolean mode, final InputStream input, final OutputStream output) { final BlockCipherAdapter cipher = newCipher(header, mode); final int outSize = cipher.getOutputSize(StreamUtil.CHUNK_SIZE); final byte[] outBuf = new byte[outSize > StreamUtil.CHUNK_SIZE ? outSize : StreamUtil.CHUNK_SIZE]; StreamUtil.pipeAll( input, output, (in, inOff, len, out) -> { final int n = cipher.processBytes(in, inOff, len, outBuf, 0); out.write(outBuf, 0, n); }); final int n = cipher.doFinal(outBuf, 0); try { output.write(outBuf, 0, n); } catch (IOException e) { throw new StreamException(e); } } /** * Creates a new cipher adapter instance suitable for the block cipher used by this class. * * @param header Ciphertext header. * @param mode True for encryption; false for decryption. * * @return Block cipher adapter that wraps an initialized block cipher that is ready for use in the given mode. */ protected abstract BlockCipherAdapter newCipher(CiphertextHeader header, boolean mode); }