# 使用非对称加密的对称密钥分发
一种最朴素的密钥分发思路是这样的 (Merkle, 1979):
Alice 首先生成一个公私钥对,将公钥 和自己的身份标识符 发送给 Bob。Bob 生成一个会话密钥,使用 Alice 发来的公钥 加密,将密文 发送给 Alice。Alice 使用自己的私钥解密就能够得到共享密钥。
很明显这是一个能够防窃听的协议,但也很明显,这个协议会受到中间人攻击。
一个中间人攻击者 Eve 可以通过如图的中间人攻击获取到 Alice 和 Bob 的会话密钥。也就是说这条协议仅在被动的窃听攻击下是安全的。
NEED78 (Needham&Schroeder, 1978) 提出了一种抗主动和被动攻击的密钥分发方案,这个方案是基于 Alice 和 Bob 已经使用了安全的方式交换了密钥的前提下的。也就是,Alice 和 Bob 已经拥有了对方的公钥。
Alice 使用 Bob 的公钥加密自己的身份标识和一个 Nonce 值 发送给 B, 的存在也就使得这个数据包不能够被重放攻击。接下来,Bob 解密得到其中的 并且再生成一个 Nonce 值,将 发送给 Alice,这个过程保证了 Bob 可以正常解密 Alice 发来的数据且其他人无法正确解密这条数据。同时,Nonce 值 也保证了这条消息的不可重放。
Alice 接下来将解密得到的 Nonce 值 加密发送给 Bob—— 这个过程保证了 Alice 身份的可靠性,因为只有 Alice 才能解密使用 加密的消息。
在最后,Alice 生成一个会话密钥,并使用自己的私钥进行签名得到(这保证了只有 Alice 才能够发送这条消息),并使用 Bob 的公钥加密发送给 Bob。
# 公钥分发
上面的协议是抗主动和被动攻击的,但是一个很明显的问题在于它需要预先实现公钥交换。
最朴素的思路就是公开发布,也就是说发送方向接收者或者全体用户广播自己的公钥。很显然,如果一个攻击者 Eve 伪造成 Alice 公布了自己的公钥,那么在 Alice 发现并纠正这个问题之前,通信者 Bob 很可能错误地使用 Eve 的公钥去加密消息。
比较可行的方式还是需要去借助于可信的第三方参与者。
一种方式是维护公钥目录。公钥管理员会维护一个公钥目录,其中以 用户:公钥
的形式存储了各个用户的公钥。当用户需要进行通信时,可以直接访问这个公钥目录去获取其中存储的公钥。如果用户需要添加、修改或者删除公钥,则需要通过安全的手段向公钥管理员验证自己的身份(安全的信道,或者干脆线下窗口拿着身份证户口本办理)。
这种方式的安全性依赖于公钥目录自身的安全性 —— 如果公钥目录自己的私钥被破解,那么整个系统的安全性便会被直接破坏。
一个更加严格控制公钥分发的思路是公钥授权。POPE79 (Popek&Kline, 1979) 给出了一种典型的公钥授权分发方案。
严格地说,这里负责公钥授权的机构并不能叫做 KDC。不过为了方便就这样吧(
<!-- @startuml
skinparam sequenceMessageAlign center
participant Alice as A
participant KDC as K
participant Bob as B
A -> K : <latex>request||T_1</latex>
K -> A : <latex>E(PR_{auth},[PU_b||request||T_1])</latex>
A -> B : <latex>E(PU_b,[ID_A||N_1])</latex>
B -> K : <latex>request||T_2</latex>
K -> B : <latex>E(PR_{auth},[PU_a||request||T_2])</latex>
B -> A : <latex>E(PU_a,[N_1||N_2])</latex>
A -> B : <latex>E(PU_b,N_2)</latex>
@enduml -->
KDC 保存有一个公私钥对。Alice 如果需要和 Bob 通信的话,需要经过下面的步骤:
Alice 发送一条带有时间戳的请求消息给 KDC。KDC 找到 Bob 的公钥,并将其和 Alice 发来的信息组合起来用自己的私钥签名发送给 Alice。这个步骤的意义在于能够让 Alice 验证这条消息来自 KDC,并且这条消息也是回应自己刚才的请求。
接下来 Alice 将自己的身份标识和一个 Nonce 值 发送给 Bob。这条步骤和前面 NEED78 的第一步一致。
Bob 同样采用类似前面的步骤向 KDC 请求 Alice 的公钥并得到回应。接下来的两个步骤和 NEED78 的第 2、3 步骤一致。
而在双方缓存了对方的公钥的情况下,POPE79 的步骤可以不必重复进行。
这个方案的缺陷在于两个用户若需要通信则就要向 KDC 请求对方的公钥。这个过程使得 KDC 成为了整个系统通信效率的瓶颈。
Kohnfelder 提出了使用证书的替代方案。证书包含了公钥和公钥所有者的标识,并有一个可信的第三方机构进行签名。一个用户 Alice 将自己的公钥交给管理员(CA),得到证书之后便可以公开证书。需要使用 Alice 的公钥的用户只需要获得 Alice 的证书并验证有效性就可以和 Alice 进行通信。
Kohnfelder (Kohnfelder, 1978) 指出公钥证书应当有这些特点:
- 通信方可以读取证书并确定拥有者的身份和公钥
- 通信方可以验证证书是由证书管理员颁发的
- 只有证书管理员(CA)才能生成和更新证书
- 通信方可以验证证书的有效性(Denning, 1983)
用户 Alice 的证书格式满足,其中 是一个时间戳, 是 Alice 的身份标识, 是 Alice 的公钥,CA 的私钥为。
Bob 需要验证证书时,仅需要使用 CA 的公钥进行验签:。
那么整个证书交换的流程也就简化为了下面的步骤:
- 从 CA 获取证书
<!-- @startuml
skinparam sequenceMessageAlign center
participant Alice as A
participant CA as CA
participant Bob as B
A -> CA : <latex>PU_a</latex>
CA -> A : <latex>C_A</latex>
B -> CA : <latex>PU_b</latex>
CA -> B : <latex>C_B</latex>
@enduml -->
- 证书交换
<!-- @startuml
skinparam sequenceMessageAlign center
participant Alice as A
participant Bob as B
A -> B : <latex>C_A</latex>
B -> A : <latex>C_B</latex>
@enduml -->
以上是基于公钥证书的方案,实际上现在使用的大多也是这个方案。
# X.509
X.509 是证书体系的一个重要的标准。在现在的各种安全体系中都被广泛采用,也可以说是当前互联网的基石之一。X.509 标准中证书的生成和验证基本上是按照下面的逻辑来进行:
一个符合 X.509 格式的证书可以采用下面的形式来定义:
这表示了一个由 发布给 并由 签名的证书,其中各个字段的定义如下:
- :证书的版本号,默认为 1。存在发行商唯一标识或主体唯一标识则为 2。存在一个或多个扩展则为 3。
- :证书的序号,用来在 CA 处唯一标识一个证书。
- :标识用于证书签名的算法。
- :证书颁发机构 CA 的名称。
- :CA 的可选唯一标识。
- :用户 A 的名称。
- :用户 A 的可选唯一标识。
- :用户 A 的公钥。
- :证书的有效时间。
以 github pages 的这个证书为例:
$ openssl x509 -noout -text -in _.github.crt | |
Certificate: | |
Data: | |
Version: 3 (0x2) | |
Serial Number: | |
08:b4:bf:75:7f:db:28:dd:f3:b8:c4:c9:35:7c:5a:49 | |
Signature Algorithm: sha256WithRSAEncryption | |
Issuer: C = US, O = DigiCert Inc, CN = DigiCert TLS RSA SHA256 2020 CA1 | |
Validity | |
Not Before: Apr 7 00:00:00 2022 GMT | |
Not After : Apr 7 23:59:59 2023 GMT | |
Subject: C = US, ST = California, L = San Francisco, O = "GitHub, Inc.", CN = *.github.com | |
Subject Public Key Info: | |
Public Key Algorithm: rsaEncryption | |
RSA Public-Key: (2048 bit) | |
Modulus: | |
00:bc:aa:66:36:ad:93:68:e7:aa:25:a6:a7:a7:ae: | |
...... |
可以查看具体的细节。
# 信任链
如果有非常多的用户,那么从一个 CA 处获得证书会变得非常困难且不可靠,而一个解决方案是设置多个 CA,用户的证书由某一个 CA 进行签发。但在这种情况下难免会出现一个问题:当 Alice 和 Bob 通信时,Alice 的证书由 签发,而 Bob 的证书由 签发,如果 Alice 不能安全获取CA2_的公钥则不能验证 Bob 的证书是否合法。
那么这就需要几个 CA 之间能够相互交换证书:Alice 从 处获得由 签名的 的证书,并从中获取 的公钥,再用来验证由 签名的 Bob 的证书。这也就是证书链,可以表示为:
当然很明显,为了 Bob 能够反向验证 Alice 的证书合法, 也应该有其签名的 的证书。
证书链也可以延长,构成一个长度为 的证书链:
X.509 中存在一个 CA 的层次结构:
每个 CA 有两种证书,前向证书和后向证书。 的前向证书是由其他 CA 生成的 的证书,而 的后向证书是由 生成的其他 CA 的证书。
当 Alice 和 Bob 通信时,只需沿途展开各个 CA 的证书就可以获取对方的公钥。
# 撤销证书
每个证书都是存在有效期的,当有效期结束之后证书将不会受到信任。同时还有其他的原因会导致 CA 提前将证书撤销:
- 用户私钥不安全
- 用户不再信任 CA
- CA 证书不安全
每个 CA 会维护一个证书撤销表(CRL),其中记录了证书撤销的信息。用户收到证书后会验证证书是否已经被撤销。
# 公钥基础设施
公钥基础设施(PKI)是用于管理证书和公私钥对的策略、流程、平台、软件、工作站等,能够颁发、维护和撤销公钥证书。PKI 要求 CA 和用户能够满足上面 Kohnfelder 和 Denning 的要求。
整个 PKI 的架构基本如下:
其中包括了:
- 终端实体:用户、设备、程序或其他能够被证书认证的实体。
- 证书颁发机构(CA):用于创建和分发公钥证书的机构,同时也负责维护证书撤销表。
- 注册机构(RA):可选的,用于实体和 CA 之间的注册、认证的功能,可以减轻 CA 的负担。
- 证书储存库:用于存储检索 PKI 中的证书、CRL 等信息。
- 信赖方:用于依靠证书数据进行决策的用户。
# 参考资料
[1] 网络安全基础 —— 应用与标准(第六版),William Stallings.
[2] 密码编码学与网络安全 —— 原理与实践(第八版),William Stallings.