Drop historic unused AESInputStream and AESOutputStream

This commit is contained in:
str4d
2017-12-02 19:29:36 +00:00
parent 0f757489ff
commit a574fcb68d
3 changed files with 0 additions and 726 deletions

View File

@ -1,470 +0,0 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import net.i2p.I2PAppContext;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.Clock;
import net.i2p.util.Log;
import net.i2p.util.RandomSource;
/**
* This reads an underlying stream as written by AESOutputStream - AES256 encrypted
* in CBC mode with PKCS#5 padding, with the padding on each and every block of
* 16 bytes. This minimizes the overhead when communication is intermittent,
* rather than when streams of large sets of data are sent (in which case, the
* padding would be on a larger size - say, 1k, though in the worst case that
* would have 1023 bytes of padding, while in the worst case here, we only have
* 15 bytes of padding). So we have an expansion factor of 6.25%. c'est la vie
*
*/
public class AESInputStream extends FilterInputStream {
private Log _log;
private I2PAppContext _context;
private SessionKey _key;
private byte[] _lastBlock;
private boolean _eofFound;
private long _cumulativeRead; // how many read from the source stream
private long _cumulativePrepared; // how many bytes decrypted and added to _readyBuf
private long _cumulativePaddingStripped; // how many bytes have been stripped
/** read but not yet decrypted */
private byte _encryptedBuf[];
/** how many bytes have been added to the encryptedBuf since it was decrypted? */
private int _writesSinceDecrypt;
/** decrypted bytes ready for reading (first available == index of 0) */
private int _decryptedBuf[];
/** how many bytes are available for reading without decrypt? */
private int _decryptedSize;
private final static int BLOCK_SIZE = 16;
public AESInputStream(I2PAppContext context, InputStream source, SessionKey key, byte[] iv) {
super(source);
_context = context;
_log = context.logManager().getLog(AESInputStream.class);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
_encryptedBuf = new byte[BLOCK_SIZE];
_writesSinceDecrypt = 0;
_decryptedBuf = new int[BLOCK_SIZE-1];
_decryptedSize = 0;
_cumulativePaddingStripped = 0;
_eofFound = false;
}
@Override
public int read() throws IOException {
while ((!_eofFound) && (_decryptedSize <= 0)) {
refill();
}
if (_decryptedSize > 0) {
int c = _decryptedBuf[0];
System.arraycopy(_decryptedBuf, 1, _decryptedBuf, 0, _decryptedBuf.length-1);
_decryptedSize--;
return c;
} else if (_eofFound) {
return -1;
} else {
throw new IOException("Not EOF, but none available? " + _decryptedSize
+ "/" + _writesSinceDecrypt
+ "/" + _cumulativeRead + "... impossible");
}
}
@Override
public int read(byte dest[]) throws IOException {
return read(dest, 0, dest.length);
}
@Override
public int read(byte dest[], int off, int len) throws IOException {
for (int i = 0; i < len; i++) {
int val = read();
if (val == -1) {
// no more to read... can they expect more?
if (_eofFound && (i == 0)) {
if (_log.shouldLog(Log.DEBUG))
_log.info("EOF? " + _eofFound
+ "\nread=" + i + " decryptedSize=" + _decryptedSize
+ " \nencryptedSize=" + _writesSinceDecrypt
+ " \ntotal=" + _cumulativeRead
+ " \npadding=" + _cumulativePaddingStripped
+ " \nprepared=" + _cumulativePrepared);
return -1;
} else {
if (i != len)
if (_log.shouldLog(Log.DEBUG))
_log.info("non-terminal eof: " + _eofFound + " i=" + i + " len=" + len);
}
return i;
}
dest[off+i] = (byte)val;
}
if (_log.shouldLog(Log.DEBUG))
_log.debug("Read the full buffer of size " + len);
return len;
}
@Override
public long skip(long numBytes) throws IOException {
for (long l = 0; l < numBytes; l++) {
int val = read();
if (val == -1) return l;
}
return numBytes;
}
@Override
public int available() throws IOException {
return _decryptedSize;
}
@Override
public void close() throws IOException {
in.close();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cumulative bytes read from source/decrypted/stripped: " + _cumulativeRead + "/"
+ _cumulativePrepared + "/" + _cumulativePaddingStripped + "] remaining [" + _decryptedSize + " ready, "
+ _writesSinceDecrypt + " still encrypted]");
}
@Override
public void mark(int readLimit) { // nop
}
@Override
public void reset() throws IOException {
throw new IOException("Reset not supported");
}
@Override
public boolean markSupported() {
return false;
}
/**
* Read at least one new byte from the underlying stream, and up to max new bytes,
* but not necessarily enough for a new decrypted block. This blocks until at least
* one new byte is read from the stream
*
*/
private void refill() throws IOException {
if ( (!_eofFound) && (_writesSinceDecrypt < BLOCK_SIZE) ) {
int read = in.read(_encryptedBuf, _writesSinceDecrypt, _encryptedBuf.length - _writesSinceDecrypt);
if (read == -1) {
_eofFound = true;
} else if (read > 0) {
_cumulativeRead += read;
_writesSinceDecrypt += read;
}
}
if (_writesSinceDecrypt == BLOCK_SIZE) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("We have " + _writesSinceDecrypt + " available to decrypt... doing so");
decryptBlock();
if ( (_writesSinceDecrypt > 0) && (_log.shouldLog(Log.DEBUG)) )
_log.debug("Bytes left in the encrypted buffer after decrypt: "
+ _writesSinceDecrypt);
}
}
/**
* Decrypt the
*/
private void decryptBlock() throws IOException {
if (_writesSinceDecrypt != BLOCK_SIZE)
throw new IOException("Error decrypting - no data to decrypt");
if (_decryptedSize != 0)
throw new IOException("decrypted size is not 0? " + _decryptedSize);
_context.aes().decrypt(_encryptedBuf, 0, _encryptedBuf, 0, _key, _lastBlock, BLOCK_SIZE);
DataHelper.xor(_encryptedBuf, 0, _lastBlock, 0, _encryptedBuf, 0, BLOCK_SIZE);
int payloadBytes = countBlockPayload(_encryptedBuf, 0);
for (int i = 0; i < payloadBytes; i++) {
int c = _encryptedBuf[i];
if (c <= 0)
c += 256;
_decryptedBuf[i] = c;
}
_decryptedSize = payloadBytes;
_cumulativePaddingStripped += BLOCK_SIZE - payloadBytes;
_cumulativePrepared += payloadBytes;
System.arraycopy(_encryptedBuf, 0, _lastBlock, 0, BLOCK_SIZE);
_writesSinceDecrypt = 0;
}
/**
* How many non-padded bytes are there in the block starting at the given
* location.
*
* PKCS#5 specifies the padding for the block has the # of padding bytes
* located in the last byte of the block, and each of the padding bytes are
* equal to that value.
* e.g. in a 4 byte block:
* 0x0a padded would become
* 0x0a 0x03 0x03 0x03
* e.g. in a 4 byte block:
* 0x01 0x02 padded would become
* 0x01 0x02 0x02 0x02
*
* We use 16 byte blocks in this AES implementation
*
* @throws IOException if the padding is invalid
*/
private int countBlockPayload(byte data[], int startIndex) throws IOException {
int numPadBytes = data[startIndex + BLOCK_SIZE - 1];
if ((numPadBytes >= BLOCK_SIZE) || (numPadBytes <= 0)) {
if (_log.shouldLog(Log.DEBUG))
_log.debug("countBlockPayload on block index " + startIndex
+ numPadBytes + " is an invalid # of pad bytes");
throw new IOException("Invalid number of pad bytes (" + numPadBytes
+ ") for " + startIndex + " index");
}
// optional, but a really good idea: verify the padding
if (true) {
for (int i = BLOCK_SIZE - numPadBytes; i < BLOCK_SIZE; i++) {
if (data[startIndex + i] != (byte) numPadBytes) {
throw new IOException("Incorrect padding on decryption: data[" + i
+ "] = " + data[startIndex + i] + " not " + numPadBytes);
}
}
}
return BLOCK_SIZE - numPadBytes;
}
int remainingBytes() {
return _writesSinceDecrypt;
}
int readyBytes() {
return _decryptedSize;
}
/**
* Test AESOutputStream/AESInputStream
*/
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
try {
System.out.println("pwd=" + new java.io.File(".").getAbsolutePath());
System.out.println("Beginning");
runTest(ctx);
} catch (Throwable e) {
ctx.logManager().getLog(AESInputStream.class).error("Fail", e);
}
try { Thread.sleep(30*1000); } catch (InterruptedException ie) {}
System.out.println("Done");
}
private static void runTest(I2PAppContext ctx) {
Log log = ctx.logManager().getLog(AESInputStream.class);
log.setMinimumPriority(Log.DEBUG);
byte orig[] = new byte[1024 * 32];
RandomSource.getInstance().nextBytes(orig);
//byte orig[] = DataHelper.getASCII("you are my sunshine, my only sunshine");
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
byte iv[] = DataHelper.getASCII("there once was a");
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 32KB data");
orig = new byte[20];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 20 byte data");
orig = new byte[3];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 3 byte data");
orig = new byte[0];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 0 byte data");
for (int i = 0; i <= 32768; i++) {
orig = new byte[i];
ctx.random().nextBytes(orig);
try {
log.info("Testing " + orig.length);
runTest(ctx, orig, key, iv);
} catch (RuntimeException re) {
log.error("Error testing " + orig.length);
throw re;
}
}
/*
orig = new byte[615280];
RandomSource.getInstance().nextBytes(orig);
for (int i = 0; i < 20; i++) {
runTest(ctx, orig, key, iv);
}
log.info("Done testing 615280 byte data");
*/
/*
for (int i = 0; i < 100; i++) {
orig = new byte[ctx.random().nextInt(1024*1024)];
ctx.random().nextBytes(orig);
try {
runTest(ctx, orig, key, iv);
} catch (RuntimeException re) {
log.error("Error testing " + orig.length);
throw re;
}
}
log.info("Done testing 100 random lengths");
*/
orig = new byte[32];
RandomSource.getInstance().nextBytes(orig);
try {
runOffsetTest(ctx, orig, key, iv);
} catch (Exception e) {
log.info("Error running offset test", e);
}
log.info("Done testing offset test (it should have come back with a statement NOT EQUAL!)");
try {
Thread.sleep(30 * 1000);
} catch (InterruptedException ie) { // nop
}
}
private static void runTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
Log log = ctx.logManager().getLog(AESInputStream.class);
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(sin, buf);
if (read > 0) baos.write(buf, 0, read);
sin.close();
byte fin[] = baos.toByteArray();
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq) {
//log.info("Equal hashes. hash: " + origHash);
} else {
throw new RuntimeException("NOT EQUAL! len=" + orig.length + " read=" + read
+ "\norig: \t" + Base64.encode(orig) + "\nnew : \t"
+ Base64.encode(fin));
}
boolean ok = DataHelper.eq(orig, fin);
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (IOException ioe) {
log.error("ERROR transferring", ioe);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
private static void runOffsetTest(I2PAppContext ctx, byte orig[], SessionKey key, byte[] iv) {
Log log = ctx.logManager().getLog(AESInputStream.class);
try {
long start = Clock.getInstance().now();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
long endE = Clock.getInstance().now();
log.info("Encrypted segment length: " + encrypted.length);
byte encryptedSegment[] = new byte[40];
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(sin, buf);
int remaining = sin.remainingBytes();
int readyBytes = sin.readyBytes();
log.info("Read: " + read);
if (read > 0) baos.write(buf, 0, read);
sin.close();
byte fin[] = baos.toByteArray();
log.info("fin.length: " + fin.length + " remaining: " + remaining + " ready: " + readyBytes);
long end = Clock.getInstance().now();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
boolean eq = origHash.equals(newHash);
if (eq)
log.info("Equal hashes. hash: " + origHash);
else
throw new RuntimeException("NOT EQUAL! len=" + orig.length + "\norig: \t" + Base64.encode(orig) + "\nnew : \t" + Base64.encode(fin));
boolean ok = DataHelper.eq(orig, fin);
log.debug("EQ data? " + ok + " origLen: " + orig.length + " fin.length: " + fin.length);
log.debug("Time to D(E(" + orig.length + ")): " + (end - start) + "ms");
log.debug("Time to E(" + orig.length + "): " + (endE - start) + "ms");
log.debug("Time to D(" + orig.length + "): " + (end - endE) + "ms");
} catch (RuntimeException re) {
throw re;
} catch (IOException ioe) {
log.error("ERROR transferring", ioe);
}
//try { Thread.sleep(5000); } catch (Throwable t) {}
}
}

