HKDF 在 Java 中的实现
No, 基于哈希消息认证码 (HMAC) 的密钥派生函数 (HKDF) https://eprint.iacr.org/2010/264.pdf与大多数 KDF 一样,JCA 中没有标准实现(截至 2020 年)。
其他项目中嵌入了一些实现(就像您已经说过的):
- mozilla 服务/同步加密 https://github.com/mozilla-services/sync-crypto/blob/master/src/main/java/org/mozilla/android/sync/crypto/HKDF.java
- WhisperSystems/libsignal-protocol-java https://github.com/WhisperSystems/libsignal-protocol-java/blob/master/java/src/main/java/org/whispersystems/libsignal/kdf/HKDF.java
- 广场/keywhiz https://github.com/square/keywhiz/blob/master/hkdf/src/main/java/keywhiz/hkdf/Hkdf.java
当然还有,充气城堡 https://github.com/bcgit/bc-java/blob/master/core/src/main/java/org/bouncycastle/crypto/generators/HKDFBytesGenerator.java它们使用自己的 Hmac/Mac 实现和 API。然而,BC 是一个巨大的依赖性,并且对于例如嵌入式或移动用例。为此我实施了一个独立的,轻量级 java lib(通过所有 RFC 5869 测试向量),适用于任何javax.crypto.Mac https://docs.oracle.com/javase/7/docs/api/javax/crypto/Mac.html实例:
- https://github.com/patrickfav/hkdf https://github.com/patrickfav/hkdf
如果您愿意,当然可以自己实现它,当使用内置 JCA Hmac 实现时,这是一个相当简单的规范。
HKDF 中的信息参数
来自RFC 5869 https://www.rfc-editor.org/rfc/rfc5869#section-3.2:
虽然“info”值在 HKDF 的定义中是可选的,但它是
在应用中通常非常重要。其主要目标是
将派生的密钥材料绑定到特定于应用程序和上下文的
信息。 (...) 特别是,它可能会阻止推导
对于不同的上下文使用相同的密钥材料。
例如,如果您想从相同的源材料中导出密钥和 IV,您可以使用 info 参数(使用这个库 https://github.com/patrickfav/hkdf):
//example input
String userInput = "this is a user input with bad entropy";
HKDF hkdf = HKDF.fromHmacSha256();
//extract the "raw" data to create output with concentrated entropy
byte[] pseudoRandomKey = hkdf.extract(staticSalt32Byte, userInput.getBytes(StandardCharsets.UTF_8));
//create expanded bytes for e.g. AES secret key and IV
byte[] expandedAesKey = hkdf.expand(pseudoRandomKey, "aes-key".getBytes(StandardCharsets.UTF_8), 16);
byte[] expandedIv = hkdf.expand(pseudoRandomKey, "aes-iv".getBytes(StandardCharsets.UTF_8), 16);
//Example boilerplate encrypting a simple string with created key/iv
SecretKey key = new SecretKeySpec(expandedAesKey, "AES"); //AES-128 key
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(expandedIv));
byte[] encrypted = cipher.doFinal("my secret message".getBytes(StandardCharsets.UTF_8));