(mmMMmm profiling)

2004-10-30  jrandom
    * Cache the temporary objects used in the AES encryption/decryption
      process so that AES doesn't require any memory allocation to process
      data.
    * Dramatically reduce memory usage within various crypto implementations
      by avoiding unnecessary (though simplifying) buffers.
    * If we specify some tags to be sent in an I2CP message explicitly, use
      only those, not those plus a new set (otherwise we aren't sure on ACK
      which set was delivered)
    * Allow configuration for the partial send timeout (how long before
      resending a message down a different tunnel in a lease).  This can be
      updated with the "router.clientPartialSendTimeout" router config prop.
    * Logging
This commit is contained in:
jrandom
2004-10-30 23:43:59 +00:00
committed by zzz
parent b571f331ec
commit 58fcbad20a
20 changed files with 530 additions and 284 deletions

View File

@ -26,6 +26,7 @@ public class I2PSocketManagerFactory {
public static final String PROP_MANAGER = "i2p.streaming.manager"; public static final String PROP_MANAGER = "i2p.streaming.manager";
public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl"; public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerImpl";
//public static final String DEFAULT_MANAGER = "net.i2p.client.streaming.I2PSocketManagerFull";
/** /**
* Create a socket manager using a brand new destination connected to the * Create a socket manager using a brand new destination connected to the
@ -83,9 +84,11 @@ public class I2PSocketManagerFactory {
if (true) { if (true) {
// for the old streaming lib // for the old streaming lib
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED); opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_GUARANTEED);
//opts.setProperty("tunnels.depthInbound", "0");
} else { } else {
// for new streaming lib: // for new streaming lib:
opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT); opts.setProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT);
//opts.setProperty("tunnels.depthInbound", "0");
} }
opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost); opts.setProperty(I2PClient.PROP_TCP_HOST, i2cpHost);

View File

@ -8,6 +8,7 @@ import java.io.OutputStream;
import java.net.ConnectException; import java.net.ConnectException;
import java.net.NoRouteToHostException; import java.net.NoRouteToHostException;
import java.util.Properties;
import java.util.Random; import java.util.Random;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
@ -26,6 +27,8 @@ public class StreamSinkClient {
private int _sendSize; private int _sendSize;
private int _writeDelay; private int _writeDelay;
private String _peerDestFile; private String _peerDestFile;
private String _i2cpHost;
private int _i2cpPort;
/** /**
@ -35,6 +38,11 @@ public class StreamSinkClient {
* @param serverDestFile file containing the StreamSinkServer's binary Destination * @param serverDestFile file containing the StreamSinkServer's binary Destination
*/ */
public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) { public StreamSinkClient(int sendSize, int writeDelayMs, String serverDestFile) {
this(null, -1, sendSize, writeDelayMs, serverDestFile);
}
public StreamSinkClient(String i2cpHost, int i2cpPort, int sendSize, int writeDelayMs, String serverDestFile) {
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
_sendSize = sendSize; _sendSize = sendSize;
_writeDelay = writeDelayMs; _writeDelay = writeDelayMs;
_peerDestFile = serverDestFile; _peerDestFile = serverDestFile;
@ -46,7 +54,11 @@ public class StreamSinkClient {
* *
*/ */
public void runClient() { public void runClient() {
I2PSocketManager mgr = I2PSocketManagerFactory.createManager(); I2PSocketManager mgr = null;
if (_i2cpHost != null)
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
else
mgr = I2PSocketManagerFactory.createManager();
Destination peer = null; Destination peer = null;
FileInputStream fis = null; FileInputStream fis = null;
try { try {
@ -81,9 +93,9 @@ public class StreamSinkClient {
try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {} try { Thread.sleep(_writeDelay); } catch (InterruptedException ie) {}
} }
} }
sock.close();
long afterSending = System.currentTimeMillis(); long afterSending = System.currentTimeMillis();
System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms"); System.out.println("Sent " + _sendSize + "KB in " + (afterSending-beforeSending) + "ms");
sock.close();
} catch (InterruptedIOException iie) { } catch (InterruptedIOException iie) {
_log.error("Timeout connecting to the peer", iie); _log.error("Timeout connecting to the peer", iie);
return; return;
@ -103,7 +115,7 @@ public class StreamSinkClient {
} }
/** /**
* Fire up the client. <code>Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile</code> <br /> * Fire up the client. <code>Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile</code> <br />
* <ul> * <ul>
* <li><b>sendSizeKB</b>: how many KB to send</li> * <li><b>sendSizeKB</b>: how many KB to send</li>
* <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li> * <li><b>writeDelayMs</b>: how long to wait between each .write (0 for no delay)</li>
@ -111,25 +123,40 @@ public class StreamSinkClient {
* </ul> * </ul>
*/ */
public static void main(String args[]) { public static void main(String args[]) {
if (args.length != 3) { StreamSinkClient client = null;
System.out.println("Usage: StreamSinkClient sendSizeKB writeDelayMs serverDestFile"); int sendSizeKB = -1;
} else { int writeDelayMs = -1;
int sendSizeKB = -1;
int writeDelayMs = -1; switch (args.length) {
try { case 3:
sendSizeKB = Integer.parseInt(args[0]); try {
} catch (NumberFormatException nfe) { sendSizeKB = Integer.parseInt(args[0]);
System.err.println("Send size invalid [" + args[0] + "]"); } catch (NumberFormatException nfe) {
return; System.err.println("Send size invalid [" + args[0] + "]");
} return;
try { }
writeDelayMs = Integer.parseInt(args[1]); try {
} catch (NumberFormatException nfe) { writeDelayMs = Integer.parseInt(args[1]);
System.err.println("Write delay ms invalid [" + args[1] + "]"); } catch (NumberFormatException nfe) {
return; System.err.println("Write delay ms invalid [" + args[1] + "]");
} return;
StreamSinkClient client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]); }
client.runClient(); client = new StreamSinkClient(sendSizeKB, writeDelayMs, args[2]);
break;
case 5:
try {
int port = Integer.parseInt(args[1]);
sendSizeKB = Integer.parseInt(args[2]);
writeDelayMs = Integer.parseInt(args[3]);
client = new StreamSinkClient(args[0], port, sendSizeKB, writeDelayMs, args[4]);
} catch (NumberFormatException nfe) {
System.err.println("arg error");
}
break;
default:
System.out.println("Usage: StreamSinkClient [i2cpHost i2cpPort] sendSizeKB writeDelayMs serverDestFile");
} }
if (client != null)
client.runClient();
} }
} }

View File

