forked from I2P_Developers/i2p.i2p
Drop historic unused AESInputStream and AESOutputStream
This commit is contained in:
@ -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) {}
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user