- Replace BC MD5 with JVM version, refactor I2PHMAC to use
     MessageDigest instead of BC Digest (ticket #1189)
   - Use JVM HmacSHA256 instead of I2PHMAC for Syndie since it is standard
This commit is contained in:
zzz
2014-02-17 12:03:22 +00:00
parent 5542406f3d
commit 18cbf3d253
10 changed files with 262 additions and 597 deletions

View File

@@ -1,36 +1,101 @@
package net.i2p.crypto;
import gnu.crypto.hash.Sha256Standalone;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.spec.SecretKeySpec;
import net.i2p.I2PAppContext;
import net.i2p.data.DataHelper;
import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import org.bouncycastle.oldcrypto.Digest;
import org.bouncycastle.oldcrypto.macs.I2PHMac;
/**
* Calculate the HMAC-SHA256 of a key+message. All the good stuff occurs
* in {@link org.bouncycastle.oldcrypto.macs.I2PHMac} and
* {@link gnu.crypto.hash.Sha256Standalone}.
* Calculate the HMAC-SHA256 of a key+message.
* This is compatible with javax.crypto.Mac.getInstance("HmacSHA256").
*
* This should be compatible with javax.crypto.Mac.getInstance("HmacSHA256")
* but that is untested.
* As of 0.9.12, uses javax.crypto.Mac.
*
* deprecated used only by syndie
* Deprecated, used only by Syndie.
*/
public class HMAC256Generator extends HMACGenerator {
/**
* @param context unused
*/
public HMAC256Generator(I2PAppContext context) { super(context); }
/**
* @deprecated unused (not even by Syndie)
* @throws UnsupportedOperationException since 0.9.12
*/
@Override
protected I2PHMac acquire() {
I2PHMac rv = _available.poll();
if (rv != null)
return rv;
// the HMAC is hardcoded to use SHA256 digest size
// for backwards compatability. next time we have a backwards
// incompatible change, we should update this by removing ", 32"
return new I2PHMac(new Sha256ForMAC());
throw new UnsupportedOperationException();
}
/**
* Calculate the HMAC of the data with the given key
*
* @return the first 16 bytes contain the HMAC, the last 16 bytes are zero
* @deprecated unused (not even by Syndie)
* @throws UnsupportedOperationException always
* @since 0.9.12 overrides HMACGenerator
*/
@Override
public Hash calculate(SessionKey key, byte data[]) {
throw new UnsupportedOperationException();
}
/**
* Calculate the HMAC of the data with the given key.
* Outputs 32 bytes to target starting at targetOffset.
*
* @throws UnsupportedOperationException if the JVM does not support it
* @throws IllegalArgumentException for bad key or target too small
* @since 0.9.12 overrides HMACGenerator
*/
@Override
public void calculate(SessionKey key, byte data[], int offset, int length, byte target[], int targetOffset) {
try {
javax.crypto.Mac mac = javax.crypto.Mac.getInstance("HmacSHA256");
Key keyObj = new SecretKeySpec(key.getData(), "HmacSHA256");
mac.init(keyObj);
mac.update(data, offset, length);
mac.doFinal(target, targetOffset);
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("HmacSHA256", e);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("HmacSHA256", e);
}
}
/**
* Verify the MAC inline, reducing some unnecessary memory churn.
*
* @param key session key to verify the MAC with
* @param curData MAC to verify
* @param curOffset index into curData to MAC
* @param curLength how much data in curData do we want to run the HMAC over
* @param origMAC what do we expect the MAC of curData to equal
* @param origMACOffset index into origMAC
* @param origMACLength how much of the MAC do we want to verify, use 32 for HMAC256
* @since 0.9.12 overrides HMACGenerator
*/
@Override
public boolean verify(SessionKey key, byte curData[], int curOffset, int curLength,
byte origMAC[], int origMACOffset, int origMACLength) {
byte calc[] = acquireTmp();
calculate(key, curData, curOffset, curLength, calc, 0);
boolean eq = DataHelper.eq(calc, 0, origMAC, origMACOffset, origMACLength);
releaseTmp(calc);
return eq;
}
/******
private static class Sha256ForMAC extends Sha256Standalone implements Digest {
public String getAlgorithmName() { return "sha256 for hmac"; }
public int getDigestSize() { return 32; }
@@ -43,7 +108,6 @@ public class HMAC256Generator extends HMACGenerator {
}
/******
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte data[] = new byte[64];
@@ -53,4 +117,81 @@ public class HMAC256Generator extends HMACGenerator {
System.out.println(Base64.encode(mac.getData()));
}
******/
/**
* Test the BC and the JVM's implementations for speed
*
* Results on 2012 hexcore box, OpenJDK 7:
* BC 9275 ms (before converting to MessageDigest)
* BC 8500 ms (after converting to MessageDigest)
* JVM 8065 ms
*
*/
/****
public static void main(String args[]) {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte[] rand = new byte[32];
byte[] data = new byte[1500];
Key keyObj = new SecretKeySpec(rand, "HmacSHA256");
SessionKey key = new SessionKey(rand);
HMAC256Generator gen = new HMAC256Generator(I2PAppContext.getGlobalContext());
byte[] result = new byte[32];
javax.crypto.Mac mac;
try {
mac = javax.crypto.Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
System.err.println("Fatal: " + e);
return;
}
// warmup and comparison
System.out.println("Warmup and comparison:");
int RUNS = 25000;
for (int i = 0; i < RUNS; i++) {
ctx.random().nextBytes(rand);
ctx.random().nextBytes(data);
keyObj = new SecretKeySpec(rand, "HmacSHA256");
byte[] keyBytes = keyObj.getEncoded();
if (!DataHelper.eq(rand, keyBytes))
System.out.println("secret key in != out");
key = new SessionKey(rand);
gen.calculate(key, data, 0, data.length, result, 0);
try {
mac.init(keyObj);
} catch (GeneralSecurityException e) {
System.err.println("Fatal: " + e);
return;
}
byte[] result2 = mac.doFinal(data);
if (!DataHelper.eq(result, result2))
throw new IllegalStateException();
}
// real thing
System.out.println("Passed");
System.out.println("BC Test:");
RUNS = 500000;
long start = System.currentTimeMillis();
for (int i = 0; i < RUNS; i++) {
gen.calculate(key, data, 0, data.length, result, 0);
}
long time = System.currentTimeMillis() - start;
System.out.println("Time for " + RUNS + " HMAC-SHA256 computations:");
System.out.println("BC time (ms): " + time);
System.out.println("JVM Test:");
start = System.currentTimeMillis();
for (int i = 0; i < RUNS; i++) {
try {
mac.init(keyObj);
} catch (GeneralSecurityException e) {
System.err.println("Fatal: " + e);
}
byte[] sha = mac.doFinal(data);
}
time = System.currentTimeMillis() - start;
System.out.println("JVM time (ms): " + time);
}
****/
}

View File

@@ -1,5 +1,7 @@
package net.i2p.crypto;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.concurrent.LinkedBlockingQueue;
@@ -16,7 +18,6 @@ import net.i2p.data.Hash;
import net.i2p.data.SessionKey;
import net.i2p.util.SimpleByteCache;
import org.bouncycastle.oldcrypto.digests.MD5Digest;
import org.bouncycastle.oldcrypto.macs.I2PHMac;
/**
@@ -56,7 +57,7 @@ public class HMACGenerator {
* Calculate the HMAC of the data with the given key
*
* @return the first 16 bytes contain the HMAC, the last 16 bytes are zero
* @deprecated unused
* @deprecated unused (not even by Syndie)
*/
public Hash calculate(SessionKey key, byte data[]) {
if ((key == null) || (key.getData() == null) || (data == null))
@@ -69,6 +70,9 @@ public class HMACGenerator {
/**
* Calculate the HMAC of the data with the given key
*
* @return the first 16 bytes contain the HMAC, the last 16 bytes are zero
* @throws IllegalArgumentException for bad key or target too small
*/
public void calculate(SessionKey key, byte data[], int offset, int length, byte target[], int targetOffset) {
if ((key == null) || (key.getData() == null) || (data == null))
@@ -91,8 +95,10 @@ public class HMACGenerator {
* @param origMAC what do we expect the MAC of curData to equal
* @param origMACOffset index into origMAC
* @param origMACLength how much of the MAC do we want to verify
* @throws IllegalArgumentException for bad key
*/
public boolean verify(SessionKey key, byte curData[], int curOffset, int curLength, byte origMAC[], int origMACOffset, int origMACLength) {
public boolean verify(SessionKey key, byte curData[], int curOffset, int curLength,
byte origMAC[], int origMACOffset, int origMACLength) {
if ((key == null) || (key.getData() == null) || (curData == null))
throw new NullPointerException("Null arguments for HMAC");
@@ -116,7 +122,12 @@ public class HMACGenerator {
// for backwards compatability. next time we have a backwards
// incompatible change, we should update this by removing ", 32"
// SEE NOTES ABOVE
return new I2PHMac(new MD5Digest(), 32);
try {
MessageDigest md = MessageDigest.getInstance("MD5");
return new I2PHMac(md, 32);
} catch (NoSuchAlgorithmException nsae) {
throw new UnsupportedOperationException("MD5");
}
}
private void release(I2PHMac mac) {
@@ -124,15 +135,15 @@ public class HMACGenerator {
}
/**
* Not really tmp, just from the byte array cache.
* 32 bytes from the byte array cache.
* Does NOT zero.
*/
private byte[] acquireTmp() {
protected byte[] acquireTmp() {
byte rv[] = SimpleByteCache.acquire(Hash.HASH_LENGTH);
return rv;
}
private void releaseTmp(byte tmp[]) {
protected void releaseTmp(byte tmp[]) {
SimpleByteCache.release(tmp);
}

View File

@@ -9,6 +9,8 @@ import net.i2p.data.Base64;
import net.i2p.data.DataHelper;
import net.i2p.data.SessionKey;
//import org.bouncycastle.oldcrypto.digests.MD5Digest;
/**
* Manage both plaintext and salted/hashed password storage in
* router.config.
@@ -197,4 +199,43 @@ public class PasswordManager {
} catch (NoSuchAlgorithmException nsae) {}
return null;
}
/**
* speed/comparison test before removing BC version;
* JVM was slightly faster
*/
/*****
public static void main(String[] args) {
RandomSource rand = RandomSource.getInstance();
byte[] d = new byte[1500];
MD5Digest md = new MD5Digest();
byte[] bc = new byte[16];
// warmup and comparison
int runs = 25000;
for (int i = 0; i < runs; i++) {
rand.nextBytes(d);
byte[] jvm = md5Sum(d);
md.update(d, 0, d.length);
md.doFinal(bc, 0);
if (!DataHelper.eq(jvm, bc))
throw new IllegalStateException();
md.reset();
}
// real thing
runs = 500000;
long start = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
md5Sum(d);
}
System.out.println("JVM " + (System.currentTimeMillis() - start));
start = System.currentTimeMillis();
for (int i = 0; i < runs; i++) {
md.update(d, 0, d.length);
md.doFinal(bc, 0);
md.reset();
}
System.out.println("BC " + (System.currentTimeMillis() - start));
}
*****/
}