@ -85,9 +85,9 @@ public class StreamSinkSend {
} }
} }
fis.close(); fis.close();
sock.close();
long afterSending = System.currentTimeMillis(); long afterSending = System.currentTimeMillis();
System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms"); System.out.println("Sent " + (size / 1024) + "KB in " + (afterSending-beforeSending) + "ms");
sock.close();
} catch (InterruptedIOException iie) { } catch (InterruptedIOException iie) {
_log.error("Timeout connecting to the peer", iie); _log.error("Timeout connecting to the peer", iie);
return; return;

View File

@ -6,6 +6,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.ConnectException; import java.net.ConnectException;
import java.util.Properties;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
import net.i2p.I2PException; import net.i2p.I2PException;
@ -23,6 +24,8 @@ public class StreamSinkServer {
private Log _log; private Log _log;
private String _sinkDir; private String _sinkDir;
private String _destFile; private String _destFile;
private String _i2cpHost;
private int _i2cpPort;
/** /**
* Create but do not start the streaming server. * Create but do not start the streaming server.
@ -31,8 +34,13 @@ public class StreamSinkServer {
* @param ourDestFile filename to write our binary destination to * @param ourDestFile filename to write our binary destination to
*/ */
public StreamSinkServer(String sinkDir, String ourDestFile) { public StreamSinkServer(String sinkDir, String ourDestFile) {
this(sinkDir, ourDestFile, null, -1);
}
public StreamSinkServer(String sinkDir, String ourDestFile, String i2cpHost, int i2cpPort) {
_sinkDir = sinkDir; _sinkDir = sinkDir;
_destFile = ourDestFile; _destFile = ourDestFile;
_i2cpHost = i2cpHost;
_i2cpPort = i2cpPort;
_log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class); _log = I2PAppContext.getGlobalContext().logManager().getLog(StreamSinkServer.class);
} }
@ -42,7 +50,11 @@ public class StreamSinkServer {
* *
*/ */
public void runServer() { public void runServer() {
I2PSocketManager mgr = I2PSocketManagerFactory.createManager(); I2PSocketManager mgr = null;
if (_i2cpHost != null)
mgr = I2PSocketManagerFactory.createManager(_i2cpHost, _i2cpPort, new Properties());
else
mgr = I2PSocketManagerFactory.createManager();
Destination dest = mgr.getSession().getMyDestination(); Destination dest = mgr.getSession().getMyDestination();
System.out.println("Listening for connections on: " + dest.calculateHash().toBase64()); System.out.println("Listening for connections on: " + dest.calculateHash().toBase64());
FileOutputStream fos = null; FileOutputStream fos = null;
@ -95,6 +107,7 @@ public class StreamSinkServer {
sink.mkdirs(); sink.mkdirs();
File cur = File.createTempFile("clientSink", ".dat", sink); File cur = File.createTempFile("clientSink", ".dat", sink);
_fos = new FileOutputStream(cur); _fos = new FileOutputStream(cur);
System.out.println("Writing to " + cur.getAbsolutePath());
} catch (IOException ioe) { } catch (IOException ioe) {
_log.error("Error creating sink", ioe); _log.error("Error creating sink", ioe);
_fos = null; _fos = null;
@ -121,18 +134,30 @@ public class StreamSinkServer {
} }
/** /**
* Fire up the streaming server. <code>Usage: StreamSinkServer sinkDir ourDestFile</code><br /> * Fire up the streaming server. <code>Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile</code><br />
* <ul> * <ul>
* <li><b>sinkDir</b>: Directory to store received files in</li> * <li><b>sinkDir</b>: Directory to store received files in</li>
* <li><b>ourDestFile</b>: filename to write our binary destination to</li> * <li><b>ourDestFile</b>: filename to write our binary destination to</li>
* </ul> * </ul>
*/ */
public static void main(String args[]) { public static void main(String args[]) {
if (args.length != 2) { StreamSinkServer server = null;
System.out.println("Usage: StreamSinkServer sinkDir ourDestFile"); switch (args.length) {
} else { case 2:
StreamSinkServer server = new StreamSinkServer(args[0], args[1]); server = new StreamSinkServer(args[0], args[1]);
server.runServer(); break;
case 4:
try {
int port = Integer.parseInt(args[1]);
server = new StreamSinkServer(args[2], args[3], args[0], port);
} catch (NumberFormatException nfe) {
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
}
break;
default:
System.out.println("Usage: StreamSinkServer [i2cpHost i2cpPort] sinkDir ourDestFile");
} }
if (server != null)
server.runServer();
} }
} }

View File

@ -109,8 +109,8 @@ class I2CPMessageProducer {
// generateNewTags would only generate tags if necessary // generateNewTags would only generate tags if necessary
data.setEncryptedData(encr); data.setEncryptedData(encr);
_log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: " //_log.debug("Encrypting the payload to public key " + dest.getPublicKey().toBase64() + "\nPayload: "
+ data.calculateHash()); // + data.calculateHash());
return data; return data;
} }

View File

@ -114,17 +114,21 @@ class I2PSessionImpl2 extends I2PSessionImpl {
Set sentTags = null; Set sentTags = null;
int oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key); int oldTags = _context.sessionKeyManager().getAvailableTags(dest.getPublicKey(), key);
long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key); long availTimeLeft = _context.sessionKeyManager().getAvailableTimeLeft(dest.getPublicKey(), key);
if (oldTags < 10) {
sentTags = createNewTags(50); if ( (tagsSent == null) || (tagsSent.size() <= 0) ) {
//_log.error("** sendBestEffort only had " + oldTags + " adding 50"); if (oldTags < 10) {
} else if (availTimeLeft < 30 * 1000) { sentTags = createNewTags(50);
// if we have > 10 tags, but they expire in under 30 seconds, we want more //_log.error("** sendBestEffort only had " + oldTags + " adding 50");
sentTags = createNewTags(50); } else if (availTimeLeft < 30 * 1000) {
if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones"); // if we have > 10 tags, but they expire in under 30 seconds, we want more
//_log.error("** sendBestEffort available time left " + availTimeLeft); sentTags = createNewTags(50);
} else { if (_log.shouldLog(Log.DEBUG)) _log.debug(getPrefix() + "Tags are almost expired, adding 50 new ones");
//_log.error("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft); //_log.error("** sendBestEffort available time left " + availTimeLeft);
} else {
//_log.error("sendBestEffort old tags: " + oldTags + " available time left: " + availTimeLeft);
}
} }
SessionKey newKey = null; SessionKey newKey = null;
if (false) // rekey if (false) // rekey
newKey = _context.keyGenerator().generateSessionKey(); newKey = _context.keyGenerator().generateSessionKey();

View File

