Crypto: Add HKDF class for LS2 and NTCP2 (proposal 123)

Minor speedup in HMAC256
This commit is contained in:
zzz
2018-12-13 14:39:08 +00:00
parent 6c3c227c1b
commit 468871f21e
2 changed files with 171 additions and 12 deletions

View File

@@ -0,0 +1,137 @@
package net.i2p.crypto;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.i2p.I2PAppContext;
import net.i2p.crypto.HMAC256Generator.HMACKey;
/**
* Various flavors of HKDF using HMAC-SHA256.
* See RFC 5869.
* One or two outputs, with or without "info".
* All keys and outputs are exactly 32 bytes.
*
* @since 0.9.38
*/
public final class HKDF {
private final I2PAppContext _context;
private static final byte[] ONE = new byte[] { 1 };
/**
* Thread safe, no state, can be reused
*/
public HKDF(I2PAppContext context) {
_context = context;
}
/**
* One output, no info.
*
* @param key first 32 bytes used as the key
* @param out must be exactly 32 bytes
*/
public void calculate(byte[] key, byte[] data, byte[] out) {
HMAC256Generator hmac = _context.hmac256();
// Extract using out as a temp buffer
hmac.calculate(key, data, 0, data.length, out, 0);
// Expand
// HMAC with 0x01
// output to out
hmac.calculate(out, ONE, 0, 1, out, 0);
}
/**
* One output with info.
*
* @param key first 32 bytes used as the key
* @param info non-null ASCII, "" if none
* @param out must be exactly 32 bytes
*/
public void calculate(byte[] key, byte[] data, String info, byte[] out) {
HMAC256Generator hmac = _context.hmac256();
int ilen = info.length();
// Extract using out as a temp buffer
hmac.calculate(key, data, 0, data.length, out, 0);
byte[] tmp = new byte[ilen + 1];
for (int i = 0; i < ilen; i++) {
tmp[i] = (byte)info.charAt(i);
}
tmp[ilen] = 1;
// Expand
// HMAC with info and 0x01
// output to out
hmac.calculate(out, tmp, 0, tmp.length, out, 0);
}
/**
* Two outputs, no info.
*
* @param key first 32 bytes used as the key
* @param out 32 bytes will be copied to here
* @param out2 32 bytes will be copied to here, may be the same as out
* @param off2 offset for copy to out2
*/
public void calculate(byte[] key, byte[] data, byte[] out,
byte[] out2, int off2) {
calculate(key, data, "", out, out2, off2);
}
/**
* Two outputs with info.
*
* @param key first 32 bytes used as the key
* @param info non-null ASCII, "" if none
* @param out 32 bytes will be copied to here
* @param out2 32 bytes will be copied to here, may be the same as out
* @param off2 offset for copy to out2
*/
public void calculate(byte[] key, byte[] data, String info, byte[] out,
byte[] out2, int off2) {
int ilen = info.length();
byte[] tmp = new byte[ilen + 1];
for (int i = 0; i < ilen; i++) {
tmp[i] = (byte)info.charAt(i);
}
tmp[ilen] = 1;
try {
// here we use Java Mac directly rather than
// HMAC256Generator for efficiency.
Mac mac = Mac.getInstance("HmacSHA256");
// SecretKeySpec copies the data, HMACKey doesn't
SecretKey keyObj = new HMACKey(key);
mac.init(keyObj);
mac.update(data);
// Extract to out as a tmp
mac.doFinal(out, 0);
// PRK
// SecretKeySpec copies the data, HMACKey doesn't
keyObj = new SecretKeySpec(out, 0, 32, "HmacSHA256");
mac.init(keyObj);
// Expand
// HMAC 1 with info and 0x01
// output 1 to out
mac.update(tmp, 0, ilen + 1);
mac.doFinal(out, 0);
tmp[ilen] = 2;
// HMAC 2 with output 1, info, and 0x02
// output 2 to out2
mac.reset();
mac.update(out, 0, 32);
mac.update(tmp, 0, ilen + 1);
mac.doFinal(out2, off2);
} catch (NoSuchAlgorithmException e) {
throw new UnsupportedOperationException("HmacSHA256", e);
} catch (GeneralSecurityException e) {
throw new IllegalArgumentException("HmacSHA256", e);
} finally {
// we could re-init the mac with a zero key
// no way to zero out the SecretKeySpec though
}
}
}

