forked from I2P_Developers/i2p.i2p
* Crypto: Rework use of SHA256 for efficiency and
to avoid clogging the Hash cache with one-time hashes, and avoiding the global cache lock. This also greatly increases Hash cache hit rates. Also use SimpleByteCache for temporary byte buffers.
This commit is contained in:
@@ -15,6 +15,7 @@ import net.i2p.data.Hash;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.SimpleByteCache;
|
||||
|
||||
/**
|
||||
* Dummy wrapper for AES cipher operation.
|
||||
@@ -64,7 +65,7 @@ public class AESEngine {
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt the SHA-256 Hash of the payload, the 4 byte length, and the payload,
|
||||
* Encrypt the SHA-256 Hash of the IV, the 4 byte length, and the payload,
|
||||
* with random padding up to the paddedSize, rounded up to the next multiple of 16.
|
||||
*
|
||||
* @param paddedSize minimum size of the output
|
||||
@@ -81,11 +82,8 @@ public class AESEngine {
|
||||
int padding = ElGamalAESEngine.getPaddingSize(size, paddedSize);
|
||||
|
||||
byte data[] = new byte[size + padding];
|
||||
Hash h = _context.sha().calculateHash(iv);
|
||||
|
||||
int cur = 0;
|
||||
System.arraycopy(h.getData(), 0, data, cur, Hash.HASH_LENGTH);
|
||||
cur += Hash.HASH_LENGTH;
|
||||
_context.sha().calculateHash(iv, 0, 16, data, 0);
|
||||
int cur = Hash.HASH_LENGTH;
|
||||
|
||||
DataHelper.toLong(data, cur, 4, payload.length);
|
||||
cur += 4;
|
||||
@@ -116,16 +114,16 @@ public class AESEngine {
|
||||
return null;
|
||||
}
|
||||
|
||||
int cur = 0;
|
||||
byte h[] = _context.sha().calculateHash(iv).getData();
|
||||
for (int i = 0; i < Hash.HASH_LENGTH; i++) {
|
||||
if (decr[i] != h[i]) {
|
||||
byte h[] = SimpleByteCache.acquire(Hash.HASH_LENGTH);
|
||||
_context.sha().calculateHash(iv, 0, 16, h, 0);
|
||||
boolean eq = DataHelper.eq(decr, 0, h, 0, Hash.HASH_LENGTH);
|
||||
SimpleByteCache.release(h);
|
||||
if (!eq) {
|
||||
_log.error("Hash does not match [key=" + sessionKey + " / iv =" + DataHelper.toString(iv, iv.length)
|
||||
+ "]", new Exception("Hash error"));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
cur += Hash.HASH_LENGTH;
|
||||
int cur = Hash.HASH_LENGTH;
|
||||
|
||||
long len = DataHelper.fromLong(decr, cur, 4);
|
||||
cur += 4;
|
||||
|
@@ -26,6 +26,7 @@ import net.i2p.data.PublicKey;
|
||||
import net.i2p.data.SessionKey;
|
||||
import net.i2p.data.SessionTag;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.SimpleByteCache;
|
||||
|
||||
/**
|
||||
* Handles the actual ElGamal+AES encryption and decryption scenarios using the
|
||||
@@ -87,7 +88,7 @@ public class ElGamalAESEngine {
|
||||
}
|
||||
|
||||
byte tag[] = new byte[32];
|
||||
System.arraycopy(data, 0, tag, 0, tag.length);
|
||||
System.arraycopy(data, 0, tag, 0, 32);
|
||||
SessionTag st = new SessionTag(tag);
|
||||
SessionKey key = keyManager.consumeTag(st);
|
||||
SessionKey foundKey = new SessionKey();
|
||||
@@ -185,27 +186,30 @@ public class ElGamalAESEngine {
|
||||
return null;
|
||||
}
|
||||
|
||||
byte preIV[] = null;
|
||||
|
||||
int offset = 0;
|
||||
byte key[] = new byte[SessionKey.KEYSIZE_BYTES];
|
||||
System.arraycopy(elgDecr, offset, key, 0, SessionKey.KEYSIZE_BYTES);
|
||||
offset += SessionKey.KEYSIZE_BYTES;
|
||||
usedKey.setData(key);
|
||||
preIV = new byte[32];
|
||||
byte[] preIV = SimpleByteCache.acquire(32);
|
||||
System.arraycopy(elgDecr, offset, preIV, 0, 32);
|
||||
offset += 32;
|
||||
|
||||
//_log.debug("Pre IV for decryptNewSession: " + DataHelper.toString(preIV, 32));
|
||||
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
|
||||
Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
|
||||
// use alternate calculateHash() method to avoid object churn and caching
|
||||
//Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
//byte iv[] = new byte[16];
|
||||
//System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
byte[] iv = halfHash(preIV);
|
||||
SimpleByteCache.release(preIV);
|
||||
|
||||
// feed the extra bytes into the PRNG
|
||||
_context.random().harvester().feedEntropy("ElG/AES", elgDecr, offset, elgDecr.length - offset);
|
||||
|
||||
byte aesDecr[] = decryptAESBlock(data, 514, data.length-514, usedKey, iv, null, foundTags, foundKey);
|
||||
SimpleByteCache.release(iv);
|
||||
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
// _log.debug("Decrypt with a NEW session successfull: # tags read = " + foundTags.size(),
|
||||
@@ -238,15 +242,19 @@ public class ElGamalAESEngine {
|
||||
*/
|
||||
private byte[] decryptExistingSession(byte data[], SessionKey key, PrivateKey targetPrivateKey, Set foundTags,
|
||||
SessionKey usedKey, SessionKey foundKey) throws DataFormatException {
|
||||
byte preIV[] = new byte[32];
|
||||
System.arraycopy(data, 0, preIV, 0, preIV.length);
|
||||
Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
byte preIV[] = SimpleByteCache.acquire(32);
|
||||
System.arraycopy(data, 0, preIV, 0, 32);
|
||||
// use alternate calculateHash() method to avoid object churn and caching
|
||||
//Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
//byte iv[] = new byte[16];
|
||||
//System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
byte[] iv = halfHash(preIV);
|
||||
SimpleByteCache.release(preIV);
|
||||
|
||||
//_log.debug("Pre IV for decryptExistingSession: " + DataHelper.toString(preIV, 32));
|
||||
//_log.debug("SessionKey for decryptNewSession: " + DataHelper.toString(key.getData(), 32));
|
||||
byte decrypted[] = decryptAESBlock(data, 32, data.length-32, key, iv, preIV, foundTags, foundKey);
|
||||
SimpleByteCache.release(iv);
|
||||
if (decrypted == null) {
|
||||
// it begins with a valid session tag, but thats just a coincidence.
|
||||
//if (_log.shouldLog(Log.DEBUG))
|
||||
@@ -336,7 +344,8 @@ public class ElGamalAESEngine {
|
||||
//System.arraycopy(decrypted, cur, hashval, 0, Hash.HASH_LENGTH);
|
||||
//readHash = new Hash();
|
||||
//readHash.setData(hashval);
|
||||
readHash = Hash.create(decrypted, cur);
|
||||
//readHash = Hash.create(decrypted, cur);
|
||||
int hashIndex = cur;
|
||||
cur += Hash.HASH_LENGTH;
|
||||
byte flag = decrypted[cur++];
|
||||
if (flag == 0x01) {
|
||||
@@ -348,9 +357,14 @@ public class ElGamalAESEngine {
|
||||
}
|
||||
byte unencrData[] = new byte[(int) len];
|
||||
System.arraycopy(decrypted, cur, unencrData, 0, (int)len);
|
||||
cur += len;
|
||||
Hash calcHash = _context.sha().calculateHash(unencrData);
|
||||
boolean eq = calcHash.equals(readHash);
|
||||
cur += (int) len;
|
||||
// use alternate calculateHash() method to avoid object churn and caching
|
||||
//Hash calcHash = _context.sha().calculateHash(unencrData);
|
||||
//boolean eq = calcHash.equals(readHash);
|
||||
byte[] calcHash = SimpleByteCache.acquire(32);
|
||||
_context.sha().calculateHash(unencrData, 0, (int) len, calcHash, 0);
|
||||
boolean eq = DataHelper.eq(decrypted, hashIndex, calcHash, 0, 32);
|
||||
SimpleByteCache.release(calcHash);
|
||||
|
||||
if (eq) {
|
||||
// everything matches. w00t.
|
||||
@@ -457,7 +471,7 @@ public class ElGamalAESEngine {
|
||||
//_log.debug("Encrypting to a NEW session");
|
||||
byte elgSrcData[] = new byte[SessionKey.KEYSIZE_BYTES+32+158];
|
||||
System.arraycopy(key.getData(), 0, elgSrcData, 0, SessionKey.KEYSIZE_BYTES);
|
||||
byte preIV[] = new byte[32];
|
||||
byte preIV[] = SimpleByteCache.acquire(32);
|
||||
_context.random().nextBytes(preIV);
|
||||
System.arraycopy(preIV, 0, elgSrcData, SessionKey.KEYSIZE_BYTES, 32);
|
||||
byte rnd[] = new byte[158];
|
||||
@@ -484,10 +498,15 @@ public class ElGamalAESEngine {
|
||||
|
||||
// should we also feed the encrypted elG block into the harvester?
|
||||
|
||||
Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
// use alternate calculateHash() method to avoid object churn and caching
|
||||
//Hash ivHash = _context.sha().calculateHash(preIV);
|
||||
//byte iv[] = new byte[16];
|
||||
//System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
byte[] iv = halfHash(preIV);
|
||||
SimpleByteCache.release(preIV);
|
||||
|
||||
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize);
|
||||
SimpleByteCache.release(iv);
|
||||
//_log.debug("AES encrypted length: " + aesEncr.length);
|
||||
|
||||
byte rv[] = new byte[elgEncr.length + aesEncr.length];
|
||||
@@ -522,16 +541,38 @@ public class ElGamalAESEngine {
|
||||
|
||||
//_log.debug("Pre IV for encryptExistingSession (aka tag): " + currentTag.toString());
|
||||
//_log.debug("SessionKey for encryptNewSession: " + DataHelper.toString(key.getData(), 32));
|
||||
Hash ivHash = _context.sha().calculateHash(rawTag);
|
||||
byte iv[] = new byte[16];
|
||||
System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
// use alternate calculateHash() method to avoid object churn and caching
|
||||
//Hash ivHash = _context.sha().calculateHash(rawTag);
|
||||
//byte iv[] = new byte[16];
|
||||
//System.arraycopy(ivHash.getData(), 0, iv, 0, 16);
|
||||
byte[] iv = halfHash(rawTag);
|
||||
|
||||
byte aesEncr[] = encryptAESBlock(data, key, iv, tagsForDelivery, newKey, paddedSize, SessionTag.BYTE_LENGTH);
|
||||
SimpleByteCache.release(iv);
|
||||
// that prepended SessionTag.BYTE_LENGTH bytes at the beginning of the buffer
|
||||
System.arraycopy(rawTag, 0, aesEncr, 0, rawTag.length);
|
||||
return aesEncr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the first 16 bytes of the SHA-256 hash of the data.
|
||||
*
|
||||
* Here we are careful to use the SHA256Generator method that does not
|
||||
* generate a Hash object or cache the result.
|
||||
*
|
||||
* @param preIV the 32 byte pre-IV. Caller should call SimpleByteCache.release(data) after use.
|
||||
* @return a 16 byte array. Caller should call SimpleByteCache.release(rv) after use.
|
||||
* @since 0.8.9
|
||||
*/
|
||||
private byte[] halfHash(byte[] preIV) {
|
||||
byte[] ivHash = SimpleByteCache.acquire(32);
|
||||
_context.sha().calculateHash(preIV, 0, 32, ivHash, 0);
|
||||
byte iv[] = SimpleByteCache.acquire(16);
|
||||
System.arraycopy(ivHash, 0, iv, 0, 16);
|
||||
SimpleByteCache.release(ivHash);
|
||||
return iv;
|
||||
}
|
||||
|
||||
/**
|
||||
* For both scenarios, this method encrypts the AES area using the given key, iv
|
||||
* and making sure the resulting data is at least as long as the paddedSize and
|
||||
@@ -581,8 +622,10 @@ public class ElGamalAESEngine {
|
||||
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);
|
||||
// use alternate calculateHash() method to avoid object churn and caching
|
||||
//Hash hash = _context.sha().calculateHash(data);
|
||||
//System.arraycopy(hash.getData(), 0, aesData, cur, Hash.HASH_LENGTH);
|
||||
_context.sha().calculateHash(data, 0, data.length, aesData, cur);
|
||||
cur += Hash.HASH_LENGTH;
|
||||
|
||||
//_log.debug("hash of data: " + DataHelper.toString(hash.getData(), 32));
|
||||
|
@@ -41,6 +41,7 @@ import net.i2p.util.Clock;
|
||||
import net.i2p.util.Log;
|
||||
import net.i2p.util.NativeBigInteger;
|
||||
import net.i2p.util.RandomSource;
|
||||
import net.i2p.util.SimpleByteCache;
|
||||
|
||||
/**
|
||||
* Wrapper for ElGamal encryption/signature schemes.
|
||||
@@ -123,8 +124,7 @@ public class ElGamalEngine {
|
||||
byte d2[] = new byte[1+Hash.HASH_LENGTH+data.length];
|
||||
// FIXME this isn't a random nonzero byte!
|
||||
d2[0] = (byte)0xFF;
|
||||
Hash hash = _context.sha().calculateHash(data);
|
||||
System.arraycopy(hash.getData(), 0, d2, 1, Hash.HASH_LENGTH);
|
||||
_context.sha().calculateHash(data, 0, data.length, d2, 1);
|
||||
System.arraycopy(data, 0, d2, 1+Hash.HASH_LENGTH, data.length);
|
||||
|
||||
//long t0 = _context.clock().now();
|
||||
@@ -221,12 +221,14 @@ public class ElGamalEngine {
|
||||
//byte hashData[] = new byte[Hash.HASH_LENGTH];
|
||||
//System.arraycopy(val, i + 1, hashData, 0, Hash.HASH_LENGTH);
|
||||
//Hash hash = new Hash(hashData);
|
||||
Hash hash = Hash.create(val, i + 1);
|
||||
//Hash hash = Hash.create(val, i + 1);
|
||||
byte rv[] = new byte[payloadLen];
|
||||
System.arraycopy(val, i + 1 + Hash.HASH_LENGTH, rv, 0, rv.length);
|
||||
|
||||
Hash calcHash = _context.sha().calculateHash(rv);
|
||||
boolean ok = calcHash.equals(hash);
|
||||
byte[] calcHash = SimpleByteCache.acquire(Hash.HASH_LENGTH);
|
||||
_context.sha().calculateHash(rv, 0, payloadLen, calcHash, 0);
|
||||
boolean ok = DataHelper.eq(calcHash, 0, val, i + 1, Hash.HASH_LENGTH);
|
||||
SimpleByteCache.release(calcHash);
|
||||
|
||||
long end = _context.clock().now();
|
||||
|
||||
@@ -243,7 +245,7 @@ public class ElGamalEngine {
|
||||
return rv;
|
||||
}
|
||||
if (_log.shouldLog(Log.DEBUG))
|
||||
_log.debug("Doesn't match hash [sent hash=" + hash + "]\ndata = "
|
||||
_log.debug("Doesn't match hash data = "
|
||||
+ Base64.encode(rv), new Exception("Doesn't match"));
|
||||
return null;
|
||||
}
|
||||
|
Reference in New Issue
Block a user