View File

@ -1,104 +0,0 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import junit.framework.TestCase;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.RandomSource;
/**
* @author Comwiz
*/
public class AESInputStreamTest extends TestCase {
public void testMultiple() throws Exception{
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
byte iv[] = DataHelper.getASCII("there once was a");
int[] sizes = {1024 * 32, 20, 3, 0};
for(int j = 0; j < sizes.length; j++){
byte orig[] = new byte[sizes[j]];
for (int i = 0; i < 20; i++) {
RandomSource.getInstance().nextBytes(orig);
runTest(orig, key, iv);
}
}
}
private static void runTest(byte orig[], SessionKey key, byte[] iv) throws Exception{
I2PAppContext ctx = I2PAppContext.getGlobalContext();
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encrypted);
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(sin, buf);
if (read > 0) baos.write(buf, 0, read);
sin.close();
byte fin[] = baos.toByteArray();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
assertEquals(origHash, newHash);
assertTrue(DataHelper.eq(orig, fin));
}
public static void testOffset() throws Exception{
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte[] orig = new byte[32];
RandomSource.getInstance().nextBytes(orig);
SessionKey key = KeyGenerator.getInstance().generateSessionKey();
byte iv[] = DataHelper.getASCII("there once was a");
ByteArrayOutputStream origStream = new ByteArrayOutputStream(512);
AESOutputStream out = new AESOutputStream(ctx, origStream, key, iv);
out.write(orig);
out.close();
byte encrypted[] = origStream.toByteArray();
byte encryptedSegment[] = new byte[40];
System.arraycopy(encrypted, 0, encryptedSegment, 0, 40);
ByteArrayInputStream encryptedStream = new ByteArrayInputStream(encryptedSegment);
AESInputStream sin = new AESInputStream(ctx, encryptedStream, key, iv);
ByteArrayOutputStream baos = new ByteArrayOutputStream(512);
byte buf[] = new byte[1024 * 32];
int read = DataHelper.read(sin, buf);
int remaining = sin.remainingBytes();
int readyBytes = sin.readyBytes();
if (read > 0)
baos.write(buf, 0, read);
sin.close();
byte fin[] = baos.toByteArray();
Hash origHash = SHA256Generator.getInstance().calculateHash(orig);
Hash newHash = SHA256Generator.getInstance().calculateHash(fin);
assertFalse(origHash.equals(newHash));
assertFalse(DataHelper.eq(orig, fin));
}
}

