package freenet.crypt;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Random;
import org.bouncycastle.crypto.BlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.engines.AESLightEngine;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
/** Uses bouncycastle's AEAD code. BC provides Cipher*Stream but they don't work with
* authenticating. FIXME This probably needs an internal buffer. Shouldn't be too inefficient
* provided that any short writes are buffered before they reach here though.
* @author toad
*/
public class AEADOutputStream extends FilterOutputStream {
private final AEADBlockCipher cipher;
/** Create an encrypting, authenticating OutputStream. Will write the nonce to the stream.
* @param os The underlying OutputStream.
* @param key The encryption key.
* @param nonce The nonce. This serves the function of an IV. As a nonce, this MUST be unique.
* We will write it to the stream so the other side can pick it up, like an IV. Should
* generally be generated from a SecureRandom. The top bit must be 0, i.e. nonce[0] &= 0x7F.
* @param mainCipher The BlockCipher for encrypting data. E.g. AES; not a block mode. This will
* be used for encrypting a fairly large amount of data so could be any of the 3 BC AES impl's.
* @param hashCipher The BlockCipher for the final hash. E.g. AES, not a block mode. This will
* not be used very much so should be e.g. an AESLightEngine. */
public AEADOutputStream(OutputStream os, byte[] key, byte[] nonce, BlockCipher hashCipher,
BlockCipher mainCipher) throws IOException {
super(os);
os.write(nonce);
cipher = new OCBBlockCipher_v149(hashCipher, mainCipher);
KeyParameter keyParam = new KeyParameter(key);
AEADParameters params = new AEADParameters(keyParam, MAC_SIZE_BITS, nonce);
cipher.init(true, params);
}
@Override
public void write(int b) throws IOException {
write(new byte[] { (byte)b });
}
@Override
public void write(byte[] buf) throws IOException {
write(buf, 0, buf.length);
}
@Override
public void write(byte[] buf, int offset, int length) throws IOException {
byte[] output = new byte[cipher.getUpdateOutputSize(length)];
cipher.processBytes(buf, offset, length, output, 0);
out.write(output);
}
@Override
public void close() throws IOException {
byte[] output = new byte[cipher.getOutputSize(0)];
try {
cipher.doFinal(output, 0);
} catch (InvalidCipherTextException e) {
// Impossible???
throw new RuntimeException("Impossible: "+e);
}
out.write(output);
out.close();
}
static final int MAC_SIZE_BITS = 128;
static final int MAC_SIZE_BYTES = MAC_SIZE_BITS/8;
static final int AES_BLOCK_SIZE = 16;
public static final int AES_OVERHEAD = AES_BLOCK_SIZE + MAC_SIZE_BYTES;
public static AEADOutputStream createAES(OutputStream os, byte[] key, SecureRandom random) throws IOException {
return innerCreateAES(os, key, random);
}
/** For unit tests only */
static AEADOutputStream innerCreateAES(OutputStream os, byte[] key, Random random) throws IOException {
AESEngine mainCipher = new AESEngine();
AESLightEngine hashCipher = new AESLightEngine();
byte[] nonce = new byte[mainCipher.getBlockSize()];
random.nextBytes(nonce);
nonce[0] &= 0x7F;
return new AEADOutputStream(os, key, nonce, hashCipher, mainCipher);
}
@Override
public String toString() {
return "AEADOutputStream:"+out.toString();
}
}