@ -13,13 +13,13 @@ import net.i2p.I2PAppContext;
import net.i2p.data.i2cp.I2CPMessage; import net.i2p.data.i2cp.I2CPMessage;
import net.i2p.data.i2cp.MessageStatusMessage; import net.i2p.data.i2cp.MessageStatusMessage;
import net.i2p.data.i2cp.ReceiveMessageBeginMessage; import net.i2p.data.i2cp.ReceiveMessageBeginMessage;
import net.i2p.util.Log;
/** /**
* Handle I2CP MessageStatusMessages from the router. This currently only takes * Handle I2CP MessageStatusMessages from the router. This currently only takes
* into account status of available, automatically prefetching them as soon as * into account status of available, automatically prefetching them as soon as
* possible * possible
* *
* @author jrandom
*/ */
class MessageStatusMessageHandler extends HandlerImpl { class MessageStatusMessageHandler extends HandlerImpl {
public MessageStatusMessageHandler(I2PAppContext context) { public MessageStatusMessageHandler(I2PAppContext context) {
@ -28,41 +28,44 @@ class MessageStatusMessageHandler extends HandlerImpl {
public void handleMessage(I2CPMessage message, I2PSessionImpl session) { public void handleMessage(I2CPMessage message, I2PSessionImpl session) {
boolean skipStatus = true; boolean skipStatus = true;
if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions() if (I2PClient.PROP_RELIABILITY_GUARANTEED.equals(session.getOptions().getProperty(I2PClient.PROP_RELIABILITY,
.getProperty(I2PClient.PROP_RELIABILITY, I2PClient.PROP_RELIABILITY_BEST_EFFORT)))
I2PClient.PROP_RELIABILITY_BEST_EFFORT)))
skipStatus = false; skipStatus = false;
_log.debug("Handle message " + message); if (_log.shouldLog(Log.DEBUG))
_log.debug("Handle message " + message);
MessageStatusMessage msg = (MessageStatusMessage) message; MessageStatusMessage msg = (MessageStatusMessage) message;
switch (msg.getStatus()) { switch (msg.getStatus()) {
case MessageStatusMessage.STATUS_AVAILABLE: case MessageStatusMessage.STATUS_AVAILABLE:
ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage(); ReceiveMessageBeginMessage m = new ReceiveMessageBeginMessage();
m.setMessageId(msg.getMessageId()); m.setMessageId(msg.getMessageId());
m.setSessionId(msg.getSessionId()); m.setSessionId(msg.getSessionId());
try { try {
session.sendMessage(m); session.sendMessage(m);
} catch (I2PSessionException ise) { } catch (I2PSessionException ise) {
_log.error("Error asking for the message", ise); _log.error("Error asking for the message", ise);
} }
return; return;
case MessageStatusMessage.STATUS_SEND_ACCEPTED: case MessageStatusMessage.STATUS_SEND_ACCEPTED:
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
// noop // noop
return; return;
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS: case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_SUCCESS:
case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS: case MessageStatusMessage.STATUS_SEND_GUARANTEED_SUCCESS:
_log.info("Message delivery succeeded for message " + msg.getMessageId()); if (_log.shouldLog(Log.INFO))
//if (!skipStatus) _log.info("Message delivery succeeded for message " + msg.getMessageId());
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); //if (!skipStatus)
return; session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE: return;
case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE: case MessageStatusMessage.STATUS_SEND_BEST_EFFORT_FAILURE:
_log.info("Message delivery FAILED for message " + msg.getMessageId()); case MessageStatusMessage.STATUS_SEND_GUARANTEED_FAILURE:
//if (!skipStatus) if (_log.shouldLog(Log.INFO))
session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus()); _log.info("Message delivery FAILED for message " + msg.getMessageId());
return; //if (!skipStatus)
default: session.receiveStatus(msg.getMessageId().getMessageId(), msg.getNonce(), msg.getStatus());
_log.error("Invalid message delivery status received: " + msg.getStatus()); return;
default:
if (_log.shouldLog(Log.ERROR))
_log.error("Invalid message delivery status received: " + msg.getStatus());
} }
} }
} }

View File

@ -9,8 +9,6 @@ package net.i2p.crypto;
* *
*/ */
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
@ -52,25 +50,26 @@ public class AESEngine {
public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) { public byte[] safeEncrypt(byte payload[], SessionKey sessionKey, byte iv[], int paddedSize) {
if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null; if ((iv == null) || (payload == null) || (sessionKey == null) || (iv.length != 16)) return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream(paddedSize + 64); int size = Hash.HASH_LENGTH
+ 4 // sizeof(payload)
+ payload.length;
int padding = ElGamalAESEngine.getPaddingSize(size, paddedSize);
byte data[] = new byte[size + padding];
Hash h = _context.sha().calculateHash(iv); Hash h = _context.sha().calculateHash(iv);
try {
h.writeBytes(baos); int cur = 0;
DataHelper.writeLong(baos, 4, payload.length); System.arraycopy(h.getData(), 0, data, cur, Hash.HASH_LENGTH);
baos.write(payload); cur += Hash.HASH_LENGTH;
byte tv[] = baos.toByteArray(); DataHelper.toLong(data, cur, 4, payload.length);
baos.write(ElGamalAESEngine.getPadding(_context, tv.length, paddedSize)); cur += 4;
} catch (IOException ioe) { System.arraycopy(payload, 0, data, cur, payload.length);
_log.error("Error writing data", ioe); cur += payload.length;
return null; byte paddingData[] = ElGamalAESEngine.getPadding(_context, size, paddedSize);
} catch (DataFormatException dfe) { System.arraycopy(paddingData, 0, data, cur, paddingData.length);
_log.error("Error writing data", dfe);
return null; encrypt(data, 0, data, 0, sessionKey, iv, data.length);
} return data;
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[]) { public byte[] safeDecrypt(byte payload[], SessionKey sessionKey, byte iv[]) {
@ -82,31 +81,29 @@ public class AESEngine {
_log.error("Error decrypting the data - payload " + payload.length + " decrypted to null"); _log.error("Error decrypting the data - payload " + payload.length + " decrypted to null");
return null; return null;
} }
ByteArrayInputStream bais = new ByteArrayInputStream(decr);
Hash h = _context.sha().calculateHash(iv); int cur = 0;
try { byte h[] = _context.sha().calculateHash(iv).getData();
Hash rh = new Hash(); for (int i = 0; i < Hash.HASH_LENGTH; i++) {
rh.readBytes(bais); if (decr[i] != h[i]) {
if (!h.equals(rh)) {
_log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length) _log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length)
+ "]", new Exception("Hash error")); + "]", new Exception("Hash error"));
return null; return null;
} }
long len = DataHelper.readLong(bais, 4); }
byte data[] = new byte[(int) len]; cur += Hash.HASH_LENGTH;
int read = bais.read(data);
if (read != len) { long len = DataHelper.fromLong(decr, cur, 4);
_log.error("Not enough to read"); cur += 4;
return null;
} if (cur + len > decr.length) {
return data; _log.error("Not enough to read");
} catch (IOException ioe) {
_log.error("Error writing data", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing data", dfe);
return null; return null;
} }
byte data[] = new byte[(int)len];
System.arraycopy(decr, cur, data, 0, (int)len);
return data;
} }

View File

@ -9,7 +9,6 @@ package net.i2p.crypto;
* *
*/ */
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream; import java.io.FilterOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;

View File

