读《图解密码技术》:密钥、随机数和应用技术
最后一篇了,如果还没看过前两篇的,最好先翻回去看看,因为这最后一篇的内容是建立在前两篇的基础之上的。本篇的内容包括密钥、随机数、PGP、SSL/TLS,最后再讲讲密码技术的现状和局限性,以及简单介绍一下量子密码和量子计算机。
密钥
在使用对称密码、公钥密码、消息认证码、数字签名等密码技术时,都需要密钥。密钥长度一般不能太短,太短意味着密钥空间太小,那么,进行暴力破解就很容易。
DES的密钥长度为56比特(7字节),密钥空间为2^56,在现有的计算能力下,还是比较容易被暴力破解的。三重DES的DES-EDE3的密 钥长度为168比特(21字节),是DES的密钥长度的三倍多,但是密钥空间可不是三倍这么简单,DES-EDE3的密钥空间为2^168,整整是DES 密钥空间的2^112倍,这么大的密钥空间,以现有的计算能力,还无法在现实的时间里被暴力破解。AES的密钥长度则可以从128、192和256比特中 进行选择,三者的密钥空间也是不小的。
密钥和明文其实是等价的,因为对攻击者来说,得到密钥就等价于得到明文。
各种不同的密钥
从前两篇文章我们就知道,密钥分很多种类,这里我们做一下整理。
在对称密码中,加密和解密使用的是相同的 共享密钥 。而在公钥密码中,加密用的是 公钥 ,解密用的则是 私钥 ,相对应的公钥和私钥组为 密钥对 。消息认证码使用的也是共享密钥。而数字签名使用的和公钥密码一样是密钥对,用私钥签名,用公钥验证签名。混合密码系统中还使用了一次性密钥,称为 会话密钥 。而相对于每次通信都更换的会话密钥,一直被重复使用的密码则称为 主密钥 。用于加密内容的密钥称为CEK (Contents Encrypting Key,内容加密密钥);相对地,用于加密密钥的密钥则称为 KEK (Key Encrypting Key,密钥加密密钥)。CEK 和 KEK 的用法可以如下图所示:
在很多情况下,会话密钥都是被作为 CEK 使用的,而主密钥则是被作为 KEK 使用的。
密钥的管理
生成密钥最好的方法就是使用真正的随机数,因为密钥需要具备不可预测性。不过,一般我们都是使用伪随机数生成器来生成密钥。另外,密码学用途的伪随机数生成器必须是专门针对密码学用途而设计的。毕竟,生成伪随机数的算法很多,但有些并不具备不可预测性。
有时我们也会使用容易记住的 口令 (password 或 passphrase)来生成密钥。passphrase 指的是一种由多个单词组成的较长的 passwrod,在此将两者统称为口令。严格来说,很少直接用口令来作为密钥使用,一般都是将口令输入单向散列函数,然后将得到的散列值作为密钥使用。 而在使用口令生成密钥时,为了防止字典攻击,需要在口令上面附加一串称为 盐 (salt)的随机数,然后再将其输入单向散列函数。这种方法称为“基于口令的密码”(Password Based Encryption, PBE)。关于 PBE 稍后再详细介绍。
对于共享密钥,就会存在密钥配送问题。在密码篇就提到几种解决方案: 事先共享密钥 、 使用密钥分配中心 、 使用公钥密码 、 Diffie-Hellman密钥交换 。关于Diffie-Hellman密钥交换的原理,之前的文章没讲,在本篇稍后会详细介绍。
为了提高通信的机密性,还可以采用 密钥更新 (key updating)的方法。这种方法就是在使用共享密钥进行通信的过程中,定期改变密钥。例如,在更新密钥时,发送者和接收者使用单向散列函数计算当前密钥的散列值,并将这个散列值用作新的密钥。简单说,就是 用当前密钥的散列值作为下一个密钥 。
除了只使用一次的会话密钥,其他密钥基本都需要考虑 保存密钥 的问题。尤其对于共享密钥来说,很多应用都需要将密钥保存在客户端,例如移动App,要么将密钥硬编码在代码里,或者保存在文件中,但无论哪种方式,应用一旦被反编译,密钥就存在泄漏的风险。以防密钥被盗,可以使用 将密钥加密后保存 的方法。但要将密钥加密,必然需要另一个密钥,即 KEK。那么,KEK 又如何保存?这问题还真不好解决。不过,对密钥进行加密的方法却可以 减少需要保管的密钥数量 。比如,假设平台系统接入了10万个应用,每个应用都有一个自己的密钥,即系统需要保管10万个密钥。那么,用 KEK 对这10万个密钥进行加密,这样的话只要保管这一个 KEK 就可以了。即是说,不需要确保多个密钥(CEK)的机密性,而只需要确保一个密钥(KEK)的机密性就可以了。这和认证机构的层级化非常相似。在后者中, 我们不需要信任多个认证机构,而只需要信任一个根 CA 就可以了。
Diffie-Hellman密钥交换
通过Diffie-Hellman密钥交换算法,通信双方仅通过交换一些可以公开的信息就能够生成出共享的秘密数字,而这一秘密数字就可以被用作 对称密码的密钥。虽然这种方法名字叫“密钥交换”,但实际上双方并没有真正交换密钥,而是通过计算生成出了一个相同的共享密钥。因此,这种方法也称为** Diffie-Hellman密钥协商**。
Diffie-Hellman密钥交换的步骤如下:
Alice 向 Bob 发送两个质数 P 和 G
P 必须是一个非常大的质数,而 G 则是一个和 P 相关的数,称为 生成元 (generator)。G 可以是一个较小的数字。
Alice 生成一个随机数 A
A 是一个 1 ~ P-2 之间的整数。这个数是一个只有 Alice 知道的秘密数字。
Bob 生成一个随机数 B
B 也是一个 1 ~ P-2 之间的整数。这个数是一个只有 Bob 才知道的秘密数字。
Alice 将 G^A mod P 计算结果的数发送给 Bob
Bob 将 G^B mod P 计算结果的数发送给 Alice
Alice 用 Bob 发过来的数计算 A 次方并求 mod P
这个数就是共享密钥。Alice 计算的密钥 = (G^B mod P)^A mod P = G^(A*B) mod P
Bob 用 Alice 发过来的数计算 B 次方并求 mod P
Bob 计算的密钥 = (G^A mod P)^B mod P = G^(A*B) mod P = Alice 计算的密钥。可见两方计算的密钥是相等的。
关于第1步提到的生成元是什么呢?先来看一张表,假设 P = 13:
其中,2、6、7、11都是13的生成元。这几个数有什么性质呢?从上表可以发现,这几个数的乘方结果中都出现了1~12的全部整数。也就是 说,P 的生成元的乘方结果与 1 ~ P-1 中的数字是一一对应的。正是因为具有这样一一对应的关系,Alice 才能够从 1 ~ P-2 的范围中随机选择一个数字(之所以不能选择 P-1,是因为 G^(P-1) mod P 的值一定是等于1的)。
最后,需要清楚,针对Diffie-Hellman密钥交换是可以发动中间人攻击的。而为了防止中间人攻击,可以使用数字签名、证书等方法来应对。
基于口令的密码(PBE)
基于口令的密码(Password Based Encryption,PBE)就是一种根据口令生成密钥并用该密钥进行加密的方法。
PBE 的加密可以用下图来表示:
主要有三个步骤:
生成 KEK
首先,通过伪随机数生成器生成一个被称为 盐 (salt)的随机数。然后,将盐和口令一起输入单向散列函数,输出的结果就是 KEK。盐是一种用于防御字典攻击的机制。
生成会话密钥并加密
会话密钥 CEK 也是通过伪随机数生成器来生成,生成之后使用 KEK 对其进行加密,然后将加密后的会话密钥和盐一起保存在安全的地方。
加密消息
最后,使用 CEK 对消息进行加密。
而 PBE 解密的过程则如下图:
解密主要也是有三个步骤:
重建KEK
将之前保存下来的盐和口令一起输入单向散列函数,得到的散列值就是 KEK 了。
解密会话密钥
再将之前保存下来的已加密的会话密钥用 KEK 进行解密,就能得到会话密钥 CEK 了。
解密消息
最后,用已解密的 CEK 对密文进行解密即可。
另外,在生成 KEK 时,通过多次使用单向散列函数可以提高安全性。
随机数
有哪些场景使用到随机数呢?主要可能有以下这些:
生成密钥
生成密钥对
生成初始化向量(IV)
生成nonce
生成盐
随机数的性质主要分为三类:
随机性 :不存在统计学偏差,是完全杂乱的数列。
不可预测性 :不能从过去的数列推测出下一个出现的数。
不可重现性 :除非将数列本身保存下来,否则不能重现相同的数列。
上面三个性质中,越往下就越严格。具备随机性,不代表一定具备不可预测性。具备不可预测性的数列,则一定具备随机性。具备不可重现性的数列,也一定具备不可预测性和随机性。在书中,将这三个性质的随机数按顺序分别命名为“弱伪随机数”、“强伪随机数”和“真随机数”。
伪随机数生成器
随机数可以通过硬件来生成,也可以通过软件来生成。通过硬件生成的随机数列一般都是真随机数,是从不可重现的物理现象中获取信息而生成数列的,比如周围的温度和声音的变化、用户移动鼠标的位置信息、键盘输入的时间间隔、放射线测量仪的输出值等。像这样的硬件设备称为 随机数生成器 (Random Number Generator,RNG)。而生成随机数的软件则称为 伪随机数生成器 (Pseudo Random Number Generator,PRNG)。因为仅靠软件无法生成真随机数,因为要加上一个“伪”字。
伪随机数生成器具有“内部状态”,并根据外部输入的“种子”来生成伪随机数列,如下图:
伪随机数生成器的 内部状态 ,是指伪随机数生成器所管理的内存中的数值。这个数值在每次生成随机数后都会改变。而 种子 是用来初始化内部状态的。伪随机数生成器是公开的,但种子是需要保密的,这就好像密码算法是公开的,但密钥是保密的。
具体的伪随机数生成器
具体的伪随机数生成器有很多,书中介绍了五种:杂乱的方法、线性同余法、单向散列函数法、密码法、ANSI X9.17。
杂乱的方法
杂乱的方法就是使用杂乱无章的算法来生成随机数,但这种方法其实并不可取。一是因为复杂算法所生成的数列大多数具有很短的周期(即短数列的不断重复);二是因为如果程序员不能够理解算法的详细内容,那么就无法判断所生成的随机数是否具备不可预测性。
线性同余法
线性同余法就是将当前的伪随机数值乘以 A 再加上 C,然后将除以 M 得到的余数作为下一个伪随机数。其中,A、C、M都是常量,且 A 和 C 需要小于 M。C 语言的库函数 rand,以及Java 的 Random 类,都采用了线性同余法。线性同余法并不具备不可预测性,因此不可以用于密码技术。
单向散列函数法
使用单向散列函数可以编写出具备不可预测性的伪随机数列(即强伪随机数)的伪随机数生成器。单向散列函数的单向性是支撑伪随机数生成器不可预测性的基础。
密码法
也可以使用密码来编写能够生成强伪随机数的伪随机数生成器。既可以使用 AES 等对称密码,也可以使用 RSA 等公钥密码。密码的机密性是支撑伪随机数生成器不可预测性的基础。
ANSI X9.17
ANSI X9.17 伪随机数生成器的结构则有点复杂,PGP 密码软件就使用了这种方法。
PGP
PGP 将多种密码技术进行了完美的组合,其具备了现代密码软件所必需的几乎全部功能,包括但不限于:对称密码、公钥密码、数字签名、单向散列函数、证书、压缩、大文件的拆分和拼合、钥匙串管理等。
生成密钥对
要在 PGP 中进行加密和数字签名,需要先生成自己的密钥对。下图展示了从命令行生成密钥的过程,其中,粗体为用户输入的内容:
加密和解密
使用 PGP 进行加密的过程如下图所示:
而解密的过程则如下:
PGP 的私钥是保存在用户的钥匙串中的。另外,私钥都是以加密状态保存的,并在保存时使用了基于口令的密码(PBE)。
生成和验证数字签名
生成数字签名的过程如下图:
而验证签名的过程则如下图:
生成数字签名并加密以及解密并验证数字签名
如何将密码和数字签名进行组合,下面两张图是整本书最复杂的,但它只不过是将之前讲解的内容组合起来了而已。
下图是生成数字签名并加密的过程:
而下图则是解密并验证数字签名的过程:
信任网
如何确认公钥的合法性?前面介绍的证书是一种方法。对公钥的信任是建立在对认证机构的信任的基础之上的。不过,PGP 却没有使用认证机构,而是采用了一种叫做 信任网 (也称为 信任圈 或 好友圈 )的方法。信任网的要点是“不依赖认证机构,而是建立每个人之间的信任关系”。换言之,就是能够自己决定要信任哪些公钥。
PGP 当初的设计目的是在连国家都不可信的情况下依然能够使用,因此它并不关心有没有可信的认证机构,而是采用了“由用户自己来决定信任谁”这样的设计。
需要注意,“公钥是否合法”与“所有者是否可信”是两个不同的问题,因为尽管公钥合法,其所有者也可以是不可信的。例如,Alice认为从Bob 那获得的公钥是合法的,因为这个公钥是Bob当面交给Alice的。但是Alice不信任Bob在数字签名上的判断能力,即便Bob对其他的公钥进行了数 字签名,Alice也会怀疑Bob是否真的进行了本人确认。
在 PGP 中,信任级别可以分为四种:绝对信任、完全信任、有限信任和不信任。
SSL/TLS
SSL/TLS也是综合运用了对称密码、公钥密码、消息认证码、数字签名、伪随机数生成器等密码技术。而我们知道SSL/TLS最广泛的应用就是 套接在HTTP上,但实际上,SSL/TLS还可以套接在其他网络协议之上的,例如,SMTP 和 POP3 之类的电子邮件协议。因为现在广泛使用的是TLS协议,因此下文只以TLS协议为主。
TLS安全协议可分为两层: TLS记录协议 和 TLS握手协议 。TLS记录协议在TLS握手协议的下层,负责数据封装、压缩、加密等功能。而TLS握手协议则用于在实际的数据传输开始前,通信双方进行身份认证、协商 加密算法、交换密钥等。TLS握手协议又分为4个子协议:握手协议、密码规格变更协议、警告协议和应用数据协议。TLS协议的层次结构如下图:
TLS记录协议
TLS记录协议的处理过程如下图:
首先,消息被分割成多个片段,然后分别对每个片段进行压缩。压缩算法需要与通信对象协商决定。接下来,经过压缩的片段会被加上消息认证码,这就 可以保证完整性,并进行数据的认证。同时,为了防止重放攻击,在计算消息认证码时,还加上了片段的编号。单向散列函数的算法,以及消息认证码所使用的密钥 都需要与通信对象协商决定。再接下来,就是加密了。加密使用CBC模式,CBC模式的初始化向量(IV)通过主密码生成,而对称密码的算法和共享密钥也是 需要与通信对象协商决定。最后,密文再加上数据类型、版本号、压缩后的长度组成的报头就是最终的报文数据。其中,数据类型为TLS握手协议中的4个子协议 之一。
TLS握手协议
TLS握手协议可分为4个子协议,其中, 握手协议 是最复杂的一个子协议,其过程如下图:
1. ClientHello(客户端->服务器)
客户端向服务器发送ClientHello消息,消息内容主要包括:可用的版本号、当前时间、客户端随机数、会话ID、可用的密码套件清单、可用的压缩方式清单。
2. ServerHello(服务器->客户端)
对于客户端发送的ClientHello消息,服务器会返回一个ServerHello消息,消息内容主要包括:使用的版本号、当前时间、服务器随机数、会话ID、使用的密码套件、使用的压缩方式。这一步确定了通信中使用的“版本号”、“密码套件”和“压缩方式”。
3. Certificate(服务器->客户端)
服务器再向客户端发送Certificate消息,主要是 证书清单 。首先发送的是服务器的证书,然后会按顺序发送对服务器证书签名的认证机构的证书。
4. ServerKeyExchange(服务器->客户端)
当Certificate消息不足以满足需求时,服务器会向客户端发送ServerKeyExchange消息。具体所发送的消息内容会根据所使用的密码套件而有所不同。
5. CertificateRequest(服务器->客户端)
CertificateRequest消息用于服务器向客户端请求证书,这是为了进行 客户端认证 。消息内容还包括:服务器能够理解的证书类型清单和认证机构名称清单。当不使用客户端认证时,不会发送CertificateRequest消息。
6. ServerHelloDone(服务器->客户端)
服务器发送ServerHelloDone消息则表示从ServerHello消息开始的一系列消息的结束。
7. Certificate(客户端->服务器)
当服务器发送了CertificateRequest消息时,则客户端会发送Certificate消息,将自己的证书同消息一起发送给服务器。如果服务器没有发送CertificateRequest消息,客户端则不会发送Certificate消息。
8. ClientKeyExchange(客户端->服务器)
客户端发送ClientKeyExchange消息。当密码套件中保护RSA时,会随消息一起发送 经过加密的预备主密码 。当密码套件中包含Diffie-Hellman密钥交换时,会随消息一起发送 Diffie-Hellman的公开值 。预备主密码是由客户端生成的随机数,之后会被用作生成主密码的种子。根据预备主密码,服务器和客户端会计算出相同的 主密码 ,然后再根据主密码生成:对称密码的密钥、消息认证码的密钥、对称密码的CBC模式中使用的初始化向量(IV)。
9. CertificateVerify(客户端->服务器)
客户端只有在服务器发送CertificateRequest消息时才会发送CertificateVerify消息。这个消息的目的是向服务 器证明自己的确持有客户端证书的私钥。为了实现这一目的,客户端会计算“主密码”和“握手协议中传送的消息”的散列值,并加上自己的数字签名后发送给服务 器。
10. ChangeCipherSpec(客户端->服务器)
客户端发送ChangeCipherSpec消息表示要切换密码。实际上,这不是握手协议的消息,而是密码规格变更协议的消息。在 ChangeCipherSpec消息之前,客户端和服务器之间以及交换了所有关于密码套件的信息,因此在收到这一消息时,客户端和服务器会同时切换密 码。在这一消息之后,TLS记录协议就开始使用双方协商决定的密码通信方式了。
11. Finished(客户端->服务器)
客户端发送Finished消息表示握手协议到此结束。这个消息其实是使用切换后的密码套件来发送的。实际负责加密操作的是TLS记录协议。
12. ChangeCipherSpec(服务器->客户端)
这次轮到服务器发送ChangeCipherSpec消息了,表明服务器要切换密码了。
13. Finished(服务器->客户端)
服务器也同样发送Finished消息表明握手协议到此结束。这个消息同样使用切换后的密码套件来发送。实际负责加密操作的也是TLS记录协议。
14. 切换至应用数据协议
在此之后,客户端和服务器会使用应用数据协议和TLS记录协议进行密码通信。
从结果来看,握手协议完成了下列操作:
客户端获得了服务器的合法公钥,完成了服务器认证。
服务器获得了客户端的合法公钥,完成了客户端认证(当需要客户端认证时)。
客户端和服务器生成了密码通信中使用的共享密钥。
客户端和服务器生成了消息认证码中使用的共享密钥。
除了握手协议,其他3各子协议都很简单。 密码规格变更协议 用于密码切换的同步。简单地说,就跟向对方喊“1、2、3!”差不多。当协议中途发生错误时,就会通过警告协议传达给对方。 警告协议 负责在发生错误时将错误传达给对方。如果没有发生错误,则会使用应用数据协议来进行通信。 应用数据协议 用于和通信对象之间传送应用数据。当TLS套接在HTTP时,HTTP的请求和相应就会通过TLS的应用数据协议和TLS记录协议来进行传送。
密码技术与现实社会
前面讲到的6种基本的密码技术可整理成下图:
书中多次使用了 框架 这个说法。框架的特点就是能够对其中作为组成元素的技术进行替换,就像更好零件一样。例如,消息认证码算法HMAC的设计就允许对单向散列函数的算法进行 替换。在PGP中,对称密码、公钥密码、单向散列函数等都是可以替换的。在SSL/TLS中,客户端和服务器可以通过握手协议进行通信,并当场决定所使用 的密码套件。使用框架能够提高密码技术系统的重用性,也能够提高系统的强度。通过将单独的密码技术像零件一样组合起来,并根据需要进行替换,能够实现更长 期的、更高的安全性。
另外,所有密码技术其实也可以看成是一种“压缩技术”,如下表所示:
量子密码和量子计算机
量子密码是基于量子理论的通信技术,是一种让通信本身不可窃听的技术,也可以理解为是一种利用光子的量子特性来实现通信的方法。最早的量子密码中,利用了两个事实:
1. 从原理上说,无法准确测出光子的偏振方向
根据这一事实,可以让窃听者得到的内容变得不正确。
2. 测量行为本身会导致光子的状态发送改变
根据这一事实,接收者可以判断出通信是否被窃听。
而 量子计算机 则有着超强的计算能力。如果有了量子计算机,那现有的所有密码都能够瞬间被暴力破解。根据量子理论,粒子可同时具有多种状态。如果使用具有多种状态的粒子 进行计算,则可以同时完成多种状态的计算。如果用1个粒子能够计算0和1两种状态,那么用128个这样的粒子就可以同时计算2^128中状态。换句话说, 就是一台超级并行计算机。
如果量子密码比量子计算机先进入实用领域,则可以使用量子密码来实现一次性密码本,从而产生完美的密码技术。由于一次性密码本在原理上是无法破译 的,因此即使用量子计算机也无法破译量子密码。然而,如果量子计算机比量子密码先进入实用领域,则实用目前的密码技术所产生的密文将会全部被破译。
只有完美的密码,没有完美的人
就算量子密码进入实用领域,也不能实现完美的安全。因为在安全问题中,密码技术能够覆盖的范围是非常有限的。在确保系统的整体安全方面,人是一个特别巨大的弱点。
为了配送对称密码的密钥,我们需要使用公钥密码,而为了对公钥进行认证,我们又需要认证机构的公钥。以此类推,无穷无尽,我们必须在某个节点上找到一个公钥是自己能够完全信任的,也就是必须要有一个信任的种子。
通过密码技术,我们可以提高机密性,也能够让认证变得更加容易,但是这并不意味着我们可以实现完美的机密性和完美的认证。
就算通过人的指纹、声纹、面容识别等生物识别认证也并不是完美的认证。要进行生物识别认证,就必须在某个时间点上将生物信息转换为比特序列,而实际的认证则是通过转换后的比特序列来完成的。因此,如果这些比特序列被窃取,就会和钥匙被偷产生相同的后果。
另外,“防御必须天衣无缝,攻击只需突破一点”。为了保卫系统安全,我们必须应对各种可能的攻击,而且这种防御必须24小时连续工作。另一方面,要攻击一个系统,则只要找到一种有效的攻击方法,而且只需利用防御方一瞬间的破绽就可以完成了。
写在最后
其实,在实际应用中,安全问题所涉及的技术,远比这本书里所讲到的密码技术多得多,也复杂得多。例如,App的加壳保护、OAuth认证等。在实 际的应用中,还需要考虑更多,比如,安全性和性能之间需要平衡。虽然,懂得了这些密码技术,并不意味着就能设计出非常安全的系统。但是,如果不懂这些密码 技术,那就更难以设计出安全的系统。
上一篇: Map接口的使用