本文作者总结了密码学中常见的陷阱,包括基础概念的误解、nonce/IV 的错误使用、算法选择的漏洞、AES-GCM 的使用不当、签名机制的缺陷以及侧信道攻击的风险,并对X.509证书的相关问题进行了分析说明,旨在帮助密码学从业人员避免这些常见的错误,提高密码系统的安全性。
背景
本文是关于什么?
我意识到密码学中存在许多违反直觉的“陷阱”。
虽然其中一些是相对众所周知的(例如“加密并不意味着完整性”),
但还有许多不太为人所知,甚至会让经验相对丰富的人也栽跟头。
因此,这是一个脑力激荡,我希望它能帮助审查和设计加密系统的其他人。
本文不介绍什么?
这不会教你密码学或帮助你入门。为此,请参阅我的入门
和如何阅读研究论文页面。
这也不是试图告诉人们该怎么做。
已经有很多优秀的指南,包括Latacora 的密码学正确答案或为你完成大部分所需功能的库,例如 AWS Encryption SDK。
事实上,本文实际上永远不会试图告诉你该怎么做,只是告诉你 不要 做什么。
这也意味着目标受众实际上并不是初级工程师(尽管我希望他们仍然会觉得它有用并且会帮助他们进步)。
如果你是一名初级工程师,你真的不应该在这个层面工作。
相反,请查看本段前面链接,以获得关于你应该 做 什么的更好指导。
我还强烈推荐 Dan Boneh 的在线密码学课程 和 The Cryptopals Crypto Challenges(两者都是免费的)。
相反,本文针对的是已经非常了解密码学但尚未记住世界上每一种算法和怪癖的来龙去脉的更有经验的人。
词汇表
我喜欢开玩笑说,学究气是密码学家工作描述的一部分。像许多笑话一样,其中也包含着一些真理。根据我的想法(或人们的询问),我将在此处添加一些我以非常具体的方式使用的重要词语。因此,对于这些术语,你可能需要忘记它们的正常含义并使用我在此处提供的含义。值得注意的是,这些是我的定义,可能与标准定义并不完全匹配。尽管如此,我会尽力做到最好,并且我认为它们之间的细微差别可能很重要。
- 规范 (Canonical): 当我提到某事物是“规范”时,我的意思是指它只有一个特定的(正确的)表示形式,而所有其他表示形式都是不正确的。更重要的是,你可以构造正确的表示形式,并确定特定表示形式是否是正确(规范)的表示形式。例如。我可以定义“整数的规范形式是十进制,没有前导零。唯一的例外是零值,它表示为单个 '0'。”在这种情况下,“5”是规范的,但“05”和“00”不是。当格式为规范时,任何人都不能为同一事物创建多个表示形式(例如,磁盘上的位)。一般来说,任何数据编码或格式都不应被视为(或假定)是规范的,除非明确设计和记录为如此。
- 不可变 (Immutable): 当我将某事物称为不可变时,这意味着某些范畴的人(或参与者)无法在不使其失效的情况下修改它。与“规范”形式(任何人无法修改)不同,任意参与者都无法修改不可变形式。它可能可以由特权参与者修改。例如,AES-GCM 加密的数据可以被认为是不可变的(因为 AES-GCM 提供了完整性,并且任意参与者无法修改它)。但是,拥有密钥的人可以修改 AES-GCM 密文而不使其失效(甚至可以在不更改标签的情况下这样做)。
陷阱
基础知识
这些是你应该注意的标准事项。希望你已经学会了所有这些,但以防万一,这里还是列出来。
- 阅读标准。许多标准文档包含关于如何做事的明确指示(例如,构造 nonce、检查输入或应用什么限制)。如果你要使用某种算法,请阅读定义它的标准。
- 加密并不(一定)提供完整性/真实性。
- 没有非对称模式提供真实性(任何人都可以加密)。
- 除非它是明确的认证加密(例如,AES-GCM),否则它不提供任何完整性或真实性。
- 永远不要将一个密钥用于多个用途(这条规则有时会被非常小心地违反)。
- 密钥只能用于单个算法。不要将相同的密钥用于 AES 和 HMAC(或 CMAC 或任何其他算法)。
- 这也适用于同一算法的不同模式。不要将相同的密钥用于 AES-GCM、AES-CBC 和 AES-CTR。在非对称领域,你不应该将相同的密钥用于签名、密钥协商和加密。(是的,这条规则通常会被 RSA 密钥违反,虽然通常是安全的,但也曾在过去造成问题。)
- 即使使用相同的算法和模式,你仍然不应该将相同的密钥用于不同的目的。例如,如果你与另一方有一个双向信道,并且两个方向都使用 AES-GCM 加密,那么你可能应该为每个方向使用单独的密钥。
- 始终使用密码学安全的伪随机数生成器。
即使你没有将其用于任何具有安全含义的事情,也没关系。只是始终使用它。
如果你必须使用不安全的生成器(可能是出于性能原因),请将变量命名为
insecureRandom
之类的名称,以确保你不会意外地交叉连接某些东西。
有关如何在多种语言中执行此操作的指导,请参阅 Paragon Initiative Enterprises 关于此确切主题的优秀博客文章。
- 哈希永远不能提供认证。
由于哈希不包含密钥,攻击者可以仅计算篡改数据上的正确哈希,而你永远不会知道。
也不要尝试自己合并密钥,因为它行不通。(请参阅长度扩展攻击和Flickr 在这方面的问题)。
如果你需要类似的东西,你应该使用一种可以为你正确处理密钥的构造(例如 HMAC 或 AES-GCM)。
- HMAC 密钥的长度应与哈希输出的长度相同。
- 虽然 HMAC 密钥的长度可以安全地达到基础块的长度(SHA-1 和 SHA-256 为 512 位,SHA-384 和 SHA-512 为 1024 位),但大于输出大小没有任何实际价值
- 如果 HMAC 密钥大于基础块大小,则在使用前会对其进行哈希处理。这意味着对于所有
K
和 m
,只要 K
足够大,HMAC(H(K), m) = HMAC(K, m)
。这将违反你的安全期望。
- 密码密钥会“磨损”。
最简单的解决方案是定期进行密钥轮换。
如果这看起来仍然会成为你的问题,请寻找旨在避免此问题的模式/库(例如 AWS Encryption SDK)或寻找专家。解决此问题超出了本文档的当前范围。(Soatok 有一篇优秀的博客文章,其中包含一些内部细节和具体数字。)
- 通常,仅担心对称密钥。
- 在极少数情况下,这受到 生日悖论 的约束,这意味着使用密钥执行超过 2<sup>b/2</sup> 次操作会降低你的安全性。你通常应远低于此限制。我个人建议将其保持在约 2^48 次操作以下。
- 对于任何基于分组密码的内容(例如几乎任何 AES 的使用),这将限制使用给定密钥加密的块的数量。
- 对于 MAC,这将是生成的标签的数量。
- 某些模式(例如 AES-GCM)对使用量有额外的限制。请仔细检查它们。
- 除非明确设计为如此,否则不要假设任何编码是规范的。虽然有忽略空格的明显情况(十六进制、base64、yaml、json、xml 等),但许多情况也忽略大小写(十六进制、电子邮件标头等)。许多格式还支持元素顺序无关紧要的无序集合的概念(ASN.1 ProtoBuf等)。有趣的是,Base64(即使忽略空格)也不是规范的!由于尾随填充(通常是可选的)会导致你忽略位,因此忽略的位可以是任何东西。
例如,虽然
example
通常会被编码为 ZXhhbXBsZQ==
,但它还有许多其他可能的值,包括 ZXhhbXBsZR==
、ZXhhbXBsZY==
和 ZXhhbXBsZf==
。 <!-- cspell:disable-line -->
杂项
还有一些陷阱我还没有想好如何分类。
它们并不是很“基础”,但要么它们适用于很多东西,要么我还没有足够的内容可讲。
也许我只是还没有抽出时间来组织它们?
(或者也许它们有专门的页面,只需要从某个地方链接。)
- 大多数密码不是“承诺”的。
这意味着可以使用不同的密钥将单个密文解密为不同的有效明文。
即使对于 AEAD 密码(如 AES-GCM 和 chacha20/poly1305),情况也是如此。 <!-- cspell:disable-line -->
AES-GCM 不是承诺的,破坏了 Facebook Messenger 的一些安全属性。
- 当仅长度发生变化时,密钥派生函数 (KDF) 可能不会生成不同的输出。
(HKDF,我最喜欢的 KDF,就是一个例子。)大多数 KDF 接受初始密钥材料 (
IKM
) 和每个派生密钥 Info
值以及 Length
。(它们可能接受其他参数,但为了这个陷阱可以安全地忽略这些参数。)如果除了 Length
之外的所有输入都保持不变,则输出可能相关。例如,以下是
针对 Length=16
和 Length=32
的 HKDF(IKM=0x0102030405060708, Salt="mysalt", Info="myinfo", Length=X)
的输出: <!-- cspell:disable-line -->
0x6adb5cbd648b0af649d1f507543df984
0x6adb5cbd648b0af649d1f507543df98484ed986c43cfcec47056b1d49795d944
- 公钥是公开的,任何假设它们保持秘密的系统都可能被破坏。由于非对称算法/实现假设公钥是公开的,因此它们很少采取任何预防措施来保护它们。 RSA 密文都小于公钥,因此你可以使用德国坦克问题来估计公钥。(这破坏了澳大利亚政府系统 PLAID 的隐私保证。)许多签名允许密钥恢复。(有关更多信息,请参见“签名”部分。)最后,由于公钥不是秘密的,因此许多实现没有在使用它们时使用恒定时间(或无侧信道)算法,这会将公钥暴露于“泄露”。
- 域隔离是一项关键属性,可确保数据不会在不同用途之间混淆。
这个主题非常复杂,我专门为其创建了一个页面。
Nonce/IV
Nonce 或 初始化向量 (IV) 本质上是同一事物的两个不同名称。
虽然有些人(包括我自己)试图区分它们,但事实是这基本上是徒劳的,你不能仅根据名称来假设有关 Nonce/IV 的任何信息。
虽然正在进行一些关于“抗 nonce 重用”密码学的工作,但你仍然应该尽量避免重用这些值。仅为了安全起见。
- Nonce/IV 绝不能重复(在给定的上下文中,通常相对于密钥)。
如果你使用的是随机 nonce/IV,一个好的经验法则是生成的随机值不超过 2<sup>(n-32)/2</sup>,其中 n 是 nonce 中的位数。例如,当将 AES-GCM 与 12 字节(96 位)随机 nonce 一起使用时,你的限制为 2<sup>32</sup> 个随机值。
- 从通用加密熵源生成密码学随机 nonce 永远不会出错。
除此之外的任何构造都可能导致问题。
- 许多 nonce 都有不明显的限制。如果除了独立的随机值之外,你还在做其他任何事情,那么请阅读特定于算法/协议的文档。
解密/验证的算法/密钥选择
本节不是关于为你的设计选择合适的算法或密钥(请参见介绍)。
它是关于在解密或验证数据时,如何为你的设计的特定用途选择正确的参数。
现在,我知道密码学界正在大力推动尽可能消除这种协商,我对此表示同情,但这并非总是可行。
如果你需要解密/验证过去某个时间点的数据,或者轮换密钥,或者能够移除已弃用的算法,则需要某种方式来指示这一点。
当然,这里有很多陷阱(这也是社区试图首先消除它的主要原因)。
- 不要让你的对手选择完全任意的密钥。
如果对手可以让你使用任意密钥,那么它可能是一个他们控制的密钥,而且你永远不会想到使用它。
这听起来可能很荒谬,但是许多非对称签名都带有用于验证的公钥。
相反,必须极其仔细地检查所有密钥,以确保它们在使用前受到信任。
完成此操作的两种方式是 PKI(功能强大,难以正确使用,仅适用于非对称密钥)或为密钥提供唯一标识符并使用该标识符。
当然,即使使用唯一标识符,你也需要非常小心,并且可能需要列入白名单以确定你接受哪些标识符,因为可能存在具有有效标识符但不应用于此用例的密钥。
(这方面的一个例子是任何大规模的云/公司 KMS 系统,其中每个密钥都有一个唯一标识符。)
- 不要让你的对手选择完全任意的算法。
如果对手可以让你使用任意算法,那么他们可以选择不安全或完全被破坏的算法。
(这方面的一个例子是降级攻击。)
最多只能从预先批准的白名单中选择。
确保该算法适合密钥!(我明确建议人们不要使用 JWT,而这种历史问题是其中一部分原因。)
如何做到这一点
这是我极少数提供关于如何做某事的建议,而不是简单地说什么不要做。
这是因为支持密钥轮换和能够更改算法非常重要。
因此,如果你要加密数据并且以后需要解密它,我建议以下(简单)的解决方案。
- 在你的密文前面加上一个版本号(4 字节整数?)
- 每个版本号对应于解密该消息所需的不可变的密钥集(通常只有一个,但如果你有多个用于保密性/加密的密钥,则可以同时指示这两者)和使用的确切算法。
- 将你将接受的版本列入白名单,并在可以时修剪此列表。
这将允许你轮换密钥(通过递增版本)甚至在需要时迁移到新算法(通过递增版本),同时避免协议协商的所有常见复杂性。
它仍然不是万无一失的,但对于许多简单情况来说,这是一个合理的设计。
如果这不足以满足你的设计,请寻求专家进行交谈。
AES-GCM/GMAC
AES-GCM 是一种非常流行的模式,也是人们选择的更好模式之一。但是,它的行为并不总是符合直觉。
- 重用(nonce/IV、密钥)组合不仅会破坏受影响密文的机密性,还会导致密钥保护的所有密文失去完整性/真实性。当 GCM 最初标准化时,这已为人所知,甚至在现实世界中也发生过。
因此,根据上面的“密钥磨损”和“nonce”,对于 96 位 nonce(推荐的情况),你不应使用给定密钥和随机 nonce 生成超过 2<sup>32</sup> 个密文。
- 虽然 nonce 可以在不同的密钥之间重复使用,但这是一种更高级的设计,应谨慎进行。
请注意,即使 nonce 没有变化,仍然需要一个 nonce。我强烈建议使用 96 位的零。
零长度的 nonce 不符合规范,极其不安全,绝不能使用。
- 由于 AES-GCM 使用底层计数器模式,因此你一次不能加密超过 2<sup>36</sup>-32 字节,因为它会导致计数器溢出。
- 由于数据的内部编码,AAD 的长度也限制为 2<sup>61</sup>-1 字节
- 如果你使用具有相同密钥的不同长度标签,则会降低该密钥生成的所有标签的安全性,而不仅仅是短标签的安全性。(有关此构造和其他有趣的问题,请参见 Niels Ferguson 的 GCM 中的身份验证弱点。)
- 生成的标签不能被视为“随机”值(例如,像随机函数或哈希函数的输出)。你期望的任何属性(抗冲突性、不可逆性等)都可能不存在。你唯一可以假设它们拥有的属性是 MAC 的定义明确承诺的属性。
- 例如,对于知道密钥的人来说,创建具有任何任意标签的消息非常简单。
- 这意味着对于知道密钥的人来说,创建具有相同标签的多个消息非常简单
- 将其与 HMAC 生成的标签进行对比,后者通常表现得像人们期望的那样(因为它们具有抗冲突性并像 随机预言机 的输出一样工作)
- AES-GCM 不是承诺的。(请参见前面“基础知识”下的讨论)。
- 在验证标签之前,不要使用明文! 这被称为“发布未经验证的明文”,非常糟糕。
一些实现(我说的是 OpenSSL 和 BouncyCastle)在标签经过验证之前(例如,在
doFinal
调用之前)发布明文。虽然从性能的角度来看这很棒,但在标签经过验证之前,明文必须被认为是完全不受信任的。这意味着你不能对它做任何你不能完全回滚的事情。实际上,最好完全忽略它。如果你要解密数据并将其传递给其他组件,你可能应该缓冲所有明文,直到你知道它是有效的。
- 不知道 AAD 不会阻止拥有密钥的人解密数据。
AES-GCM 只是 AES-CTR(一种未经身份验证的模式)加上 GMAC。这意味着拥有密钥的攻击者可以仅以 AES-CTR 模式解密密文,而忽略额外的 GMAC 标签。或者他们可以使用像 OpenSSL 这样的库来发布未经验证的明文(请参阅前面的要点),并通过这种方式获取数据。
签名
数字签名 通常可以安全使用,但许多人假定它们具有它们不具备的属性。从本质上讲,它们仅意味着如果没有私钥,攻击者就无法找到与其关联的公钥在任意值上的签名,而他们事先不知道该值的签名。(又名[存在不可伪造性])
关于这些额外的(未承诺的)安全属性的最佳概述在 看起来合法:签名协议上微妙攻击的自动分析 中
我强烈建议每个人至少浏览一下该论文。
(其中有很多形式验证,你可能可以跳过,但引言、定义的属性和案例研究至关重要。)
为了更易于阅读,虽然完全专注于 ECDSA,但你应该查看 如何不使用 ECDSA。
也就是说,以下是数字签名的许多“陷阱”:
- 许多签名是可延展的。这意味着给定有效签名,攻击者通常可以找到同一消息的其他有效签名。
- (EC)DSA 有几种不同的编码:ASN.1、IEEE P1363 和“原始”(我命名的)。这意味着攻击者可以将一种形式的有效签名转换为另一种形式。
- 虽然 IEEE P1360 和原始形式都有特定的长度要求,但许多系统不强制执行它们。(这是稍后陷阱列表中“边缘情况”签名的一个示例。)
- 虽然 ASN.1 编码应该使用 DER 编码,但许多库接受任何(半)有效的 BER 编码。这意味着攻击者可以在知道单个签名后,通常可以利用 BER 的灵活性来制作基本上无限数量的有效签名(对于同一消息)。(更多“边缘情况”签名)
- ECDSA 在数学上也是可延展的。它由两个值
(r, s)
组成,并且给定一个签名,计算新的 s'
(等于阶数减去原始 s
)非常简单,这会导致同一消息上的新有效签名 (r, s')
。
- 不得允许攻击者选择在签名中验证的实际值。(所有标准签名中的哈希处理步骤可以防止这种情况,因为他们只能选择哈希预图像,而不能选择哈希值)。如果他们可以这样做,那么他们可以通过(基本上)生成随机签名并查看该签名将验证的消息,然后返回该消息/签名对,从而轻松地为任意公钥制作有效签名。
- 仅仅因为签名对于给定消息有效,并不意味着它对于其他消息无效。
- 仅仅因为签名对于消息和公钥有效,并不意味着私钥的持有者必须知道消息是什么。
- 如果底层哈希函数被破坏,这可能是无意的(请参阅前面的要点)
- 否则,这通常需要签名者的有意行为:
- 考虑一下之前恶意创建的通用签名。
- 签名者可能只知道哈希而不知道真实消息,这是一个微不足道的情况。
- 这也可以是功能(而不是错误),就像 盲签名 的情况一样
- 仅仅因为签名对于给定公钥有效,并不意味着它对于其他公钥无效。
就此而言,给定签名和消息,攻击者可以制作验证签名和消息的公钥。
这被称为 重复签名密钥选择攻击。
- 一些签名是随机的(例如,(EC)DSA 系列和 RSA-PSS),而一些签名是确定性的(例如,RSA PKCS#1 v1 和 v1.5)。这意味着假设任何一种属性都会让你失望。
- 签名(必须)不隐藏它们签名的消息。它们通常会显示所签名数据的哈希(或更多)。
- 签名不隐藏用于验证消息的公钥。
- “公钥恢复”是 ECDSA 的标准化部分。(请参阅 SEC2,第 4.1.6-4.1.7 节。)
- 对于 RSA 来说,这稍微更具挑战性,但仍然可以做到,如此处所述和此处实现。
- 对于许多签名算法,除了标准定义的“有效”和“无效”签名列表之外,还可能存在大量“边缘情况”签名。
这些可以被认为是技术上无效但许多实现可能会接受而不会破坏签名基本保证的签名。
(即,这些无效签名不会使[存在不可伪造性]失效,因此需要使用私钥来构造。)
这些包括:(EC)DSA 签名的无效 DER 编码、IEEE P1363 (EC)DSA 签名的无效长度、大于组顺序/模数的值、非规范点编码等。
虽然对于大多数系统来说,这并不是安全问题,但它肯定会破坏诸如共识协议之类的系统,这些协议要求不同的参与者对数据的有效性完全一致。
如果不同的实现可能接受不同的签名,这将破坏协议。
想象一下,如果区块链的所有客户端都不同意区块是否有效?完全混乱!
(有关 Ed25519 签名的优秀文章,请参见 Henry de Valence 的博客文章。
此外,感谢 Deirdre Connolly 在她的 播客 中强调了这个问题。)
侧信道
引用 SwiftOnSecurity 的话,“密码学是噩梦般的魔法数学,它关心你使用哪种笔。”
在侧信道领域,这一点尤为真实。
仅仅得到正确的答案并执行正确的计算是不够的,你必须以正确的方式做到这一点。
我真的相信编写良好的无侧信道实现是加密开发这个(高度专业化)领域中的一项专业,我绝对不是专家。
因此,请将所有这些建议不仅视为来自该特定角度,而且还针对其他(像我这样的)非专家。
- 任何基于秘密值变化的行为都可能被攻击者(潜在地)利用。
这包括时间、内存访问、功耗、流过电线的位以及字面上任何其他东西。
- 这意味着任何分支、内存访问或基于秘密值的简单数学之外的任何逻辑都是一个问题(甚至简单的数学也可能是危险的)。
- 对于标准开发人员来说,最常见的这种情况是将秘密值(例如 MAC 标签)与另一个值进行比较。
这必须通过时间安全等于检查来完成。
许多语言和框架已经有一个。如果你必须自己编写一个,请寻求专家的帮助。
就此而言,在密码学代码中,我建议对所有数组比较使用时间安全相等检查。
为什么?不是因为它在大多数情况下都是必要的,而是因为它意味着你不会意外地省略它(当你需要它时),而且你也不需要浪费时间不断地重新审查每个不安全的检查,以确定它在这种情况下是否真的安全,并且没有任何改变。
省去你的压力,只需始终使用恒定时间实现。
(待办事项:创建/找到一个单页,其中列出尽可能多的语言/框架的最佳模式。)
- 如果你发现你需要担心除恒定时间相等检查之外的侧信道保护,请寻求专家或重构你的设计以使其不再成为问题。
通常,最好的策略是通过依赖你信任的库来使这成为别人的问题。
例如,你可能会依赖 OpenSSL 或 Microsoft 的 CNG 来进行密码学实现,并简单地声明“我们相信他们的实现是无侧信道的。”
现在,它们不会是。至少不是完美的。
但它们可能比你能编写的任何东西都要好得多,并且更有可能在发现问题时进行审查和修补。
X.509 证书
现在,X.509 证书不再是密码学,就像汽车不再是发动机一样。但是,正如精通发动机的人会花费大量时间担心和修理汽车一样,密码学家(不幸的是)也需要处理 X.509 证书。以下只是与这些恐怖相关的许多陷阱中的一部分。与往常一样,如果你实际上需要使用它们,你应该阅读规范 (RFC 5280 和许多其他规范)。
- X.509 证书应该是 DER 编码的 ASN.1,但是大多数系统会很乐意接受任何(半)有效的 BER 编码。这意味着这些技术上无效的证书几乎总是可以工作,除非它们不能。
(ASN.1 本身就是一个噩梦。说实话,我对它有一种特殊的感情,因为我认为它完成了一个非常有用的功能。但我也喜欢 Perl 和 C++,所以也许我的品味值得怀疑。处理 ASN.1 的三个最佳资源是 ASN.1、BER 和 DER 子集的通俗指南、ASN.1 和 DER 热烈欢迎 和 ASN.1 JavaScript 解码器。这些不止一次地拯救了我。)
- 即使是正确 DER 编码的 X.509 证书也不是规范的,并且通常可以由第三方修改,而不会使签名无效。
这是因为 X.509 证书包含三个主要子部分:TBS(待签名)证书(你关心的所有数据)、签名算法(描述证书如何签名)和签名(对 TBS 证书进行签名)。
由于签名算法未签名,因此任何人都可以更改它(前提是它不会更改签名的解释方式)。
签名算法字段通常有一个可选的
NULL
值,这意味着它有两种有效的变体。
然后,签名本身不能保证是规范的(请参阅 Gotchas 中的其他位置),并且可能可以被任何人修改,而不会使其失效。
非规范的 X.509 证书实际上在 2020 年为 Java 创造了一个现实问题。
JDK 包含一个必须不信任的证书的显式列表。
由于未知原因,这实际上表示为证书的哈希列表。
这意味着有人可以轻松地修改被阻止的证书以逃避检测。
JDK 错误 8237995 通过生成被禁止证书的不同(有效)变体并对每个变体进行哈希处理来修复了此问题。(由于以下要点,这实际上应该是公钥的哈希值。)
- 根证书没有什么特别之处。使证书成为“根”证书的全部原因是其密钥保存在安全的地方并标记为“信任根”。
- 请注意,我在这里说的是“密钥”。通常,密钥是真正的信任根,而“根证书”只是打包密钥的便捷方式。
- 根不需要自签名
- 根通常由其他根签名(通常是出于迁移原因)
- 根上的签名、过期、证书限制等通常会被忽略。(还记得我说的“密钥”是真正的根吗?)
- 根存储通常具有围绕如何使用特定根的自定义逻辑和限制,除非它们没有并且只是信任世界。
- 并非所有“根 CA”都受到公众信任。你可能最熟悉浏览器和 CA/B 论坛 中的那些,但还有许多其他的。
- 虽然一些(公开信任的)根是好的并且管理良好,但另一些只是向一群审计员支付资金以声称它们可以被信任。从外部来看,很难知道哪个是哪个。
- 证书路径构建很复杂,永远不要自己构建。
- 信任现有的库或你的平台。他们可能会出错,但你通常会比他们做得更糟。
- 从叶证书到根可能存在多个有效路径。(有时到同一个根,有时到不同的根。)
- 并非所有证书约束始终都能得到正确执行(如果能够执行),例如名称约束或策略
- 多个证书可以具有相同的公钥。
- 当 CA 需要续订但他们希望保持由其签名的所有内容有效时,这种情况很常见。
- 其他情况下也会发生这种情况
- 证书验证(这是正确的证书并且格式良好吗?)很复杂,永远不要自己构建。
- 现在真的应该忽略服务器证书上的“公用名”(CN),而只应尊重 使用者备用名称 (SAN)。尽管如此,当出现问题时,它仍然经常受到责备。
- 通配符仅适用于最左边的单个级别。因此
*.example.com
匹配 foo.example.com
和 bar.example.com
,但不匹配 foo.bar.example.com
。为此,你需要 *.bar.example.com
。这意味着 *.*.example.com
和 foo.*.example.com
都是无效的。
- 顶级域名 (TLD) 很奇怪,需要特殊处理。(例如,以下证书均无效:
*.com
、*.uk
和 *.ac.uk
。)
- 让我们甚至不要谈论 国际化域名
- 如果你不处理 HTTPS,你通常可以抛弃所有关于名称/主机验证的知识,因为它不再适用。
- 密钥用途(和其他扩展)在这里可能很重要,它们本身就是一个完整的领域。查阅详细规范或将其留给专家。(注意,此警告同样适用于路径构建。)
- 密钥和签名类型可能会(并且经常会)在同一链中有所不同。因此,ECDSA 证书可能由 RSA(中间)CA 颁发,然后由 DSA CA 颁发。(但如果你找到 DSA CA,你可能应该尖叫着跑向稍微新一点的东西。)
贡献和许可
我一直对收到反馈感兴趣。问题 或 拉取请求 可能是最好的,但任何(合理的)联系方式都可以。
有错误吗?告诉我!
我遗漏了什么重要的东西吗?请告诉我!
你能帮助充实我的参考文献或以其他方式改进它吗?我想收到你的来信!
致谢
特别感谢以下帮助过我的人员和团体。
- MVP Slack
- 多年来,我的同事们不断地问我有趣的问题并教我有用的东西
- 我非常有耐心的(非密码学家)妻子,她坐着让我向她解释密码学,直到我想清楚如何传达复杂的概念