@ -29,10 +29,12 @@ public class CryptixAESEngine extends AESEngine {
private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm(); private final static CryptixRijndael_Algorithm _algo = new CryptixRijndael_Algorithm();
private final static boolean USE_FAKE_CRYPTO = false; private final static boolean USE_FAKE_CRYPTO = false;
private final static byte FAKE_KEY = 0x2A; private final static byte FAKE_KEY = 0x2A;
private CryptixAESKeyCache _cache;
public CryptixAESEngine(I2PAppContext context) { public CryptixAESEngine(I2PAppContext context) {
super(context); super(context);
_log = context.logManager().getLog(CryptixAESEngine.class); _log = context.logManager().getLog(CryptixAESEngine.class);
_cache = new CryptixAESKeyCache();
} }
public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) { public void encrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) {
@ -65,8 +67,13 @@ public class CryptixAESEngine extends AESEngine {
public void decrypt(byte payload[], int payloadIndex, byte out[], int outIndex, SessionKey sessionKey, byte iv[], int length) { 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) if ((iv== null) || (payload == null) || (payload.length <= 0) || (sessionKey == null)
|| (iv.length != 16)) || (iv.length != 16) )
throw new IllegalArgumentException("bad setup"); throw new IllegalArgumentException("bad setup");
else if (out == null)
throw new IllegalArgumentException("out is null");
else if (out.length - outIndex < length)
throw new IllegalArgumentException("out is too small (out.length=" + out.length
+ " outIndex=" + outIndex + " length=" + length);
if (USE_FAKE_CRYPTO) { if (USE_FAKE_CRYPTO) {
_log.warn("AES Crypto disabled! Using trivial XOR"); _log.warn("AES Crypto disabled! Using trivial XOR");
@ -74,23 +81,26 @@ public class CryptixAESEngine extends AESEngine {
return ; return ;
} }
int numblock = payload.length / 16; int numblock = length / 16;
if (payload.length % 16 != 0) numblock++; if (length % 16 != 0) numblock++;
decryptBlock(payload, 0, sessionKey, out, 0); decryptBlock(payload, payloadIndex, sessionKey, out, outIndex);
DataHelper.xor(out, 0, iv, 0, out, 0, 16); DataHelper.xor(out, outIndex, iv, 0, out, outIndex, 16);
for (int x = 1; x < numblock; x++) { for (int x = 1; x < numblock; x++) {
decryptBlock(payload, x * 16, sessionKey, out, x * 16); decryptBlock(payload, payloadIndex + (x * 16), sessionKey, out, outIndex + (x * 16));
DataHelper.xor(out, x * 16, payload, (x - 1) * 16, out, x * 16, 16); DataHelper.xor(out, outIndex + x * 16, payload, payloadIndex + (x - 1) * 16, out, outIndex + x * 16, 16);
} }
} }
final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) { final void encryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte out[], int outIndex) {
CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey();
try { try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16); Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData);
CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, key, 16); CryptixRijndael_Algorithm.blockEncrypt(payload, out, inIndex, outIndex, key, 16);
} catch (InvalidKeyException ike) { } catch (InvalidKeyException ike) {
_log.error("Invalid key", ike); _log.error("Invalid key", ike);
} finally {
_cache.releaseKey(keyData);
} }
} }
@ -100,11 +110,20 @@ public class CryptixAESEngine extends AESEngine {
* @return unencrypted data * @return unencrypted data
*/ */
final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) { final void decryptBlock(byte payload[], int inIndex, SessionKey sessionKey, byte rv[], int outIndex) {
if ( (payload == null) || (rv == null) )
throw new IllegalArgumentException("null block args [payload=" + payload + " rv="+rv);
if (payload.length - inIndex > rv.length - outIndex)
throw new IllegalArgumentException("bad block args [payload.len=" + payload.length
+ " inIndex=" + inIndex + " rv.len=" + rv.length
+ " outIndex="+outIndex);
CryptixAESKeyCache.KeyCacheEntry keyData = _cache.acquireKey();
try { try {
Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16); Object key = CryptixRijndael_Algorithm.makeKey(sessionKey.getData(), 16, keyData);
CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, key, 16); CryptixRijndael_Algorithm.blockDecrypt(payload, rv, inIndex, outIndex, key, 16);
} catch (InvalidKeyException ike) { } catch (InvalidKeyException ike) {
_log.error("Invalid key", ike); _log.error("Invalid key", ike);
} finally {
_cache.releaseKey(keyData);
} }
} }
@ -113,6 +132,8 @@ public class CryptixAESEngine extends AESEngine {
try { try {
testEDBlock(ctx); testEDBlock(ctx);
testED(ctx); testED(ctx);
testFake(ctx);
testNull(ctx);
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -134,6 +155,42 @@ public class CryptixAESEngine extends AESEngine {
else else
System.out.println("full D(E(orig)) == orig"); System.out.println("full D(E(orig)) == orig");
} }
private static void testFake(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionKey wrongKey = 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, wrongKey, iv, encrypted.length);
if (DataHelper.eq(decrypted,orig))
throw new RuntimeException("full D(E(orig)) == orig when we used the wrong key!");
else
System.out.println("full D(E(orig)) != orig when we used the wrong key");
}
private static void testNull(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
SessionKey wrongKey = 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);
try {
aes.decrypt(null, 0, null, 0, wrongKey, iv, encrypted.length);
} catch (IllegalArgumentException iae) {
return;
}
throw new RuntimeException("full D(E(orig)) didn't fail when we used null!");
}
private static void testEDBlock(I2PAppContext ctx) { private static void testEDBlock(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey(); SessionKey key = ctx.keyGenerator().generateSessionKey();
byte iv[] = new byte[16]; byte iv[] = new byte[16];

View File

@ -0,0 +1,70 @@
package net.i2p.crypto;
import java.util.ArrayList;
import java.util.List;
/**
* Cache the objects used in CryptixRijndael_Algorithm.makeKey to reduce
* memory churn. The KeyCacheEntry should be held onto as long as the
* data referenced in it is needed (which often is only one or two lines
* of code)
*
*/
final class CryptixAESKeyCache {
private List _availableKeys;
private static final int KEYSIZE = 32; // 256bit AES
private static final int BLOCKSIZE = 16;
private static final int ROUNDS = CryptixRijndael_Algorithm.getRounds(KEYSIZE, BLOCKSIZE);
private static final int BC = BLOCKSIZE / 4;
private static final int KC = KEYSIZE / 4;
public CryptixAESKeyCache() {
_availableKeys = new ArrayList(64);
for (int i = 0; i < 64; i++) {
_availableKeys.add(createNew());
}
}
/**
* Get the next available structure, either from the cache or a brand new one
*
*/
public final KeyCacheEntry acquireKey() {
synchronized (_availableKeys) {
if (_availableKeys.size() > 0)
return (KeyCacheEntry)_availableKeys.remove(0);
}
return createNew();
}
/**
* Put this structure back onto the available cache for reuse
*
*/
public final void releaseKey(KeyCacheEntry key) {
synchronized (_availableKeys) {
_availableKeys.add(key);
}
}
private static final KeyCacheEntry createNew() {
KeyCacheEntry e = new KeyCacheEntry();
e.Ke = new int[ROUNDS + 1][BC]; // encryption round keys
e.Kd = new int[ROUNDS + 1][BC]; // decryption round keys
e.tk = new int[KC];
e.key = new Object[] { e.Ke, e.Kd };
return e;
}
/**
* all the data alloc'ed in a makeKey call
*/
public static final class KeyCacheEntry {
int[][] Ke;
int[][] Kd;
int[] tk;
Object[] key;
}
}

View File

@ -453,6 +453,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
* @param sessionKey The session key to use for decryption. * @param sessionKey The session key to use for decryption.
*/ */
public static final void blockDecrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) { public static final void blockDecrypt(byte[] in, byte[] result, int inOffset, int outOffset, Object sessionKey) {
if (in.length - inOffset > result.length - outOffset)
throw new IllegalArgumentException("result too small: in.len=" + in.length + " in.offset=" + inOffset
+ " result.len=" + result.length + " result.offset=" + outOffset);
if (in.length - inOffset <= 15)
throw new IllegalArgumentException("data too small: " + in.length + " inOffset: " + inOffset);
if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")"); if (_RDEBUG) trace(_IN, "blockDecrypt(" + in + ", " + inOffset + ", " + sessionKey + ")");
int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys int[][] Kd = (int[][]) ((Object[]) sessionKey)[1]; // extract decryption round keys
int ROUNDS = Kd.length - 1; int ROUNDS = Kd.length - 1;
@ -534,18 +539,31 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
* @exception InvalidKeyException If the key is invalid. * @exception InvalidKeyException If the key is invalid.
*/ */
public static final/* synchronized */Object makeKey(byte[] k, int blockSize) throws InvalidKeyException { public static final/* synchronized */Object makeKey(byte[] k, int blockSize) throws InvalidKeyException {
return makeKey(k, blockSize, null);
}
public static final/* synchronized */Object makeKey(byte[] k, int blockSize, CryptixAESKeyCache.KeyCacheEntry keyData) throws InvalidKeyException {
if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")"); if (_RDEBUG) trace(_IN, "makeKey(" + k + ", " + blockSize + ")");
if (k == null) throw new InvalidKeyException("Empty key"); if (k == null) throw new InvalidKeyException("Empty key");
if (!(k.length == 16 || k.length == 24 || k.length == 32)) if (!(k.length == 16 || k.length == 24 || k.length == 32))
throw new InvalidKeyException("Incorrect key length"); throw new InvalidKeyException("Incorrect key length");
int ROUNDS = getRounds(k.length, blockSize); int ROUNDS = getRounds(k.length, blockSize);
int BC = blockSize / 4; int BC = blockSize / 4;
int[][] Ke = new int[ROUNDS + 1][BC]; // encryption round keys int[][] Ke = null; // new int[ROUNDS + 1][BC]; // encryption round keys
int[][] Kd = new int[ROUNDS + 1][BC]; // decryption round keys int[][] Kd = null; // new int[ROUNDS + 1][BC]; // decryption round keys
int ROUND_KEY_COUNT = (ROUNDS + 1) * BC; int ROUND_KEY_COUNT = (ROUNDS + 1) * BC;
int KC = k.length / 4; int KC = k.length / 4;
int[] tk = new int[KC]; int[] tk = null; // new int[KC];
int i, j; int i, j;
if (keyData == null) {
Ke = new int[ROUNDS + 1][BC];
Kd = new int[ROUNDS + 1][BC];
tk = new int[KC];
} else {
Ke = keyData.Ke;
Kd = keyData.Kd;
tk = keyData.tk;
}
// copy user material bytes into temporary ints // copy user material bytes into temporary ints
for (i = 0, j = 0; i < KC;) for (i = 0, j = 0; i < KC;)
@ -604,7 +622,11 @@ public final class CryptixRijndael_Algorithm // implicit no-argument constructor
} }
// assemble the encryption (Ke) and decryption (Kd) round keys into // assemble the encryption (Ke) and decryption (Kd) round keys into
// one sessionKey object // one sessionKey object
Object[] sessionKey = new Object[] { Ke, Kd}; Object[] sessionKey = null;
if (keyData == null)
sessionKey = new Object[] { Ke, Kd};
else
sessionKey = keyData.key;
if (_RDEBUG) trace(_OUT, "makeKey()"); if (_RDEBUG) trace(_OUT, "makeKey()");
return sessionKey; return sessionKey;
} }