View File

@@ -1,10 +1,11 @@
package net.i2p.crypto;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import net.i2p.I2PAppContext;
@@ -70,7 +71,7 @@ public final class HMAC256Generator extends HMACGenerator {
* Calculate the HMAC of the data with the given key.
* Outputs 32 bytes to target starting at targetOffset.
*
* @param key 32 bytes
* @param key first 32 bytes used as the key
* @throws UnsupportedOperationException if the JVM does not support it
* @throws IllegalArgumentException for bad key or target too small
* @since 0.9.38
@@ -78,7 +79,7 @@ public final class HMAC256Generator extends HMACGenerator {
public void calculate(byte[] key, byte data[], int offset, int length, byte target[], int targetOffset) {
try {
Mac mac = Mac.getInstance("HmacSHA256");
Key keyObj = new SecretKeySpec(key, "HmacSHA256");
SecretKey keyObj = new HMACKey(key);
mac.init(keyObj);
mac.update(data, offset, length);
mac.doFinal(target, targetOffset);
@@ -111,6 +112,24 @@ public final class HMAC256Generator extends HMACGenerator {
return eq;
}
/**
* Like SecretKeySpec but doesn't copy the key in the construtor, for speed.
* It still returns a copy in getEncoded(), because Mac relies
* on that, doesn't work otherwise.
* First 32 bytes are returned in getEncoded(), data may be longer.
*
* @since 0.9.38
*/
static final class HMACKey implements SecretKey {
private final byte[] _data;
public HMACKey(byte[] data) { _data = data; }
public String getAlgorithm() { return "HmacSHA256"; }
public byte[] getEncoded() { return Arrays.copyOf(_data, 32); }
public String getFormat() { return "RAW"; }
}
/******
private static class Sha256ForMAC extends Sha256Standalone implements Digest {
public String getAlgorithmName() { return "sha256 for hmac"; }
@@ -150,14 +169,14 @@ public final class HMAC256Generator extends HMACGenerator {
I2PAppContext ctx = I2PAppContext.getGlobalContext();
byte[] rand = new byte[32];
byte[] data = new byte[LENGTH];
Key keyObj = new SecretKeySpec(rand, "HmacSHA256");
SecretKey keyObj = null;
SessionKey key = new SessionKey(rand);
HMAC256Generator gen = new HMAC256Generator(I2PAppContext.getGlobalContext());
byte[] result = new byte[32];
javax.crypto.Mac mac;
Mac mac;
try {
mac = javax.crypto.Mac.getInstance("HmacSHA256");
mac = Mac.getInstance("HmacSHA256");
} catch (NoSuchAlgorithmException e) {
System.err.println("Fatal: " + e);
return;
@@ -172,8 +191,7 @@ public final class HMAC256Generator extends HMACGenerator {
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);
gen.calculate(rand, data, 0, data.length, result, 0);
try {
mac.init(keyObj);
} catch (GeneralSecurityException e) {
@@ -181,17 +199,21 @@ public final class HMAC256Generator extends HMACGenerator {
return;
}
byte[] result2 = mac.doFinal(data);
if (!DataHelper.eq(result, result2))
throw new IllegalStateException();
if (!DataHelper.eq(result, result2)) {
throw new IllegalStateException("Mismatch on run " + i + ": result1:\n" +
net.i2p.util.HexDump.dump(result) +
"result1:\n" +
net.i2p.util.HexDump.dump(result2));
}
}
// real thing
System.out.println("Passed");
System.out.println("BC Test:");
RUNS = 500000;
RUNS = 1000000;
long start = System.currentTimeMillis();
for (int i = 0; i < RUNS; i++) {
gen.calculate(key, data, 0, data.length, result, 0);
gen.calculate(rand, data, 0, data.length, result, 0);
}
long time = System.currentTimeMillis() - start;
System.out.println("Time for " + RUNS + " HMAC-SHA256 computations:");