* started reducing the temporary buffers created within various crypto methods , as we've

got some pretty heavy GC churn when under load.  rough estimate is we allocate 5-8x as
much data as we need, copying it all over the place before forwarding it (or processing it).
this should cut down a few of those copies, but not enough yet.  it'd be great to get that
down to 2x.
* lots of logging
This commit is contained in:
jrandom
2004-09-28 20:33:23 +00:00
committed by zzz
parent ff1dfd8f25
commit 774231f347
20 changed files with 254 additions and 184 deletions

View File

@ -284,7 +284,7 @@ public class I2PAppContext {
* matter. Though for the crazy people out there, we do expose a way to
* disable it.
*/
public AESEngine AESEngine() {
public AESEngine aes() {
if (!_AESEngineInitialized) initializeAESEngine();
return _AESEngine;
}

View File

@ -37,21 +37,16 @@ public class AESEngine {
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param payloadIndex index into the payload to start encrypting
* @param out where to store the result
* @param outIndex where in out to start writing
* @param sessionKey private esession key to encrypt to
* @param initializationVector IV for CBC
* @return encrypted data
* @param iv IV for CBC
* @param length how much data to encrypt
*/
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ((initializationVector == null) || (payload == null) || (sessionKey == null)
|| (initializationVector.length != 16)) return null;
byte cyphertext[] = null;
if ((payload.length % 16) == 0)
cyphertext = new byte[payload.length];
else
cyphertext = new byte[payload.length + (16 - (payload.length % 16))];
System.arraycopy(payload, 0, cyphertext, 0, payload.length);
return cyphertext;
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
System.arraycopy(payload, payloadIndex, out, outIndex, length);
_log.warn("Warning: AES is disabled");
}
public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
@ -72,13 +67,17 @@ public class AESEngine {
_log.error("Error writing data", dfe);
return null;
}
return encrypt(baos.toByteArray(), sessionKey, iv);
byte orig[] = baos.toByteArray();
byte rv[] = new byte[orig.length];
encrypt(orig, 0, rv, 0, sessionKey, iv, rv.length);
return rv;
}
public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
byte decr[] = decrypt(payload, sessionKey, iv);
byte decr[] = new byte[payload.length];
decrypt(payload, 0, decr, 0, sessionKey, iv, payload.length);
if (decr == null) {
_log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
return null;
@ -110,19 +109,19 @@ public class AESEngine {
}
}
/** decrypt the data with the session key provided
* @param cyphertext encrypted data
* @param sessionKey private session key
* @param initializationVector IV for CBC
* @return unencrypted data
*/
public byte[] decrypt(byte cyphertext[], SessionKey sessionKey, byte initializationVector[]) {
if ((initializationVector == null) || (cyphertext == null) || (sessionKey == null)
|| (initializationVector.length != 16)) return null;
byte payload[] = new byte[cyphertext.length];
/** Decrypt the data with the session key
* @param payload data to be decrypted
* @param payloadIndex index into the payload to start decrypting
* @param out where to store the cleartext
* @param outIndex where in out to start writing
* @param sessionKey private session key to decrypt to
* @param iv IV for CBC
* @param length how much data to decrypt
*/
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
System.arraycopy(payload, payloadIndex, out, outIndex, length);
_log.warn("Warning: AES is disabled");
return cyphertext;
}
public static void main(String args[]) {
@ -133,14 +132,16 @@ public class AESEngine {
byte sbuf[] = new byte[16];
RandomSource.getInstance().nextBytes(sbuf);
byte se[] = ctx.AESEngine().encrypt(sbuf, key, iv);
byte sd[] = ctx.AESEngine().decrypt(se, key, iv);
byte se[] = new byte[16];
ctx.aes().encrypt(sbuf, 0, se, 0, key, iv, sbuf.length);
byte sd[] = new byte[16];
ctx.aes().decrypt(se, 0, sd, 0, key, iv, se.length);
ctx.logManager().getLog(AESEngine.class).debug("Short test: " + DataHelper.eq(sd, sbuf));
byte lbuf[] = new byte[1024];
RandomSource.getInstance().nextBytes(sbuf);
byte le[] = ctx.AESEngine().safeEncrypt(lbuf, key, iv, 2048);
byte ld[] = ctx.AESEngine().safeDecrypt(le, key, iv);
byte le[] = ctx.aes().safeEncrypt(lbuf, key, iv, 2048);
byte ld[] = ctx.aes().safeDecrypt(le, key, iv);
ctx.logManager().getLog(AESEngine.class).debug("Long test: " + DataHelper.eq(ld, lbuf));
}
}

View File

@ -230,22 +230,21 @@ public class AESInputStream extends FilterInputStream {
_log.info(encrypted.length + " bytes makes up " + numBlocks + " blocks to decrypt normally");
}
byte block[] = new byte[BLOCK_SIZE];
for (int i = 0; i < numBlocks; i++) {
System.arraycopy(encrypted, i * BLOCK_SIZE, block, 0, BLOCK_SIZE);
byte decrypted[] = _context.AESEngine().decrypt(block, _key, _lastBlock);
byte data[] = DataHelper.xor(decrypted, _lastBlock);
int cleaned[] = stripPadding(data);
for (int j = 0; j < cleaned.length; j++) {
if (cleaned[j] <= 0) {
cleaned[j] += 256;
//_log.error("(modified: " + cleaned[j] + ")");
}
_readyBuf.add(new Integer(cleaned[j]));
_context.aes().decrypt(encrypted, i * BLOCK_SIZE, encrypted, i * BLOCK_SIZE, _key, _lastBlock, BLOCK_SIZE);
DataHelper.xor(encrypted, i * BLOCK_SIZE, _lastBlock, 0, encrypted, i * BLOCK_SIZE, BLOCK_SIZE);
int payloadBytes = countBlockPayload(encrypted, i * BLOCK_SIZE);
for (int j = 0; j < payloadBytes; j++) {
int c = encrypted[j + i * BLOCK_SIZE];
if (c <= 0)
c += 256;
_readyBuf.add(new Integer(c));
}
_cumulativePrepared += cleaned.length;
//_log.debug("Updating last block for inputStream");
System.arraycopy(decrypted, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativePaddingStripped += BLOCK_SIZE - payloadBytes;
_cumulativePrepared += payloadBytes;
System.arraycopy(encrypted, i * BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
}
int remaining = encrypted.length % BLOCK_SIZE;
@ -263,6 +262,9 @@ public class AESInputStream extends FilterInputStream {
}
/**
* 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.
@ -275,31 +277,29 @@ public class AESInputStream extends FilterInputStream {
*
* We use 16 byte blocks in this AES implementation
*
* @throws IOException if the padding is invalid
*/
private int[] stripPadding(byte data[]) throws IOException {
int numPadBytes = data[data.length - 1];
if ((numPadBytes >= data.length) || (numPadBytes <= 0)) {
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("stripPadding from block " + DataHelper.toHexString(data) + " (" + data.length + "bytes): "
_log.debug("countBlockPayload on block index " + startIndex
+ numPadBytes + " is an invalid # of pad bytes");
throw new IOException("Invalid number of pad bytes (" + numPadBytes
+ ") for " + data.length + " bytes");
+ ") for " + startIndex + " index");
}
int rv[] = new int[data.length - numPadBytes];
// optional, but a really good idea: verify the padding
if (true) {
for (int i = data.length - numPadBytes; i < data.length; i++) {
if (data[i] != (byte) numPadBytes) {
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[i] + " not " + numPadBytes);
+ "] = " + data[startIndex + i] + " not " + numPadBytes);
}
}
}
for (int i = 0; i < rv.length; i++)
rv[i] = data[i];
_cumulativePaddingStripped += numPadBytes;
return rv;
return BLOCK_SIZE - numPadBytes;
}
int remainingBytes() {

View File

@ -106,18 +106,16 @@ public class AESOutputStream extends FilterOutputStream {
int numBlocks = src.length / (BLOCK_SIZE - 1);
byte block[] = new byte[BLOCK_SIZE];
block[BLOCK_SIZE - 1] = 0x01; // the padding byte for "full" blocks
for (int i = 0; i < numBlocks; i++) {
System.arraycopy(src, i * 15, block, 0, 15);
byte data[] = DataHelper.xor(block, _lastBlock);
byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
_cumulativeWritten += encrypted.length;
DataHelper.xor(src, i * 15, _lastBlock, 0, block, 0, 15);
// the padding byte for "full" blocks
block[BLOCK_SIZE - 1] = (byte)(_lastBlock[BLOCK_SIZE - 1] ^ 0x01);
_context.aes().encrypt(block, 0, block, 0, _key, _lastBlock, BLOCK_SIZE);
if (_log.shouldLog(Log.DEBUG))
_log.debug("Padding block " + i + " of " + numBlocks + " with 1 byte. orig= "
+ DataHelper.toHexString(data) + " (size=" + data.length + ") encrypted= "
+ DataHelper.toHexString(encrypted) + " (size=" + encrypted.length + ")");
out.write(encrypted);
System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
_log.debug("Padding block " + i + " of " + numBlocks + " with 1 byte");
out.write(block);
System.arraycopy(block, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativeWritten += BLOCK_SIZE;
_cumulativePadding++;
}
@ -129,12 +127,12 @@ public class AESOutputStream extends FilterOutputStream {
_log.debug("Padding " + src.length + " with " + paddingBytes + " bytes in " + numBlocks + " blocks");
System.arraycopy(src, numBlocks * 15, block, 0, remainingBytes);
Arrays.fill(block, remainingBytes, BLOCK_SIZE, (byte) paddingBytes);
byte data[] = DataHelper.xor(block, _lastBlock);
byte encrypted[] = _context.AESEngine().encrypt(data, _key, _lastBlock);
out.write(encrypted);
System.arraycopy(encrypted, encrypted.length - BLOCK_SIZE, _lastBlock, 0, BLOCK_SIZE);
DataHelper.xor(block, 0, _lastBlock, 0, block, 0, BLOCK_SIZE);
_context.aes().encrypt(block, 0, block, 0, _key, _lastBlock, BLOCK_SIZE);
out.write(block);
System.arraycopy(block, 0, _lastBlock, 0, BLOCK_SIZE);
_cumulativePadding += paddingBytes;
_cumulativeWritten += encrypted.length;
_cumulativeWritten += BLOCK_SIZE;
}
}

View File

@ -12,6 +12,7 @@ package net.i2p.crypto;
import java.security.InvalidKeyException;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
import net.i2p.util.Log;
@ -33,104 +34,63 @@ public class CryptixAESEngine extends AESEngine {
super(context);
_log = context.logManager().getLog(CryptixAESEngine.class);
}
public byte[] encrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ((initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|| (initializationVector.length != 16)) return null;
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
if ( (payload == null) || (out == null) || (sessionKey == null) || (iv == null) || (iv.length != 16) )
throw new NullPointerException("invalid args to aes");
if (payload.length < payloadIndex + length)
throw new IllegalArgumentException("Payload is too short");
if (out.length < outIndex + length)
throw new IllegalArgumentException("Output is too short");
if (length <= 0)
throw new IllegalArgumentException("Length is too small");
if (length % 16 != 0)
throw new IllegalArgumentException("Only lengths mod 16 are supported here");
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
byte rv[] = new byte[payload.length];
for (int i = 0; i < rv.length; i++)
rv[i] = (byte) (payload[i] ^ FAKE_KEY);
return rv;
System.arraycopy(payload, payloadIndex, out, outIndex, length);
return;
}
int numblock = length / 16;
DataHelper.xor(iv, 0, payload, payloadIndex, out, outIndex, 16);
encryptBlock(out, outIndex, sessionKey, out, outIndex);
for (int x = 1; x < numblock; x++) {
DataHelper.xor(out, outIndex + (x-1) * 16, payload, payloadIndex + x * 16, out, outIndex + x * 16, 16);
encryptBlock(out, outIndex + x * 16, sessionKey, out, outIndex + x * 16);
}
}
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|| (iv.length != 16))
throw new IllegalArgumentException("bad setup");
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
System.arraycopy(payload, payloadIndex, out, outIndex, length);
return ;
}
int numblock = payload.length / 16;
if (payload.length % 16 != 0) numblock++;
byte[][] plain = new byte[numblock][16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
plain[x][y] = payload[x * 16 + y];
}
}
byte[][] cipher = new byte[numblock][16];
cipher[0] = encrypt(xor(initializationVector, plain[0]), sessionKey);
decryptBlock(payload, 0, sessionKey, out, 0);
DataHelper.xor(out, 0, iv, 0, out, 0, 16);
for (int x = 1; x < numblock; x++) {
cipher[x] = encrypt(xor(cipher[x - 1], plain[x]), sessionKey);
decryptBlock(payload, x * 16, sessionKey, out, x * 16);
DataHelper.xor(out, x * 16, payload, (x - 1) * 16, out, x * 16, 16);
}
byte[] ret = new byte[numblock * 16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
ret[x * 16 + y] = cipher[x][y];
}
}
return ret;
}
public byte[] decrypt(byte payload[], SessionKey sessionKey, byte initializationVector[]) {
if ((initializationVector == null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|| (initializationVector.length != 16)) return null;
if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR");
byte rv[] = new byte[payload.length];
for (int i = 0; i < rv.length; i++)
rv[i] = (byte) (payload[i] ^ FAKE_KEY);
return rv;
}
int numblock = payload.length / 16;
if (payload.length % 16 != 0) numblock++;
byte[][] cipher = new byte[numblock][16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
cipher[x][y] = payload[x * 16 + y];
}
}
byte[][] plain = new byte[numblock][16];
plain[0] = xor(decrypt(cipher[0], sessionKey), initializationVector);
for (int x = 1; x < numblock; x++) {
plain[x] = xor(decrypt(cipher[x], sessionKey), cipher[x - 1]);
}
byte[] ret = new byte[numblock * 16];
for (int x = 0; x < numblock; x++) {
for (int y = 0; y < 16; y++) {
ret[x * 16 + y] = plain[x][y];
}
}
return ret;
}
final static byte[] xor(byte[] a, byte[] b) {
if ((a == null) || (b == null) || (a.length != b.length)) return null;
byte[] ret = new byte[a.length];
for (int x = 0; x < a.length; x++) {
ret[x] = (byte) (a[x] ^ b[x]);
}
return ret;
}
/** Encrypt the payload with the session key
* @param payload data to be encrypted
* @param sessionKey private esession key to encrypt to
* @return encrypted data
*/
final byte[] encrypt(byte payload[], SessionKey sessionKey) {
final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
byte rv[] = new byte[payload.length];
CryptixRijndael_Algorithm.blockEncrypt(payload, rv, 0, key, 16);
return rv;
CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, key, 16);
} catch (InvalidKeyException ike) {
_log.error("Invalid key", ike);
return null;
}
}
@ -139,15 +99,55 @@ public class CryptixAESEngine extends AESEngine {
* @param sessionKey private session key
* @return unencrypted data
*/
final byte[] decrypt(byte payload[], SessionKey sessionKey) {
final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16);
byte rv[] = new byte[payload.length];
CryptixRijndael_Algorithm.blockDecrypt(payload, rv, 0, key, 16);
return rv;
CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, key, 16);
} catch (InvalidKeyException ike) {
_log.error("Invalid key", ike);
return null;
}
}
public static void main(String args[]) {
I2PAppContext ctx = new I2PAppContext();
try {
testEDBlock(ctx);
testED(ctx);
} catch (Exception e) {
e.printStackTrace();
}
try { Thread.sleep(5*1000); } catch (InterruptedException ie) {}
}
private static void testED(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[128];
byte encrypted[] = new byte[128];
byte decrypted[] = new byte[128];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encrypt(orig, 0, encrypted, 0, key, iv, orig.length);
aes.decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length);
if (!DataHelper.eq(decrypted,orig))
throw new RuntimeException("full D(E(orig)) != orig");
else
System.out.println("full D(E(orig)) == orig");
}
private static void testEDBlock(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16];
byte orig[] = new byte[16];
byte encrypted[] = new byte[16];
byte decrypted[] = new byte[16];
ctx.random().nextBytes(iv);
ctx.random().nextBytes(orig);
CryptixAESEngine aes = new CryptixAESEngine(ctx);
aes.encryptBlock(orig, 0, key, encrypted, 0);
aes.decryptBlock(encrypted, 0, key, decrypted, 0);
if (!DataHelper.eq(decrypted,orig))
throw new RuntimeException("block D(E(orig)) != orig");
else
System.out.println("block D(E(orig)) == orig");
}
}