View File

@ -10,7 +10,6 @@ package net.i2p.crypto;
*/ */
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet; import java.util.HashSet;
@ -147,25 +146,19 @@ public class ElGamalAESEngine {
System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length); System.arraycopy(data, 0, elgEncr, 514 - data.length, data.length);
} }
byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey); byte elgDecr[] = _context.elGamalEngine().decrypt(elgEncr, targetPrivateKey);
if (elgDecr == null) return null; if (elgDecr == null) {
if (_log.shouldLog(Log.WARN))
ByteArrayInputStream bais = new ByteArrayInputStream(elgDecr); _log.warn("decrypt returned null", new Exception("decrypt failed"));
byte preIV[] = null;
try {
usedKey.readBytes(bais);
preIV = new byte[32];
int read = bais.read(preIV);
if (read != preIV.length) {
// hmm, this can't really happen...
throw new DataFormatException("Somehow ElGamal broke and 256 bytes is less than 32 bytes..."); }
} catch (IOException ioe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error decrypting the new session", ioe);
return null; return null;
} }
// ignore the next 192 bytes
byte aesEncr[] = new byte[data.length - 514]; byte preIV[] = null;
System.arraycopy(data, 514, aesEncr, 0, aesEncr.length);
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
System.arraycopy(elgDecr, 0, key, 0, SessionKey.KEYSIZE_BYTES);
usedKey.setData(key);
preIV = new byte[32];
System.arraycopy(elgDecr, SessionKey.KEYSIZE_BYTES, preIV, 0, 32);
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32)); //_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32)); //_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
@ -173,7 +166,7 @@ public class ElGamalAESEngine {
byte iv[] = new byte[16]; byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16); System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesDecr[] = decryptAESBlock(aesEncr, usedKey, iv, null, foundTags, foundKey); byte aesDecr[] = decryptAESBlock(data, 514, data.length-514, usedKey, iv, null, foundTags, foundKey);
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
_log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(), _log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(),
@ -203,8 +196,6 @@ public class ElGamalAESEngine {
SessionKey usedKey, SessionKey foundKey) throws DataFormatException { SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
byte preIV[] = new byte[32]; byte preIV[] = new byte[32];
System.arraycopy(data, 0, preIV, 0, preIV.length); System.arraycopy(data, 0, preIV, 0, preIV.length);
byte encr[] = new byte[data.length - 32];
System.arraycopy(data, 32, encr, 0, encr.length);
Hash ivHash = _context.sha().calculateHash(preIV); Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16]; byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16); System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
@ -213,7 +204,7 @@ public class ElGamalAESEngine {
//_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32)); //_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32)); //_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
byte decrypted[] = decryptAESBlock(encr, key, iv, preIV, foundTags, foundKey); byte decrypted[] = decryptAESBlock(data, 32, data.length-32, key, iv, preIV, foundTags, foundKey);
if (decrypted == null) { if (decrypted == null) {
// it begins with a valid session tag, but thats just a coincidence. // it begins with a valid session tag, but thats just a coincidence.
if (_log.shouldLog(Log.DEBUG)) if (_log.shouldLog(Log.DEBUG))
@ -244,12 +235,16 @@ public class ElGamalAESEngine {
* @param foundTags set which is filled with any sessionTags found during decryption * @param foundTags set which is filled with any sessionTags found during decryption
* @param foundKey session key which may be filled with a new sessionKey found during decryption * @param foundKey session key which may be filled with a new sessionKey found during decryption
*/ */
byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[], byte sentTag[], Set foundTags, byte[] decryptAESBlock(byte encrypted[], SessionKey key, byte iv[],
SessionKey foundKey) throws DataFormatException { byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
return decryptAESBlock(encrypted, 0, encrypted.length, key, iv, sentTag, foundTags, foundKey);
}
byte[] decryptAESBlock(byte encrypted[], int offset, int encryptedLen, SessionKey key, byte iv[],
byte sentTag[], Set foundTags, SessionKey foundKey) throws DataFormatException {
//_log.debug("iv for decryption: " + DataHelper.toString(iv, 16)); //_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)); //_log.debug("decrypting AES block. encr.length = " + (encrypted == null? -1 : encrypted.length) + " sentTag: " + DataHelper.toString(sentTag, 32));
byte decrypted[] = new byte[encrypted.length]; byte decrypted[] = new byte[encryptedLen];
_context.aes().decrypt(encrypted, 0, decrypted, 0, key, iv, encrypted.length); _context.aes().decrypt(encrypted, offset, decrypted, 0, key, iv, encryptedLen);
//Hash h = _context.sha().calculateHash(decrypted); //Hash h = _context.sha().calculateHash(decrypted);
//_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32)); //_log.debug("Hash of entire aes block after decryption: \n" + DataHelper.toString(h.getData(), 32));
try { try {
@ -271,7 +266,7 @@ public class ElGamalAESEngine {
} }
long len = DataHelper.readLong(bais, 4); long len = DataHelper.readLong(bais, 4);
//_log.debug("len: " + len); //_log.debug("len: " + len);
if ((len < 0) || (len > encrypted.length)) throw new Exception("Invalid size of payload"); if ((len < 0) || (len > encryptedLen)) throw new Exception("Invalid size of payload");
byte hashval[] = new byte[32]; byte hashval[] = new byte[32];
int read = bais.read(hashval); int read = bais.read(hashval);
if (read != hashval.length) throw new Exception("Invalid size of hash"); if (read != hashval.length) throw new Exception("Invalid size of hash");
@ -371,54 +366,45 @@ public class ElGamalAESEngine {
byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery, byte[] encryptNewSession(byte data[], PublicKey target, SessionKey key, Set tagsForDelivery,
SessionKey newKey, long paddedSize) { SessionKey newKey, long paddedSize) {
//_log.debug("Encrypting to a NEW session"); //_log.debug("Encrypting to a NEW session");
try { byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158];
ByteArrayOutputStream elgSrc = new ByteArrayOutputStream(64); System.arraycopy(key.getData(), 0, elgSrcData, 0, SessionKey.KEYSIZE_BYTES);
key.writeBytes(elgSrc); byte preIV[] = new byte[32];
byte preIV[] = new byte[32]; _context.random().nextBytes(preIV);
_context.random().nextBytes(preIV); System.arraycopy(preIV, 0, elgSrcData, SessionKey.KEYSIZE_BYTES, 32);
elgSrc.write(preIV); byte rnd[] = new byte[158];
byte rnd[] = new byte[158]; _context.random().nextBytes(rnd);
_context.random().nextBytes(rnd); System.arraycopy(rnd, 0, elgSrcData, SessionKey.KEYSIZE_BYTES+32, 158);
elgSrc.write(rnd);
elgSrc.flush();
//_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32)); //_log.debug("Pre IV for encryptNewSession: " + DataHelper.toString(preIV, 32));
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32)); //_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
long before = _context.clock().now(); long before = _context.clock().now();
byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrc.toByteArray(), target); byte elgEncr[] = _context.elGamalEngine().encrypt(elgSrcData, target);
long after = _context.clock().now(); long after = _context.clock().now();
if (_log.shouldLog(Log.INFO)) if (_log.shouldLog(Log.INFO))
_log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms"); _log.info("elgEngine.encrypt of the session key took " + (after - before) + "ms");
if (elgEncr.length < 514) { if (elgEncr.length < 514) {
byte elg[] = new byte[514]; byte elg[] = new byte[514];
int diff = elg.length - elgEncr.length; int diff = elg.length - elgEncr.length;
if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff); if (_log.shouldLog(Log.DEBUG)) _log.debug("Difference in size: " + diff);
System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length); System.arraycopy(elgEncr, 0, elg, diff, elgEncr.length);
elgEncr = elg; elgEncr = elg;
}
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
//_log.debug("AES encrypted length: " + aesEncr.length);
byte rv[] = new byte[elgEncr.length + aesEncr.length];
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
//_log.debug("Return length: " + rv.length);
long finish = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms");
return rv;
} catch (IOException ioe) {
_log.error("Error encrypting the new session", ioe);
return null;
} catch (DataFormatException dfe) {
_log.error("Error writing out the bytes for the new session", dfe);
return null;
} }
//_log.debug("ElGamal encrypted length: " + elgEncr.length + " elGamal source length: " + elgSrc.toByteArray().length);
Hash ivHash = _context.sha().calculateHash(preIV);
byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
//_log.debug("AES encrypted length: " + aesEncr.length);
byte rv[] = new byte[elgEncr.length + aesEncr.length];
System.arraycopy(elgEncr, 0, rv, 0, elgEncr.length);
System.arraycopy(aesEncr, 0, rv, elgEncr.length, aesEncr.length);
//_log.debug("Return length: " + rv.length);
long finish = _context.clock().now();
if (_log.shouldLog(Log.DEBUG))
_log.debug("after the elgEngine.encrypt took a total of " + (finish - after) + "ms");
return rv;
} }
/** /**
@ -445,11 +431,10 @@ public class ElGamalAESEngine {
byte iv[] = new byte[16]; byte iv[] = new byte[16];
System.arraycopy(ivHash.getData(), 0, iv, 0, 16); System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize); byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, SessionTag.BYTE_LENGTH);
byte rv[] = new byte[rawTag.length + aesEncr.length]; // that prepended SessionTag.BYTE_LENGTH bytes at the beginning of the buffer
System.arraycopy(rawTag, 0, rv, 0, rawTag.length); System.arraycopy(rawTag, 0, aesEncr, 0, rawTag.length);
System.arraycopy(aesEncr, 0, rv, rawTag.length, aesEncr.length); return aesEncr;
return rv;
} }
private final static Set EMPTY_SET = new HashSet(); private final static Set EMPTY_SET = new HashSet();
@ -469,52 +454,64 @@ public class ElGamalAESEngine {
*/ */
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey, final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize) { long paddedSize) {
return encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, 0);
}
final byte[] encryptAESBlock(byte data[], SessionKey key, byte[] iv, Set tagsForDelivery, SessionKey newKey,
long paddedSize, int prefixBytes) {
//_log.debug("iv for encryption: " + DataHelper.toString(iv, 16)); //_log.debug("iv for encryption: " + DataHelper.toString(iv, 16));
//_log.debug("Encrypting AES"); //_log.debug("Encrypting AES");
try { if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET;
ByteArrayOutputStream aesSrc = new ByteArrayOutputStream((int) paddedSize); int size = 2 // sizeof(tags)
if (tagsForDelivery == null) tagsForDelivery = EMPTY_SET; + tagsForDelivery.size()
DataHelper.writeLong(aesSrc, 2, tagsForDelivery.size()); + SessionTag.BYTE_LENGTH*tagsForDelivery.size()
for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) { + 4 // payload length
SessionTag tag = (SessionTag) iter.next(); + Hash.HASH_LENGTH
aesSrc.write(tag.getData()); + (newKey == null ? 1 : 1 + SessionKey.KEYSIZE_BYTES)
} + data.length;
//_log.debug("# tags created, registered, and written: " + tags.size()); int totalSize = size + getPaddingSize(size, paddedSize);
DataHelper.writeLong(aesSrc, 4, data.length);
//_log.debug("data length: " + data.length);
Hash hash = _context.sha().calculateHash(data);
hash.writeBytes(aesSrc);
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
byte flag = 0x00; // don't rekey
aesSrc.write(flag);
//_log.debug("flag written");
} else {
byte flag = 0x01; // rekey
aesSrc.write(flag);
aesSrc.write(newKey.getData());
}
aesSrc.write(data);
int len = aesSrc.toByteArray().length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(_context, len, paddedSize);
//_log.debug("padding length: " + padding.length);
aesSrc.write(padding);
byte aesUnencr[] = aesSrc.toByteArray(); byte aesData[] = new byte[totalSize + prefixBytes];
//Hash h = _context.sha().calculateHash(aesUnencr);
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32)); int cur = prefixBytes;
byte aesEncr[] = new byte[aesUnencr.length]; DataHelper.toLong(aesData, cur, 2, tagsForDelivery.size());
_context.aes().encrypt(aesUnencr, 0, aesEncr, 0, key, iv, aesEncr.length); cur += 2;
//_log.debug("Encrypted length: " + aesEncr.length); for (Iterator iter = tagsForDelivery.iterator(); iter.hasNext();) {
return aesEncr; SessionTag tag = (SessionTag) iter.next();
} catch (IOException ioe) { System.arraycopy(tag.getData(), 0, aesData, cur, SessionTag.BYTE_LENGTH);
if (_log.shouldLog(Log.ERROR)) _log.error("Error encrypting AES chunk", ioe); cur += SessionTag.BYTE_LENGTH;
return null;
} catch (DataFormatException dfe) {
if (_log.shouldLog(Log.ERROR)) _log.error("Error formatting the bytes to write the AES chunk", dfe);
return null;
} }
//_log.debug("# tags created, registered, and written: " + tags.size());
DataHelper.toLong(aesData, cur, 4, data.length);
cur += 4;
//_log.debug("data length: " + data.length);
Hash hash = _context.sha().calculateHash(data);
System.arraycopy(hash.getData(), 0, aesData, cur, Hash.HASH_LENGTH);
cur += Hash.HASH_LENGTH;
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
if (newKey == null) {
aesData[cur++] = 0x00; // don't rekey
//_log.debug("flag written");
} else {
aesData[cur++] = 0x01; // rekey
System.arraycopy(newKey.getData(), 0, aesData, cur, SessionKey.KEYSIZE_BYTES);
cur += SessionKey.KEYSIZE_BYTES;
}
System.arraycopy(data, 0, aesData, cur, data.length);
cur += data.length;
//_log.debug("raw data written: " + len);
byte padding[] = getPadding(_context, size, paddedSize);
//_log.debug("padding length: " + padding.length);
System.arraycopy(padding, 0, aesData, cur, padding.length);
cur += padding.length;
//Hash h = _context.sha().calculateHash(aesUnencr);
//_log.debug("Hash of entire aes block before encryption: (len=" + aesUnencr.length + ")\n" + DataHelper.toString(h.getData(), 32));
_context.aes().encrypt(aesData, prefixBytes, aesData, prefixBytes, key, iv, aesData.length - prefixBytes);
//_log.debug("Encrypted length: " + aesEncr.length);
//return aesEncr;
return aesData;
} }
/** /**
@ -523,6 +520,12 @@ public class ElGamalAESEngine {
* *
*/ */
final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) { final static byte[] getPadding(I2PAppContext context, int curSize, long minPaddedSize) {
int size = getPaddingSize(curSize, minPaddedSize);
byte rv[] = new byte[size];
context.random().nextBytes(rv);
return rv;
}
final static int getPaddingSize(int curSize, long minPaddedSize) {
int diff = 0; int diff = 0;
if (curSize < minPaddedSize) { if (curSize < minPaddedSize) {
diff = (int) minPaddedSize - curSize; diff = (int) minPaddedSize - curSize;
@ -530,9 +533,7 @@ public class ElGamalAESEngine {
int numPadding = diff; int numPadding = diff;
if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16)); if (((curSize + diff) % 16) != 0) numPadding += (16 - ((curSize + diff) % 16));
byte rv[] = new byte[numPadding]; return numPadding;
context.random().nextBytes(rv);
return rv;
} }
} }

