diff --git a/core/java/src/net/i2p/crypto/HKDF.java b/core/java/src/net/i2p/crypto/HKDF.java new file mode 100644 index 000000000..241b3d165 --- /dev/null +++ b/core/java/src/net/i2p/crypto/HKDF.java @@ -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 + } + } +} diff --git a/core/java/src/net/i2p/crypto/HMAC256Generator.java b/core/java/src/net/i2p/crypto/HMAC256Generator.java index 93dc724de..e36b3ea85 100644 --- a/core/java/src/net/i2p/crypto/HMAC256Generator.java +++ b/core/java/src/net/i2p/crypto/HMAC256Generator.java @@ -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:");