2005-07-05

* Use a buffered PRNG, pulling the PRNG data off a larger precalculated
      buffer, rather than the underlying PRNG's (likely small) one, which in
      turn reduces the frequency of recalcing.
    * More tuning to reduce temporary allocation churn
This commit is contained in:
jrandom
2005-07-05 22:08:56 +00:00
committed by zzz
parent 18d3f5d25d
commit f688b9112d
22 changed files with 709 additions and 65 deletions

View File

@ -9,12 +9,16 @@ import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.digests.MD5Digest;
import org.bouncycastle.crypto.macs.HMac;
/**
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
* in {@link org.bouncycastle.crypto.macs.HMac} and
* {@link org.bouncycastle.crypto.digests.SHA256Digest}.
* {@link org.bouncycastle.crypto.digests.SHA256Digest}. Alternately, if
* the context property "i2p.HMACMD5" is set to true, then this whole HMAC
* generator will be transformed into HMACMD5, maintaining the same size and
* using {@link org.bouncycastle.crypto.digests.MD5Digest}.
*
*/
public class HMACSHA256Generator {
@ -23,11 +27,18 @@ public class HMACSHA256Generator {
private List _available;
/** set of available byte[] buffers for verify */
private List _availableTmp;
private boolean _useMD5;
public static final boolean DEFAULT_USE_MD5 = true;
public HMACSHA256Generator(I2PAppContext context) {
_context = context;
_available = new ArrayList(32);
_availableTmp = new ArrayList(32);
if ("true".equals(context.getProperty("i2p.HMACMD5", Boolean.toString(DEFAULT_USE_MD5).toLowerCase())))
_useMD5 = true;
else
_useMD5 = false;
}
public static HMACSHA256Generator getInstance() {
@ -40,12 +51,15 @@ public class HMACSHA256Generator {
public Hash calculate(SessionKey key, byte data[]) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");
return calculate(key, data, 0, data.length);
byte rv[] = new byte[Hash.HASH_LENGTH];
calculate(key, data, 0, data.length, rv, 0);
return new Hash(rv);
}
/**
* Calculate the HMAC of the data with the given key
*/
/*
public Hash calculate(SessionKey key, byte data[], int offset, int length) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");
@ -58,6 +72,23 @@ public class HMACSHA256Generator {
release(mac);
return new Hash(rv);
}
*/
/**
* Calculate the HMAC of the data with the given key
*/
public void calculate(SessionKey key, byte data[], int offset, int length, byte target[], int targetOffset) {
if ((key == null) || (key.getData() == null) || (data == null))
throw new NullPointerException("Null arguments for HMAC");
HMac mac = acquire();
mac.init(key.getData());
mac.update(data, offset, length);
//byte rv[] = new byte[Hash.HASH_LENGTH];
mac.doFinal(target, targetOffset);
release(mac);
//return new Hash(rv);
}
/**
* Verify the MAC inline, reducing some unnecessary memory churn.
@ -92,6 +123,9 @@ public class HMACSHA256Generator {
if (_available.size() > 0)
return (HMac)_available.remove(0);
}
if (_useMD5)
return new HMac(new MD5Digest());
else
return new HMac(new SHA256Digest());
}
private void release(HMac mac) {

View File

@ -1,6 +1,8 @@
package net.i2p.crypto;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import net.i2p.I2PAppContext;
import net.i2p.data.Hash;
@ -12,7 +14,10 @@ import org.bouncycastle.crypto.digests.SHA256Digest;
*
*/
public final class SHA256Generator {
public SHA256Generator(I2PAppContext context) {}
private List _digests;
public SHA256Generator(I2PAppContext context) {
_digests = new ArrayList(32);
}
public static final SHA256Generator getInstance() {
return I2PAppContext.getGlobalContext().sha();
@ -27,11 +32,35 @@ public final class SHA256Generator {
}
public final Hash calculateHash(byte[] source, int start, int len) {
byte rv[] = new byte[Hash.HASH_LENGTH];
SHA256Digest digest = new SHA256Digest();
digest.update(source, start, len);
digest.doFinal(rv, 0);
calculateHash(source, start, len, rv, 0);
return new Hash(rv);
}
public final void calculateHash(byte[] source, int start, int len, byte out[], int outOffset) {
SHA256Digest digest = acquire();
digest.update(source, start, len);
digest.doFinal(out, outOffset);
release(digest);
}
private SHA256Digest acquire() {
SHA256Digest rv = null;
synchronized (_digests) {
if (_digests.size() > 0)
rv = (SHA256Digest)_digests.remove(0);
}
if (rv != null)
rv.reset();
else
rv = new SHA256Digest();
return rv;
}
private void release(SHA256Digest digest) {
synchronized (_digests) {
if (_digests.size() < 32) {
_digests.add(digest);
}
}
}
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();

View File

@ -0,0 +1,222 @@
package net.i2p.util;
/*
* free (adj.): unencumbered; not under the control of others
* Written by jrandom in 2005 and released into the public domain
* with no warranty of any kind, either expressed or implied.
* It probably won't make your computer catch on fire, or eat
* your children, but it might. Use at your own risk.
*
*/
import java.security.SecureRandom;
import net.i2p.I2PAppContext;
import net.i2p.crypto.EntropyHarvester;
import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
/**
* Allocate data out of a large buffer of data, rather than the PRNG's
* (likely) small buffer to reduce the frequency of prng recalcs (though
* the recalcs are now more time consuming).
*
*/
public class BufferedRandomSource extends RandomSource {
private byte _buffer[];
private int _nextByte;
private int _nextBit;
private static volatile long _reseeds;
private static final int BUFFER_SIZE = 256*1024;
public BufferedRandomSource(I2PAppContext context) {
super(context);
context.statManager().createRateStat("prng.reseedCount", "How many times the prng has been reseeded", "Encryption", new long[] { 60*1000, 10*60*1000, 60*60*1000 } );
_buffer = new byte[BUFFER_SIZE];
refillBuffer();
// stagger reseeding
_nextByte = ((int)_reseeds-1) * 16 * 1024;
}
private final void refillBuffer() {
long before = System.currentTimeMillis();
doRefillBuffer();
long duration = System.currentTimeMillis() - before;
if ( (_reseeds % 1) == 0)
_context.statManager().addRateData("prng.reseedCount", _reseeds, duration);
}
private synchronized final void doRefillBuffer() {
super.nextBytes(_buffer);
_nextByte = 0;
_nextBit = 0;
_reseeds++;
}
private static final byte GOBBLE_MASK[] = { 0x0, // 0 bits
0x1, // 1 bit
0x3, // 2 bits
0x7, // 3 bits
0xF, // 4 bits
0x1F, // 5 bits
0x3F, // 6 bits
0x7F, // 7 bits
(byte)0xFF // 8 bits
};
private synchronized final long nextBits(int numBits) {
if (false) {
long rv = 0;
for (int curBit = 0; curBit < numBits; curBit++) {
if (_nextBit >= 8) {
_nextBit = 0;
_nextByte++;
}
if (_nextByte >= BUFFER_SIZE)
refillBuffer();
rv += (_buffer[_nextByte] << curBit);
_nextBit++;
/*
int avail = 8 - _nextBit;
// this is not correct! (or is it?)
rv += (_buffer[_nextByte] << 8 - avail);
_nextBit += avail;
numBits -= avail;
if (_nextBit >= 8) {
_nextBit = 0;
_nextByte++;
}
*/
}
return rv;
} else {
long rv = 0;
int curBit = 0;
while (curBit < numBits) {
if (_nextBit >= 8) {
_nextBit = 0;
_nextByte++;
}
if (_nextByte >= BUFFER_SIZE)
refillBuffer();
int gobbleBits = 8 - _nextBit;
int want = numBits - curBit;
if (gobbleBits > want)
gobbleBits = want;
curBit += gobbleBits;
int shift = 8 - _nextBit - gobbleBits;
int c = (_buffer[_nextByte] & (GOBBLE_MASK[gobbleBits] << shift));
rv += ((c >>> shift) << (curBit-gobbleBits));
_nextBit += gobbleBits;
}
return rv;
}
}
public synchronized final void nextBytes(byte buf[]) {
int outOffset = 0;
while (outOffset < buf.length) {
int availableBytes = BUFFER_SIZE - _nextByte - (_nextBit != 0 ? 1 : 0);
if (availableBytes <= 0)
refillBuffer();
int start = BUFFER_SIZE - availableBytes;
int writeSize = Math.min(buf.length - outOffset, availableBytes);
System.arraycopy(_buffer, start, buf, outOffset, writeSize);
outOffset += writeSize;
_nextByte += writeSize;
_nextBit = 0;
}
}
public final int nextInt(int n) {
if (n <= 0) return 0;
int val = ((int)nextBits(countBits(n))) % n;
if (val < 0)
return 0 - val;
else
return val;
}
public final int nextInt() { return nextInt(Integer.MAX_VALUE); }
/**
* Like the modified nextInt, nextLong(n) returns a random number from 0 through n,
* including 0, excluding n.
*/
public final long nextLong(long n) {
if (n <= 0) return 0;
long val = nextBits(countBits(n)) % n;
if (val < 0)
return 0 - val;
else
return val;
}
public final long nextLong() { return nextLong(Long.MAX_VALUE); }
static final int countBits(long val) {
int rv = 0;
while (val > Integer.MAX_VALUE) {
rv += 31;
val >>>= 31;
}
while (val > 0) {
rv++;
val >>= 1;
}
return rv;
}
/**
* override as synchronized, for those JVMs that don't always pull via
* nextBytes (cough ibm)
*/
public final boolean nextBoolean() {
return nextBits(1) != 0;
}
private static final double DOUBLE_DENOMENATOR = (double)(1L << 53);
/** defined per javadoc ( ((nextBits(26)<<27) + nextBits(27)) / (1 << 53)) */
public final double nextDouble() {
long top = (((long)nextBits(26) << 27) + nextBits(27));
return top / DOUBLE_DENOMENATOR;
}
private static final float FLOAT_DENOMENATOR = (float)(1 << 24);
/** defined per javadoc (nextBits(24) / ((float)(1 << 24)) ) */
public float nextFloat() {
long top = nextBits(24);
return top / FLOAT_DENOMENATOR;
}
public double nextGaussian() {
// bah, unbuffered
return super.nextGaussian();
}
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[16*1024];
for (int i = 0; i < data.length; i += 4) {
long l = ctx.random().nextLong();
if (l < 0) l = 0 - l;
DataHelper.toLong(data, i, 4, l);
}
byte compressed[] = DataHelper.compress(data);
System.out.println("Compressed: " + compressed.length);
System.out.println("Orig: " + data.length + ": " + toString(data));
}
private static final String toString(byte data[]) {
StringBuffer buf = new StringBuffer(data.length * 9);
for (int i = 0; i < data.length; i++) {
for (int j = 0; j < 8; j++) {
if ((data[i] & (1 << j)) != 0)
buf.append('1');
else
buf.append('0');
}
buf.append(' ');
}
return buf.toString();
}
}

View File

@ -27,7 +27,8 @@ public class PooledRandomSource extends RandomSource {
_log = context.logManager().getLog(PooledRandomSource.class);
_pool = new RandomSource[POOL_SIZE];
for (int i = 0; i < POOL_SIZE; i++) {
_pool[i] = new RandomSource(context);
//_pool[i] = new RandomSource(context);
_pool[i] = new BufferedRandomSource(context);
_pool[i].nextBoolean();
}
_nextPool = 0;

View File

@ -22,9 +22,11 @@ import net.i2p.crypto.EntropyHarvester;
public class RandomSource extends SecureRandom {
private Log _log;
private EntropyHarvester _entropyHarvester;
protected I2PAppContext _context;
public RandomSource(I2PAppContext context) {
super();
_context = context;
_log = context.logManager().getLog(RandomSource.class);
// when we replace to have hooks for fortuna (etc), replace with
// a factory (or just a factory method)

View File

@ -0,0 +1,302 @@
package org.bouncycastle.crypto.digests;
/**
* implementation of MD5 as outlined in "Handbook of Applied Cryptography", pages 346 - 347.
*/
public class MD5Digest
extends GeneralDigest
{
private static final int DIGEST_LENGTH = 16;
private int H1, H2, H3, H4; // IV's
private int[] X = new int[16];
private int xOff;
/**
* Standard constructor
*/
public MD5Digest()
{
reset();
}
/**
* Copy constructor. This will copy the state of the provided
* message digest.
*/
public MD5Digest(MD5Digest t)
{
super(t);
H1 = t.H1;
H2 = t.H2;
H3 = t.H3;
H4 = t.H4;
System.arraycopy(t.X, 0, X, 0, t.X.length);
xOff = t.xOff;
}
public String getAlgorithmName()
{
return "MD5";
}
public int getDigestSize()
{
return DIGEST_LENGTH;
}
protected void processWord(
byte[] in,
int inOff)
{
X[xOff++] = (in[inOff] & 0xff) | ((in[inOff + 1] & 0xff) << 8)
| ((in[inOff + 2] & 0xff) << 16) | ((in[inOff + 3] & 0xff) << 24);
if (xOff == 16)
{
processBlock();
}
}
protected void processLength(
long bitLength)
{
if (xOff > 14)
{
processBlock();
}
X[14] = (int)(bitLength & 0xffffffff);
X[15] = (int)(bitLength >>> 32);
}
private void unpackWord(
int word,
byte[] out,
int outOff)
{
out[outOff] = (byte)word;
out[outOff + 1] = (byte)(word >>> 8);
out[outOff + 2] = (byte)(word >>> 16);
out[outOff + 3] = (byte)(word >>> 24);
}
public int doFinal(
byte[] out,
int outOff)
{
finish();
unpackWord(H1, out, outOff);
unpackWord(H2, out, outOff + 4);
unpackWord(H3, out, outOff + 8);
unpackWord(H4, out, outOff + 12);
reset();
return DIGEST_LENGTH;
}
/**
* reset the chaining variables to the IV values.
*/
public void reset()
{
super.reset();
H1 = 0x67452301;
H2 = 0xefcdab89;
H3 = 0x98badcfe;
H4 = 0x10325476;
xOff = 0;
for (int i = 0; i != X.length; i++)
{
X[i] = 0;
}
}
//
// round 1 left rotates
//
private static final int S11 = 7;
private static final int S12 = 12;
private static final int S13 = 17;
private static final int S14 = 22;
//
// round 2 left rotates
//
private static final int S21 = 5;
private static final int S22 = 9;
private static final int S23 = 14;
private static final int S24 = 20;
//
// round 3 left rotates
//
private static final int S31 = 4;
private static final int S32 = 11;
private static final int S33 = 16;
private static final int S34 = 23;
//
// round 4 left rotates
//
private static final int S41 = 6;
private static final int S42 = 10;
private static final int S43 = 15;
private static final int S44 = 21;
/*
* rotate int x left n bits.
*/
private int rotateLeft(
int x,
int n)
{
return (x << n) | (x >>> (32 - n));
}
/*
* F, G, H and I are the basic MD5 functions.
*/
private int F(
int u,
int v,
int w)
{
return (u & v) | (~u & w);
}
private int G(
int u,
int v,
int w)
{
return (u & w) | (v & ~w);
}
private int H(
int u,
int v,
int w)
{
return u ^ v ^ w;
}
private int K(
int u,
int v,
int w)
{
return v ^ (u | ~w);
}
protected void processBlock()
{
int a = H1;
int b = H2;
int c = H3;
int d = H4;
//
// Round 1 - F cycle, 16 times.
//
a = rotateLeft((a + F(b, c, d) + X[ 0] + 0xd76aa478), S11) + b;
d = rotateLeft((d + F(a, b, c) + X[ 1] + 0xe8c7b756), S12) + a;
c = rotateLeft((c + F(d, a, b) + X[ 2] + 0x242070db), S13) + d;
b = rotateLeft((b + F(c, d, a) + X[ 3] + 0xc1bdceee), S14) + c;
a = rotateLeft((a + F(b, c, d) + X[ 4] + 0xf57c0faf), S11) + b;
d = rotateLeft((d + F(a, b, c) + X[ 5] + 0x4787c62a), S12) + a;
c = rotateLeft((c + F(d, a, b) + X[ 6] + 0xa8304613), S13) + d;
b = rotateLeft((b + F(c, d, a) + X[ 7] + 0xfd469501), S14) + c;
a = rotateLeft((a + F(b, c, d) + X[ 8] + 0x698098d8), S11) + b;
d = rotateLeft((d + F(a, b, c) + X[ 9] + 0x8b44f7af), S12) + a;
c = rotateLeft((c + F(d, a, b) + X[10] + 0xffff5bb1), S13) + d;
b = rotateLeft((b + F(c, d, a) + X[11] + 0x895cd7be), S14) + c;
a = rotateLeft((a + F(b, c, d) + X[12] + 0x6b901122), S11) + b;
d = rotateLeft((d + F(a, b, c) + X[13] + 0xfd987193), S12) + a;
c = rotateLeft((c + F(d, a, b) + X[14] + 0xa679438e), S13) + d;
b = rotateLeft((b + F(c, d, a) + X[15] + 0x49b40821), S14) + c;
//
// Round 2 - G cycle, 16 times.
//
a = rotateLeft((a + G(b, c, d) + X[ 1] + 0xf61e2562), S21) + b;
d = rotateLeft((d + G(a, b, c) + X[ 6] + 0xc040b340), S22) + a;
c = rotateLeft((c + G(d, a, b) + X[11] + 0x265e5a51), S23) + d;
b = rotateLeft((b + G(c, d, a) + X[ 0] + 0xe9b6c7aa), S24) + c;
a = rotateLeft((a + G(b, c, d) + X[ 5] + 0xd62f105d), S21) + b;
d = rotateLeft((d + G(a, b, c) + X[10] + 0x02441453), S22) + a;
c = rotateLeft((c + G(d, a, b) + X[15] + 0xd8a1e681), S23) + d;
b = rotateLeft((b + G(c, d, a) + X[ 4] + 0xe7d3fbc8), S24) + c;
a = rotateLeft((a + G(b, c, d) + X[ 9] + 0x21e1cde6), S21) + b;
d = rotateLeft((d + G(a, b, c) + X[14] + 0xc33707d6), S22) + a;
c = rotateLeft((c + G(d, a, b) + X[ 3] + 0xf4d50d87), S23) + d;
b = rotateLeft((b + G(c, d, a) + X[ 8] + 0x455a14ed), S24) + c;
a = rotateLeft((a + G(b, c, d) + X[13] + 0xa9e3e905), S21) + b;
d = rotateLeft((d + G(a, b, c) + X[ 2] + 0xfcefa3f8), S22) + a;
c = rotateLeft((c + G(d, a, b) + X[ 7] + 0x676f02d9), S23) + d;
b = rotateLeft((b + G(c, d, a) + X[12] + 0x8d2a4c8a), S24) + c;
//
// Round 3 - H cycle, 16 times.
//
a = rotateLeft((a + H(b, c, d) + X[ 5] + 0xfffa3942), S31) + b;
d = rotateLeft((d + H(a, b, c) + X[ 8] + 0x8771f681), S32) + a;
c = rotateLeft((c + H(d, a, b) + X[11] + 0x6d9d6122), S33) + d;
b = rotateLeft((b + H(c, d, a) + X[14] + 0xfde5380c), S34) + c;
a = rotateLeft((a + H(b, c, d) + X[ 1] + 0xa4beea44), S31) + b;
d = rotateLeft((d + H(a, b, c) + X[ 4] + 0x4bdecfa9), S32) + a;
c = rotateLeft((c + H(d, a, b) + X[ 7] + 0xf6bb4b60), S33) + d;
b = rotateLeft((b + H(c, d, a) + X[10] + 0xbebfbc70), S34) + c;
a = rotateLeft((a + H(b, c, d) + X[13] + 0x289b7ec6), S31) + b;
d = rotateLeft((d + H(a, b, c) + X[ 0] + 0xeaa127fa), S32) + a;
c = rotateLeft((c + H(d, a, b) + X[ 3] + 0xd4ef3085), S33) + d;
b = rotateLeft((b + H(c, d, a) + X[ 6] + 0x04881d05), S34) + c;
a = rotateLeft((a + H(b, c, d) + X[ 9] + 0xd9d4d039), S31) + b;
d = rotateLeft((d + H(a, b, c) + X[12] + 0xe6db99e5), S32) + a;
c = rotateLeft((c + H(d, a, b) + X[15] + 0x1fa27cf8), S33) + d;
b = rotateLeft((b + H(c, d, a) + X[ 2] + 0xc4ac5665), S34) + c;
//
// Round 4 - K cycle, 16 times.
//
a = rotateLeft((a + K(b, c, d) + X[ 0] + 0xf4292244), S41) + b;
d = rotateLeft((d + K(a, b, c) + X[ 7] + 0x432aff97), S42) + a;
c = rotateLeft((c + K(d, a, b) + X[14] + 0xab9423a7), S43) + d;
b = rotateLeft((b + K(c, d, a) + X[ 5] + 0xfc93a039), S44) + c;
a = rotateLeft((a + K(b, c, d) + X[12] + 0x655b59c3), S41) + b;
d = rotateLeft((d + K(a, b, c) + X[ 3] + 0x8f0ccc92), S42) + a;
c = rotateLeft((c + K(d, a, b) + X[10] + 0xffeff47d), S43) + d;
b = rotateLeft((b + K(c, d, a) + X[ 1] + 0x85845dd1), S44) + c;
a = rotateLeft((a + K(b, c, d) + X[ 8] + 0x6fa87e4f), S41) + b;
d = rotateLeft((d + K(a, b, c) + X[15] + 0xfe2ce6e0), S42) + a;
c = rotateLeft((c + K(d, a, b) + X[ 6] + 0xa3014314), S43) + d;
b = rotateLeft((b + K(c, d, a) + X[13] + 0x4e0811a1), S44) + c;
a = rotateLeft((a + K(b, c, d) + X[ 4] + 0xf7537e82), S41) + b;
d = rotateLeft((d + K(a, b, c) + X[11] + 0xbd3af235), S42) + a;
c = rotateLeft((c + K(d, a, b) + X[ 2] + 0x2ad7d2bb), S43) + d;
b = rotateLeft((b + K(c, d, a) + X[ 9] + 0xeb86d391), S44) + c;
H1 += a;
H2 += b;
H3 += c;
H4 += d;
//
// reset the offset and clean out the word buffer.
//
xOff = 0;
for (int i = 0; i != X.length; i++)
{
X[i] = 0;
}
}
}

View File

@ -37,10 +37,11 @@ import java.util.Properties;
public class HMACSHA256Bench {
public static void main(String args[]) {
runTest(new I2PAppContext());
System.out.println("Running as MD5");
Properties props = new Properties();
props.setProperty("i2p.fakeHMAC", "true");
//props.setProperty("i2p.fakeHMAC", "true");
props.setProperty("i2p.HMACMD5", "true");
runTest(new I2PAppContext(props));
}
private static void runTest(I2PAppContext ctx) {
SessionKey key = ctx.keyGenerator().generateSessionKey();
@ -109,7 +110,7 @@ public class HMACSHA256Bench {
private static void display(int times, long before, long after, int len, String name) {
double rate = ((double)times)/(((double)after-(double)before)/1000.0d);
double kbps = ((double)len/1024.0d)*((double)times)/(((double)after-(double)before)/1000.0d);
System.out.println(name + " HMAC-SHA256 pulled " + kbps + "KBps or " + rate + " calcs per second");
System.out.println(name + " HMAC pulled " + kbps + "KBps or " + rate + " calcs per second");
}
}