View File

@ -1,152 +0,0 @@
package net.i2p.crypto;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2003 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
/**
* This writes everything as CBC with PKCS#5 padding, but each block is padded
* so as soon as a block is received it can be decrypted (rather than wait for
* an arbitrary number of blocks to arrive). That means that each block sent
* will contain exactly one padding byte (unless it was flushed with
* numBytes % (BLOCK_SIZE-1) != 0, in which case that last block will be padded
* with up to 15 bytes). So we have an expansion factor of 6.25%. c'est la vie
*
*/
public class AESOutputStream extends FilterOutputStream {
private Log _log;
private I2PAppContext _context;
private SessionKey _key;
private byte[] _lastBlock;
/**
* buffer containing the unwritten bytes. The first unwritten
* byte is _lastCommitted+1, and the last unwritten byte is _nextWrite-1
* (aka the next byte to be written on the array is _nextWrite)
*/
private byte[] _unencryptedBuf;
private byte _writeBlock[];
/** how many bytes have we been given since we flushed it to the stream? */
private int _writesSinceCommit;
private long _cumulativeProvided; // how many bytes provided to this stream
private long _cumulativeWritten; // how many bytes written to the underlying stream
private long _cumulativePadding; // how many bytes of padding written
public final static float EXPANSION_FACTOR = 1.0625f; // 6% overhead w/ the padding
private final static int BLOCK_SIZE = 16;
private final static int MAX_BUF = 256;
public AESOutputStream(I2PAppContext context, OutputStream source, SessionKey key, byte[] iv) {
super(source);
_context = context;
_log = context.logManager().getLog(AESOutputStream.class);
_key = key;
_lastBlock = new byte[BLOCK_SIZE];
System.arraycopy(iv, 0, _lastBlock, 0, BLOCK_SIZE);
_unencryptedBuf = new byte[MAX_BUF];
_writeBlock = new byte[BLOCK_SIZE];
_writesSinceCommit = 0;
}
@Override
public void write(int val) throws IOException {
_cumulativeProvided++;
_unencryptedBuf[_writesSinceCommit++] = (byte)(val & 0xFF);
if (_writesSinceCommit == _unencryptedBuf.length)
doFlush();
}
@Override
public void write(byte src[]) throws IOException {
write(src, 0, src.length);
}
@Override
public void write(byte src[], int off, int len) throws IOException {
// i'm too lazy to unroll this into the partial writes (dealing with
// wrapping around the buffer size)
for (int i = 0; i < len; i++)
write(src[i+off]);
}
@Override
public void close() throws IOException {
flush();
out.close();
if (_log.shouldLog(Log.DEBUG))
_log.debug("Cumulative bytes provided to this stream / written out / padded: "
+ _cumulativeProvided + "/" + _cumulativeWritten + "/" + _cumulativePadding);
}
@Override
public void flush() throws IOException {
doFlush();
out.flush();
}
private void doFlush() throws IOException {
if (_log.shouldLog(Log.INFO))
_log.info("doFlush(): writesSinceCommit=" + _writesSinceCommit);
writeEncrypted();
_writesSinceCommit = 0;
}
/**
* Encrypt an arbitrary size array with AES using CBC and PKCS#5 padding,
* write it to the stream, and set _lastBlock to the last encrypted
* block. This operation works by taking every (BLOCK_SIZE-1) bytes
* from the src, padding it with PKCS#5 (aka adding 0x01), and encrypting
* it. If the last block doesn't contain exactly (BLOCK_SIZE-1) bytes, it
* is padded with PKCS#5 as well (adding # padding bytes repeated that many
* times).
*
*/
private void writeEncrypted() throws IOException {
int numBlocks = _writesSinceCommit / (BLOCK_SIZE - 1);
if (_log.shouldLog(Log.INFO))
_log.info("writeE(): #=" + _writesSinceCommit + " blocks=" + numBlocks);
for (int i = 0; i < numBlocks; i++) {
DataHelper.xor(_unencryptedBuf, i * 15, _lastBlock, 0, _writeBlock, 0, 15);
// the padding byte for "full" blocks
_writeBlock[BLOCK_SIZE - 1] = (byte)(_lastBlock[BLOCK_SIZE - 1] ^ 0x01);
_context.aes().encrypt(_writeBlock, 0, _writeBlock, 0, _key, _lastBlock, BLOCK_SIZE);
out.write(_writeBlock);
System.arraycopy(_writeBlock, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativeWritten += BLOCK_SIZE;
_cumulativePadding++;
}
if (_writesSinceCommit % 15 != 0) {
// we need to do non trivial padding
int remainingBytes = _writesSinceCommit - numBlocks * 15;
int paddingBytes = BLOCK_SIZE - remainingBytes;
if (_log.shouldLog(Log.DEBUG))
_log.debug("Padding " + _writesSinceCommit + " with " + paddingBytes + " bytes in " + (numBlocks+1) + " blocks");
System.arraycopy(_unencryptedBuf, numBlocks * 15, _writeBlock, 0, remainingBytes);
Arrays.fill(_writeBlock, remainingBytes, BLOCK_SIZE, (byte) paddingBytes);
DataHelper.xor(_writeBlock, 0, _lastBlock, 0, _writeBlock, 0, BLOCK_SIZE);
_context.aes().encrypt(_writeBlock, 0, _writeBlock, 0, _key, _lastBlock, BLOCK_SIZE);
out.write(_writeBlock);
System.arraycopy(_writeBlock, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativePadding += paddingBytes;
_cumulativeWritten += BLOCK_SIZE;
}
}
}