diff --git a/core/java/src/net/i2p/crypto/AESEngine.java b/core/java/src/net/i2p/crypto/AESEngine.java index d030647ca..652e563cd 100644 --- a/core/java/src/net/i2p/crypto/AESEngine.java +++ b/core/java/src/net/i2p/crypto/AESEngine.java @@ -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; diff --git a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java index 0d4539e3c..55854afe1 100644 --- a/core/java/src/net/i2p/crypto/ElGamalAESEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalAESEngine.java @@ -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)); diff --git a/core/java/src/net/i2p/crypto/ElGamalEngine.java b/core/java/src/net/i2p/crypto/ElGamalEngine.java index 2e7fbff09..1ea312d0c 100644 --- a/core/java/src/net/i2p/crypto/ElGamalEngine.java +++ b/core/java/src/net/i2p/crypto/ElGamalEngine.java @@ -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; }