* 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:
zzz
2011-09-06 12:11:55 +00:00
parent 19905e99be
commit 3d5beece87
3 changed files with 86 additions and 43 deletions

View File

@@ -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;

View File

@@ -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));

View File

@@ -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;
}