View File

@ -30,7 +30,6 @@ package net.i2p.crypto;
*/ */
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger; import java.math.BigInteger;
import net.i2p.I2PAppContext; import net.i2p.I2PAppContext;
@ -98,19 +97,12 @@ public class ElGamalEngine {
long start = _context.clock().now(); long start = _context.clock().now();
ByteArrayOutputStream baos = new ByteArrayOutputStream(256); byte d2[] = new byte[1+Hash.HASH_LENGTH+data.length];
try { d2[0] = (byte)0xFF;
baos.write(0xFF); Hash hash = _context.sha().calculateHash(data);
Hash hash = _context.sha().calculateHash(data); System.arraycopy(hash.getData(), 0, d2, 1, Hash.HASH_LENGTH);
hash.writeBytes(baos); System.arraycopy(data, 0, d2, 1+Hash.HASH_LENGTH, data.length);
baos.write(data);
baos.flush();
} catch (Exception e) {
if (_log.shouldLog(Log.ERROR)) _log.error("Internal error writing to buffer", e);
return null;
}
byte d2[] = baos.toByteArray();
long t0 = _context.clock().now(); long t0 = _context.clock().now();
BigInteger m = new NativeBigInteger(1, d2); BigInteger m = new NativeBigInteger(1, d2);
long t1 = _context.clock().now(); long t1 = _context.clock().now();

View File

@ -81,6 +81,13 @@ public class Payload extends DataStructureImpl {
out.write(_encryptedData); out.write(_encryptedData);
_log.debug("wrote payload: " + _encryptedData.length); _log.debug("wrote payload: " + _encryptedData.length);
} }
public int writeBytes(byte target[], int offset) {
if (_encryptedData == null) throw new IllegalStateException("Not yet encrypted. Please set the encrypted data");
DataHelper.toLong(target, offset, 4, _encryptedData.length);
offset += 4;
System.arraycopy(_encryptedData, 0, target, offset, _encryptedData.length);
return 4 + _encryptedData.length;
}
public boolean equals(Object object) { public boolean equals(Object object) {
if ((object == null) || !(object instanceof Payload)) return false; if ((object == null) || !(object instanceof Payload)) return false;
@ -94,6 +101,7 @@ public class Payload extends DataStructureImpl {
} }
public String toString() { public String toString() {
if (true) return "[Payload]";
StringBuffer buf = new StringBuffer(128); StringBuffer buf = new StringBuffer(128);
buf.append("[Payload: "); buf.append("[Payload: ");
if (getUnencryptedData() != null) if (getUnencryptedData() != null)

View File

@ -12,11 +12,13 @@ package net.i2p.data.i2cp;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream;
import net.i2p.data.DataFormatException; import net.i2p.data.DataFormatException;
import net.i2p.data.DataHelper; import net.i2p.data.DataHelper;
import net.i2p.data.Destination; import net.i2p.data.Destination;
import net.i2p.data.Payload; import net.i2p.data.Payload;
import net.i2p.data.i2cp.I2CPMessageException;
import net.i2p.util.Log; import net.i2p.util.Log;
/** /**
@ -87,20 +89,31 @@ public class SendMessageMessage extends I2CPMessageImpl {
} }
protected byte[] doWriteMessage() throws I2CPMessageException, IOException { protected byte[] doWriteMessage() throws I2CPMessageException, IOException {
if ((_sessionId == null) || (_destination == null) || (_payload == null) || (_nonce <= 0)) throw new RuntimeException("wtf, dont run me");
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
ByteArrayOutputStream os = new ByteArrayOutputStream(512);
try {
_sessionId.writeBytes(os);
_destination.writeBytes(os);
_payload.writeBytes(os);
DataHelper.writeLong(os, 4, _nonce);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing out the message data", dfe);
}
return os.toByteArray();
} }
/**
* Write out the full message to the stream, including the 4 byte size and 1
* byte type header. Override the parent so we can be more mem efficient
*
*/
public void writeMessage(OutputStream out) throws I2CPMessageException, IOException {
if ((_sessionId == null) || (_destination == null) || (_payload == null) || (_nonce <= 0))
throw new I2CPMessageException("Unable to write out the message as there is not enough data");
int len = 2 + _destination.size() + _payload.getSize() + 4 + 4;
try {
DataHelper.writeLong(out, 4, len);
DataHelper.writeLong(out, 1, getType());
_sessionId.writeBytes(out);
_destination.writeBytes(out);
_payload.writeBytes(out);
DataHelper.writeLong(out, 4, _nonce);
} catch (DataFormatException dfe) {
throw new I2CPMessageException("Error writing the msg", dfe);
}
}
public int getType() { public int getType() {
return MESSAGE_TYPE; return MESSAGE_TYPE;
} }

View File

@ -123,7 +123,7 @@ class ElGamalAESEngineTest {
_log.debug("** Encryption complete. Beginning decryption"); _log.debug("** Encryption complete. Beginning decryption");
Set foundTags = new HashSet(); Set foundTags = new HashSet();
SessionKey foundKey = new SessionKey(); SessionKey foundKey = new SessionKey();
byte decrypted[] = _context.elGamalAESEngine().decryptAESBlock(encrypted, sessionKey, iv, null, foundTags, foundKey); byte decrypted[] = _context.elGamalAESEngine().decryptAESBlock(encrypted, 0, encrypted.length, sessionKey, iv, null, foundTags, foundKey);
if (decrypted == null) throw new Exception("Decryption failed"); if (decrypted == null) throw new Exception("Decryption failed");
String read = new String(decrypted); String read = new String(decrypted);
_log.debug("read: " + read); _log.debug("read: " + read);

View File

@ -1,4 +1,18 @@
$Id: history.txt,v 1.57 2004/10/27 21:11:52 jrandom Exp $ $Id: history.txt,v 1.58 2004/10/29 21:40:52 jrandom Exp $
2004-10-30 jrandom
* Cache the temporary objects used in the AES encryption/decryption
process so that AES doesn't require any memory allocation to process
data.
* Dramatically reduce memory usage within various crypto implementations
by avoiding unnecessary (though simplifying) buffers.
* If we specify some tags to be sent in an I2CP message explicitly, use
only those, not those plus a new set (otherwise we aren't sure on ACK
which set was delivered)
* Allow configuration for the partial send timeout (how long before
resending a message down a different tunnel in a lease). This can be
updated with the "router.clientPartialSendTimeout" router config prop.
* Logging
2004-10-29 jrandom 2004-10-29 jrandom
* Strip the Referer, Via, and From headers completely, rather than * Strip the Referer, Via, and From headers completely, rather than

View File

@ -15,9 +15,9 @@ import net.i2p.CoreVersion;
* *
*/ */
public class RouterVersion { public class RouterVersion {
public final static String ID = "$Revision: 1.63 $ $Date: 2004/10/27 21:11:52 $"; public final static String ID = "$Revision: 1.64 $ $Date: 2004/10/29 21:40:52 $";
public final static String VERSION = "0.4.1.3"; public final static String VERSION = "0.4.1.3";
public final static long BUILD = 4; public final static long BUILD = 5;
public static void main(String args[]) { public static void main(String args[]) {
System.out.println("I2P Router version: " + VERSION); System.out.println("I2P Router version: " + VERSION);
System.out.println("Router ID: " + RouterVersion.ID); System.out.println("Router ID: " + RouterVersion.ID);

View File

@ -36,7 +36,7 @@ import net.i2p.util.Log;
/** /**
* Send a client message, taking into consideration the fact that there may be * Send a client message, taking into consideration the fact that there may be
* multiple inbound tunnels that the target provides. This job sends it to one * multiple inbound tunnels that the target provides. This job sends it to one
* of them and if it doesnt get a confirmation within 15 seconds (SEND_TIMEOUT_MS), * of them and if it doesnt get a confirmation within a few seconds (getSendTimeout()),
* it tries the next, continuing on until a confirmation is received, the full * it tries the next, continuing on until a confirmation is received, the full
* timeout has been reached (60 seconds, or the ms defined in the client's or * timeout has been reached (60 seconds, or the ms defined in the client's or
* router's "clientMessageTimeout" option). * router's "clientMessageTimeout" option).
@ -63,7 +63,9 @@ public class OutboundClientMessageJob extends JobImpl {
private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000; private final static long OVERALL_TIMEOUT_MS_DEFAULT = 60*1000;
/** how long for each send do we allow before going on to the next? */ /** how long for each send do we allow before going on to the next? */
private final static long SEND_TIMEOUT_MS = 10*1000; private final static long DEFAULT_SEND_PARTIAL_TIMEOUT = 10*1000;
private static final String PROP_SEND_PARTIAL_TIMEOUT = "router.clientPartialSendTimeout";
/** priority of messages, that might get honored some day... */ /** priority of messages, that might get honored some day... */
private final static int SEND_PRIORITY = 500; private final static int SEND_PRIORITY = 500;
@ -132,6 +134,15 @@ public class OutboundClientMessageJob extends JobImpl {
_shouldBundle = getShouldBundle(); _shouldBundle = getShouldBundle();
} }
private long getSendTimeout() {
String timeout = getContext().getProperty(PROP_SEND_PARTIAL_TIMEOUT, ""+DEFAULT_SEND_PARTIAL_TIMEOUT);
try {
return Long.parseLong(timeout);
} catch (NumberFormatException nfe) {
return DEFAULT_SEND_PARTIAL_TIMEOUT;
}
}
public String getName() { return "Outbound client message"; } public String getName() { return "Outbound client message"; }
public void runJob() { public void runJob() {
@ -375,7 +386,7 @@ public class OutboundClientMessageJob extends JobImpl {
SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, outTunnelId, SendTunnelMessageJob j = new SendTunnelMessageJob(getContext(), msg, outTunnelId,
lease.getRouterIdentity().getHash(), lease.getRouterIdentity().getHash(),
lease.getTunnelId(), null, onReply, lease.getTunnelId(), null, onReply,
onFail, selector, SEND_TIMEOUT_MS, onFail, selector, getSendTimeout(),
SEND_PRIORITY); SEND_PRIORITY);
getContext().jobQueue().addJob(j); getContext().jobQueue().addJob(j);
} else { } else {