JEP 478: Key Derivation Function API (Preview) | 密钥派生函数 API(预览)
概述
引入一个用于密钥派生函数(KDFs)的 API,这些是用于从秘密密钥和其他数据中派生额外密钥的加密算法。这是一个 预览 API。
目标
使应用程序能够使用如基于 HMAC 的提取和扩展密钥派生函数(HKDF, RFC 5869)和 Argon2(RFC 9106)等 KDF 算法。
使 KDF 能够在诸如 ML-KEM、TLS 1.3 中的混合密钥交换等高级协议以及混合公钥加密(HPKE, RFC 9180)等加密方案中的密钥封装机制(KEM, JEP 452)实现中使用。
允许安全提供者以 Java 代码或本地代码实现 KDF 算法。
包含 HKDF 的实现,并引入额外的特定于 HKDF 的 API。
非目标
不打算通过新的 KDF API 提供基于密码的密钥派生函数 PBKDF1 和 PBKDF2 的现有实现。这些实现将继续通过现有的
SecretKeyFactory
API 提供。对 JDK 的 TLS 实现中使用的密钥派生函数进行重构以使用新的 KDF API 也不是目标。不过,在后续工作中可能会这样做。
动机
密钥派生函数 (KDFs) 利用加密输入(如初始密钥材料、盐值和伪随机函数)创建新的加密强键材料。KDF 通常用于创建可以从其获得多个密钥的加密数据。KDF 允许双方在共享输入知识的情况下以既安全又可重复的方式创建密钥。
派生密钥类似于哈希密码。KDF 使用带密钥的哈希以及来自其他输入的额外熵来提取新的密钥材料或将值安全地扩展到更大的密钥材料流中。
随着量子计算的到来,经典加密算法将越来越容易受到实际攻击。因此,对于 Java 平台来说,支持对这些攻击具有抵抗力的 后量子加密 (PQC) 变得至关重要。我们的最终目标是通过支持混合公钥加密(HPKE),使其能够平滑过渡到量子安全加密算法来实现这一目标。集成在 JDK 21 中的 KEM API(JEP 452) 是 HPKE 的一个构建块,也是我们迈向 HPKE 和后量子准备的第一步;这里提议的 KDF API 是 HPKE 的另一个构建块,将是第二步。
添加 KDF API 所带来的可扩展性将带来额外的好处。多年来,针对加密硬件设备的 PKCS#11 标准 已经描述了 KDF 支持。通过 javax.crypto
API 提供这项技术将有利于与这些设备交互的应用程序和库。此外,第三方加密提供商的开发者可以提供自定义的 KDF 实现,特别是那些经过测试并随后由 国家标准与技术研究院 认证的实现。
最后,Java 平台必须为比 PBKDF1 和 PBKDF2 更复杂的密码散列 KDF(如 Argon2)提供更好的 API 支持。Java 平台中现有的加密 API 目前都无法自然地表示 KDF(见 下文)。第三方安全提供商的实现者已经表达了对标准 KDF API 的需求。
描述
一个密钥派生函数有两个基本操作:
实例化和初始化,这会创建 KDF 并用适当的参数初始化它,以及
派生,接受密钥材料和其他可选输入以及描述输出的参数,然后生成派生的密钥或数据。
我们定义了一个新类,javax.crypto.KDF
,来表示密钥派生函数。
这是一个 预览 API,默认情况下禁用
要在 JDK 24 中使用这个新 API,你必须启用预览功能:
使用
javac --release 24 --enable-preview Main.java
编译程序并使用java --enable-preview Main
运行它;或者,当使用 源代码启动器 时,使用
java --enable-preview Main.java
运行程序;或者,当使用
jshell
时,使用jshell --enable-preview
启动它。
实例化和初始化
KDF
类提供了通常的 getInstance
方法组合,参数包括可选的 KDFParameters
和加密提供者名称。getInstance
方法既实例化一个 KDF
也初始化其算法。
目前我们唯一打算包含的 KDF 算法是 HKDF,它不需要 KDFParameters
对象。然而,其他算法,如 SHAKE,可能需要它。
派生
KDF
类定义了两种派生密钥的方法:
deriveKey(String alg, AlgorithmParameterSpec spec)
使用指定的算法并根据指定的参数构造一个SecretKey
对象。deriveData(AlgorithmParameterSpec spec)
返回一个字节数组,可用于熵或在需要该形式的密钥材料时使用。
我们使用空接口 AlgorithmParameterSpec
,因为不同的 KDF 算法需要不同的参数。一个 KDF
实现应该定义一个或多个 AlgorithmParameterSpec
子类来描述其参数。
对于包含的 HKDF 实现,我们提供了三个这样的子类,每个代表不同操作模式的输入:
包含的 HKDFParameterSpec
接口为创建这三个类的实例定义了静态工厂方法。它还定义了一个构建器类 HKDFParameterSpec.Builder
,用于组装提取操作的关键材料。
示例
// 为指定算法创建一个 KDF 对象
KDF hkdf = KDF.getInstance("HKDF-SHA256");
// 创建一个 ExtractExpand 参数规范
AlgorithmParameterSpec params =
HKDFParameterSpec.ofExtract()
.addIKM(initialKeyMaterial)
.addSalt(salt).thenExpand(info, 32);
// 派生一个 32 字节的 AES 密钥
SecretKey key = hkdf.deriveKey("AES", params);
// 可以使用相同的 KDF 对象进行额外的 deriveKey 调用
实现 KDF 提供者
一个 KDF
实现必须扩展抽象类 javax.crypto.KDFSpi
。
一些 KDF 算法在一个派生操作中派生出多个加密密钥。如果你正在实现这样一个算法,我们建议你提供一个 SecretKey
的子类,其中包含访问每个密钥的方法,或者返回所有密钥在一个字节数组中,并记录如何分离它们。
未来工作
重构 TLS 1.3 —— 我们计划增强 JDK 的 TLS 1.3 实现,通过 KDF API 使用 HKDF 派生密钥。届时,我们将添加新的功能测试,以展示通过新 API 检索 KDF 实现的有效性。
最初,我们并不打算对 TLS 1.3 之前的版本进行此操作。
实现 Argon2 —— 我们最终打算实现 Argon2 密码哈希 KDF。
替代方案
使用现有 API —— 我们考虑使用现有的
KeyGenerator
和SecretKeyFactory
API 表示 KDF。早期的密钥派生算法如 TLS-PRF、PBKDF1 和 PBKDF2 已经被适应到这些 API 中,但总体上,这些 API 不适合 KDF。KeyGenerator
围绕引入熵设计,通过SecureRandom
对象从一组输入中创建非确定性的密钥。相比之下,KDF 支持两个独立方独立地派生相同的密钥材料。SecretKeyFactory
设计用于创建单个密钥。虽然存在 KDF 可以这种方式使用的场景,KDF 还需要支持从密钥流中按确定性方式连续派生。
使 PBKDF2 可通过新的
KDF
API 获得 —— PBKDF2 正迅速被更强的算法如 Argon2 取代。已经使用 PBKDF2 的开发者可能会继续通过SecretKeyFactory
API 使用它,而不是重构现有代码使用KDF
API。因此,目前没有必要通过新 API 提供 PBKDF2,但如果出现迫切需求,我们可以稍后这样做。
测试
我们将添加 RFC 5869 已知答案(KAT)测试(如果可用),以及异常处理和 SSL/TLS 回归测试。