密钥派生最佳实践
密钥派生在众多密码学应用中至关重要,包括密钥交换、密钥管理、安全通信以及构建健壮的密码学原语。但这也容易出错:尽管存在满足不同密钥派生需求的标准工具,我们的审计经常发现这些工具的不当使用可能会危及密钥安全。Flickr的API签名伪造漏洞就是在密钥派生过程中误用哈希函数的著名案例。
这些误用表明对密钥派生函数(KDF)可能存在潜在误解。本文涵盖使用KDF的最佳实践,包括需要谨慎处理密钥派生以实现所需安全特性的专业场景。在此过程中,我们为回答常见问题提供建议,例如:
- 我是否需要向HKDF添加额外随机性?
- 我应该对HKDF使用盐吗?
- 我应该使用不同的盐从HKDF派生多个密钥吗?
- 如何组合多个密钥材料源?
在深入探讨密钥派生最佳实践之前,我们将回顾一些重要概念以帮助更好地理解它们。
密钥材料源
密钥密码学原语(如AEAD)需要满足特定要求的密钥材料来保证安全性。在大多数情况下,原语要求密钥是均匀随机生成的或在密码学上接近均匀随机。我们将区分四种类型的密钥材料:
- (均匀)随机,例如使用操作系统CSPRNG生成的32字节
- 非均匀但高熵,例如密钥交换的输出
- 低熵,例如密码和其他容易猜测的值
- 多个源的集合,例如前量子和后量子共享秘密
上述最后一类与当前抗量子密码学的发展特别相关。结合经典和后量子密钥交换的混合密钥交换协议旨在防范"现在存储以后解密"攻击。
密钥派生工作原理
密钥派生是从某些初始密钥材料(IKM)生成适用于密码学使用的可接受密钥材料的过程。从密码学角度来看,“可接受"通常意味着从所有可能密钥的集合中均匀随机选择,或与真正随机密钥不可区分。有两个主要的密钥派生任务与初始密钥材料的性质相关:
随机性提取从具有"足够随机性"的IKM中提取密码学密钥。随机性提取可选择使用盐。自然地,我们可以将随机性提取应用于已经密码学适当的密钥。
随机性扩展从密码学密钥派生子密钥。扩展通常使用对每个子密钥唯一的"上下文"或"信息"输入。
这种分类深受广泛使用的KDF算法HKDF的影响;其他KDF设计不一定遵循相同原则。然而,提取和扩展在大多数KDF应用中得到很好体现。此外,我们将考虑与复杂密钥材料源相关的额外KDF任务,例如一组源。
提取和扩展:HKDF简要介绍
提示:如果您更喜欢HKDF的可视化演示,请参考下面的动画。
HKDF旨在同时提供提取和扩展功能。HKDF通常通过API(如HKDF(ikm, salt, info, key_len))向应用程序提供。然而,在底层会发生以下情况:首先,提取过程从IKM和盐生成伪随机密钥(PRK):prk = HKDF.Extract(ikm, salt) = HMAC(salt, ikm)。然后,生成长度为key_len的子密钥:sub_key = feedback[HMAC](prk, info)。这里,feedback[HMAC]是HMAC的包装器,通过重复调用HMAC生成所需长度的输出;换句话说,它实现了可变长度的伪随机函数。对于给定密钥,feedback将为每个新的信息输入返回所需长度的随机位串;固定的信息值将始终产生相同的输出。如果信息保持不变但长度可变,较小的输出将是较长输出的前缀。
关于提取盐:HKDF的提取阶段可选择接受盐。提取盐是用于从密钥材料中提取足够随机性的随机、非秘密值。关键的是,盐不能被攻击者控制,因为这对于KDF通常可能导致灾难性后果。Hugo Krawczyk提供了攻击者控制盐破坏盐和IKM之间独立性的理论示例,导致弱提取器构造。然而,后果也可能具有实际相关性,正如我们在下一节讨论的那样。许多应用程序(例如,除认证密钥交换外)的一个典型痛点是对盐进行认证。因此,HKDF标准建议大多数应用程序使用常量,例如全零字节串。不使用盐的代价是对HMAC做出稍强但仍合理的假设。
解决KDF误用问题
开发人员在选择KDF时必须考虑几个问题,但对KDF的误解可能导致引入安全问题的选择。下面,我们提供误用示例以及最佳实践,以帮助避免KDF的不当使用。
我应该使用不同的盐来派生多个子密钥吗?
使用上述KDF抽象,子密钥生成更适合随机性扩展。给定伪随机密钥(可能在提取步骤后获得),可以使用对每个子密钥唯一的信息输入通过随机性扩展获得子密钥。盐用于提取。此外,如上所述,攻击者控制的盐可能对安全性有害。考虑一个按需生成用户密钥的密钥管理应用程序。一个实现可能决定使用用户名作为盐从主密钥派生密钥。除了自由选择用户名外,用户可能提供上下文字符串(例如"文件加密密钥”)来指示密钥的用途,并确保不同应用程序使用独立密钥。核心功能如下面的代码片段所示:
|
|
这种构造是不好的:由于盐在提取中被用作HMAC"密钥",它首先通过PAD-or-HASH方案(密钥填充、密钥哈希)进行预处理以处理可变长度密钥。在此实现中,如果您的用户名是b"A"*65,而我选择我的用户名为sha256(b"A"*65),那么我将获得您的所有密钥!
那么我们应该怎么做呢?首先要避免的是潜在攻击者控制的盐。在上面的示例中,应用程序可以在初始化时生成随机盐,并在需要时从受信任的地方检索它。或者,应用程序也可以使用常量盐,如全零字节串,如RFC 5869所推荐。值得注意的是,对于HMAC,如果ikm已经是均匀随机密钥,使用常量不需要更强的假设。最后,如果IKM最初是随机密钥且用户名限制在我们讨论双PRF时描述的一组值中,也可以避免此问题。
我应该使用什么作为信息值?
应用程序必须确保对每个新子密钥使用唯一的信息值。在信息值中包含尽可能多的上下文信息(如会话标识符或传输哈希)也是良好实践。上下文到信息的编码必须是单射的,例如通过注意规范化问题。
我需要在HKDF的信息参数中添加额外随机性吗?
我们经常遇到在信息参数中包含额外随机性以生成子密钥的实现。希望是使HKDF更具随机性。
|
|
虽然这没有害处,但对随机性提取的初始任务也没有太大帮助。请注意,额外随机性仅影响随机性扩展。考虑以下思想实验:如果IKM没有足够的熵或者HMAC被证明是非常差的随机性提取器,额外随机性将无助于创建适合在随机性扩展中使用的密钥。用于随机性扩展的远非随机密钥偏离安全要求,因此不提供安全保证。从上述讨论中,假设HKDF是安全的,如果ikm有足够的随机性,我们将为其提取随机密钥。然后,扩展将确保子密钥与相同长度的随机密钥不可区分。此外,HKDF不要求信息材料是秘密的;只需要对每个子密钥是唯一的。
然而,应用程序可以使用额外随机性来进一步保证信息输入的唯一性。除非您对该额外随机性做一些奇怪的事情,否则使用它不会使情况更糟。
我应该对低熵输入使用HKDF吗?
不。HKDF仅由几个HMAC调用组成。对于非故意设计为缓慢且内存密集的KDF,密码破解者可以相当高效地破解大量密码。最好使用缓慢、内存困难的算法(如Argon2)进行哈希和从密码派生密钥。此外,最好避免使用密码哈希作为加密数据的密钥。更喜欢创建密钥层次结构(如密钥加密密钥),使用密码哈希加密随机生成的密钥,从中可以根据需要派生更多密钥。
我应该使用哈希函数作为通用KDF吗?
哈希函数不应在KDF中通用。在密钥派生过程中使用的信息是攻击者控制的场景中,使用哈希函数作为KDF可能使应用程序面临长度扩展攻击。这些攻击是对从秘密结合用户提供数据生成随机性的应用程序的主要关注点(如Flickr的API签名伪造漏洞)。相反,更喜欢专门为密钥派生设计的HKDF和其他KDF。尽管在特定情况下哈希作为KDF是可接受的,我们警告不要这种实践,除非用户能够合理地正式论证其应用程序的使用。如果您的应用程序真正遭受一个或两个额外压缩函数调用的困扰,请咨询专家,如果您没有使用现有KDF的强有力理由。此建议也适用于其他临时构造,如YOLO构造。
我应该将共享的Diffie-Hellman秘密密钥用于AEAD吗?
AEAD(和大多数其他密钥对称算法)的安全合同要求适当长度的均匀随机位串以提供有意义的安全保证。DH输出是高熵的,但通常不是均匀位串。因此,将它们用作密钥偏离安全合同。一些实现可能允许不知情的用户对给定原语使用错误的密钥材料(例如,将DH输出馈送到Chacha20密码中)。此类使用违反了AEAD构造的要求。
组合密钥
密码学中的常见任务是组合两个原语实例,使得整体构造与最强的原语一样强。自然地,这是与密钥派生高度相关的问题:我们能否从一组密钥材料派生秘密,使得只要其中一个密钥材料安全,整体秘密就安全?混合密钥交换协议是目前此技术的相关用例。这些协议结合通过经典和后量子密钥交换原语建立的密钥,以防范今天收集加密通信并希望在有能力量子计算机可用时解密它们的攻击者。此类协议包括PQXDH、Apple的PQ3和后量子Noise。然而,密钥组合在与量子威胁无关的其他上下文中广泛使用,例如带有预共享密钥的TLS1.3、双棘轮算法和MLS。
那么,我们如何组合秘密?为简单起见,我们将以下讨论限制为两个秘密k_1和k_2。此任务的经典工具是双PRF。与PRF类似,双PRF接受密钥和输入,并且只要其中一个密钥或输入包含均匀秘密密钥,其行为就像PRF。在双PRF中,您可以切换密钥和输入值而不影响安全性。在实践中,双PRF的最常见实例是HMAC。
然而,使用HMAC作为双PRF需要一些谨慎。标准化的HMAC允许可变长度密钥,这些密钥通过PAD-or-HASH函数处理。PAD-or-HASH不是抗碰撞的,为不受限制的HMAC密钥创建HMAC输出冲突是微不足道的。幸运的是,本文建立了HMAC的双PRF安全性,并完全描述了预期双PRF安全性的密钥集合。简而言之,HMAC的安全双PRF使用要求密钥参数(即传递给HMAC作为密钥的内容)是固定长度位串(即所有密钥必须具有相同长度)或可变长度位串,只要所有密钥的长度至少是底层哈希函数的块长度。
双PRF结果仅适用于组合两个均匀随机位串时。尽管有几项工作主张将HMAC与其他高熵输入(如Diffie-Hellman共享秘密G^xy)一起用作双PRF,但更保守的用法是对每个需要它的密钥材料应用初始提取步骤。例如:prk = HMAC( HKDF.Extract(G^xy, salt), random_kem_secret)。尽管一些分析省略了初始提取步骤,但这些使用偏离了HMAC的现有安全分析,并不直接享受安全保证。
双PRF使用的另一个良好实践是确保最终组合秘密依赖于尽可能多的上下文。这里的上下文可以是Diffie-Hellman共享、完整通信传输的(哈希)。一个好的解决方案是在扩展期间使用上下文作为信息输入使用额外的扩展步骤。
最后,存在其他组合方法,如连接KDF(CatKDF)。CatKDF大致在秘密的连接上使用KDF。在其中一个秘密可能被攻击者控制的场景中,CatKDF的安全性超出了现有安全分析范围。上述评论并不意味着实际攻击,但提高了对有时需要超出已知范围的更强假设情况的认识。有关实践中双PRF使用的进一步讨论,请参见《基于单向性的实用(后量子)密钥组合器及TLS应用》。
选择正确的工具
这篇博文检查了不同的KDF任务、执行它们的适当工具,以及我们在实践中看到的一些典型误用。最后,我们邀请您在处理下一个KDF任务时做同样的事情。邀请如下:当您面对下一个KDF任务时,退后一步考虑更高级目标以及更高级工具是否更适合。
例如,您需要KDF是因为您已建立一些Diffie-Hellman共享秘密并必须创建"安全通道"吗?考虑使用现有的经过实战测试的认证密钥交换协议,如Noise、TLS 1.3或EDHOC。
您需要KDF来加密数据流的各种块,同时期望块和整体流的某些安全保证吗?考虑使用流式AEAD代替!
自然,有时需要新颖的解决方案;在这种情况下,确保您对提议的解决方案有合理的理由,然后与您最喜欢的密码学家交谈(或来找我们)!