forked from I2P_Developers/i2p.i2p
Crypto: Add HKDF class for LS2 and NTCP2 (proposal 123)
Minor speedup in HMAC256
This commit is contained in:
137
core/java/src/net/i2p/crypto/HKDF.java
Normal file
137
core/java/src/net/i2p/crypto/HKDF.java
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -1,10 +1,11 @@
|
|||||||
package net.i2p.crypto;
|
package net.i2p.crypto;
|
||||||
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.Key;
|
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.Arrays;
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
import net.i2p.I2PAppContext;
|
import net.i2p.I2PAppContext;
|
||||||
@@ -70,7 +71,7 @@ public final class HMAC256Generator extends HMACGenerator {
|
|||||||
* Calculate the HMAC of the data with the given key.
|
* Calculate the HMAC of the data with the given key.
|
||||||
* Outputs 32 bytes to target starting at targetOffset.
|
* 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 UnsupportedOperationException if the JVM does not support it
|
||||||
* @throws IllegalArgumentException for bad key or target too small
|
* @throws IllegalArgumentException for bad key or target too small
|
||||||
* @since 0.9.38
|
* @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) {
|
public void calculate(byte[] key, byte data[], int offset, int length, byte target[], int targetOffset) {
|
||||||
try {
|
try {
|
||||||
Mac mac = Mac.getInstance("HmacSHA256");
|
Mac mac = Mac.getInstance("HmacSHA256");
|
||||||
Key keyObj = new SecretKeySpec(key, "HmacSHA256");
|
SecretKey keyObj = new HMACKey(key);
|
||||||
mac.init(keyObj);
|
mac.init(keyObj);
|
||||||
mac.update(data, offset, length);
|
mac.update(data, offset, length);
|
||||||
mac.doFinal(target, targetOffset);
|
mac.doFinal(target, targetOffset);
|
||||||
@@ -111,6 +112,24 @@ public final class HMAC256Generator extends HMACGenerator {
|
|||||||
return eq;
|
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 {
|
private static class Sha256ForMAC extends Sha256Standalone implements Digest {
|
||||||
public String getAlgorithmName() { return "sha256 for hmac"; }
|
public String getAlgorithmName() { return "sha256 for hmac"; }
|
||||||
@@ -150,14 +169,14 @@ public final class HMAC256Generator extends HMACGenerator {
|
|||||||
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
I2PAppContext ctx = I2PAppContext.getGlobalContext();
|
||||||
byte[] rand = new byte[32];
|
byte[] rand = new byte[32];
|
||||||
byte[] data = new byte[LENGTH];
|
byte[] data = new byte[LENGTH];
|
||||||
Key keyObj = new SecretKeySpec(rand, "HmacSHA256");
|
SecretKey keyObj = null;
|
||||||
SessionKey key = new SessionKey(rand);
|
SessionKey key = new SessionKey(rand);
|
||||||
|
|
||||||
HMAC256Generator gen = new HMAC256Generator(I2PAppContext.getGlobalContext());
|
HMAC256Generator gen = new HMAC256Generator(I2PAppContext.getGlobalContext());
|
||||||
byte[] result = new byte[32];
|
byte[] result = new byte[32];
|
||||||
javax.crypto.Mac mac;
|
Mac mac;
|
||||||
try {
|
try {
|
||||||
mac = javax.crypto.Mac.getInstance("HmacSHA256");
|
mac = Mac.getInstance("HmacSHA256");
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
System.err.println("Fatal: " + e);
|
System.err.println("Fatal: " + e);
|
||||||
return;
|
return;
|
||||||
@@ -172,8 +191,7 @@ public final class HMAC256Generator extends HMACGenerator {
|
|||||||
byte[] keyBytes = keyObj.getEncoded();
|
byte[] keyBytes = keyObj.getEncoded();
|
||||||
if (!DataHelper.eq(rand, keyBytes))
|
if (!DataHelper.eq(rand, keyBytes))
|
||||||
System.out.println("secret key in != out");
|
System.out.println("secret key in != out");
|
||||||
key = new SessionKey(rand);
|
gen.calculate(rand, data, 0, data.length, result, 0);
|
||||||
gen.calculate(key, data, 0, data.length, result, 0);
|
|
||||||
try {
|
try {
|
||||||
mac.init(keyObj);
|
mac.init(keyObj);
|
||||||
} catch (GeneralSecurityException e) {
|
} catch (GeneralSecurityException e) {
|
||||||
@@ -181,17 +199,21 @@ public final class HMAC256Generator extends HMACGenerator {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte[] result2 = mac.doFinal(data);
|
byte[] result2 = mac.doFinal(data);
|
||||||
if (!DataHelper.eq(result, result2))
|
if (!DataHelper.eq(result, result2)) {
|
||||||
throw new IllegalStateException();
|
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
|
// real thing
|
||||||
System.out.println("Passed");
|
System.out.println("Passed");
|
||||||
System.out.println("BC Test:");
|
System.out.println("BC Test:");
|
||||||
RUNS = 500000;
|
RUNS = 1000000;
|
||||||
long start = System.currentTimeMillis();
|
long start = System.currentTimeMillis();
|
||||||
for (int i = 0; i < RUNS; i++) {
|
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;
|
long time = System.currentTimeMillis() - start;
|
||||||
System.out.println("Time for " + RUNS + " HMAC-SHA256 computations:");
|
System.out.println("Time for " + RUNS + " HMAC-SHA256 computations:");
|
||||||
|
Reference in New Issue
Block a user