View File

@ -352,8 +352,10 @@ public class DHSessionKeyBuilder {
byte iv[] = new byte[16];
RandomSource.getInstance().nextBytes(iv);
String origVal = "1234567890123456"; // 16 bytes max using AESEngine
byte enc[] = ctx.AESEngine().encrypt(origVal.getBytes(), key1, iv);
byte dec[] = ctx.AESEngine().decrypt(enc, key2, iv);
byte enc[] = new byte[16];
byte dec[] = new byte[16];
ctx.aes().encrypt(origVal.getBytes(), 0, enc, 0, key1, iv, 16);
ctx.aes().decrypt(enc, 0, dec, 0, key2, iv, 16);
String tranVal = new String(dec);
if (origVal.equals(tranVal))
_log.debug("**Success: D(E(val)) == val");

View File

@ -248,7 +248,8 @@ public class ElGamalAESEngine {
SessionKey foundKey) throws DataFormatException {
//_log.debug("iv for decryption: " + DataHelper.toString(iv, 16));
//_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
byte decrypted[] = _context.AESEngine().decrypt(encrypted, key, iv);
byte decrypted[] = new byte[encrypted.length];
_context.aes().decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length);
//Hash h = _context.sha().calculateHash(decrypted);
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
try {
@ -503,7 +504,8 @@ public class ElGamalAESEngine {
byte aesUnencr[] = aesSrc.toByteArray();
//Hash h = _context.sha().calculateHash(aesUnencr);
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
byte aesEncr[] = _context.AESEngine().encrypt(aesUnencr, key, iv);
byte aesEncr[] = new byte[aesUnencr.length];
_context.aes().encrypt(aesUnencr, 0, aesEncr, 0, key, iv, aesEncr.length);
//_log.debug("Encrypted length: " + aesEncr.length);
return aesEncr;
} catch (IOException ioe) {

View File

@ -467,12 +467,38 @@ public class DataHelper {
public final static byte[] xor(byte lhs[], byte rhs[]) {
if ((lhs == null) || (rhs == null) || (lhs.length != rhs.length)) return null;
byte rv[] = new byte[lhs.length];
byte diff[] = new byte[lhs.length];
for (int i = 0; i < lhs.length; i++)
diff[i] = (byte) (lhs[i] ^ rhs[i]);
xor(lhs, 0, rhs, 0, diff, 0, lhs.length);
return diff;
}
/**
* xor the lhs with the rhs, storing the result in out.
*
* @param lhs one of the source arrays
* @param startLeft starting index in the lhs array to begin the xor
* @param rhs the other source array
* @param startRight starting index in the rhs array to begin the xor
* @param out output array
* @param startOut starting index in the out array to store the result
* @param len how many bytes into the various arrays to xor
*/
public final static void xor(byte lhs[], int startLeft, byte rhs[], int startRight, byte out[], int startOut, int len) {
if ( (lhs == null) || (rhs == null) || (out == null) )
throw new NullPointerException("Invalid params to xor (" + lhs + ", " + rhs + ", " + out + ")");
if (lhs.length < startLeft + len)
throw new IllegalArgumentException("Left hand side is too short");
if (rhs.length < startRight + len)
throw new IllegalArgumentException("Right hand side is too short");
if (out.length < startOut + len)
throw new IllegalArgumentException("Result is too short");
for (int i = 0; i < len; i++)
out[startOut + i] = (byte) (lhs[startLeft + i] ^ rhs[startRight + i]);
}
//
// The following hashcode helpers make it simpler to write consistently hashing
// functions for objects based on their value, not JVM memory address