This commit is contained in:
louzefeng
2024-07-09 18:38:56 +00:00
parent 8bafaef34d
commit bf99793fd0
6071 changed files with 1017944 additions and 0 deletions

View File

@@ -0,0 +1,167 @@
<audio id="audio" title="06对称密钥如何保护私密数据" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/4c/3c/4caeb75ddb32fa93aaba177db0b7ea3c.mp3"></audio>
你好,我是范学雷。
在上一个模块,我们学习了单向散列函数。从今天开始,我们将开启一个新的模块,在这个模块里,我将跟你讨论加密技术的相关知识。是不是感觉上一个模块的学习还意犹未尽?
别着急,单向散列函数还会出现在我们的视野里。那么,加密技术是用来做什么的呢?
还记得上一讲,我们讨论了单向散列函数的使用场景吗?其中,**一个重要的限制是<strong><strong>我们**</strong>需要确保给定的散列值不能被修改</strong>。这个简单、直观的限制,给单向散列函数的使用套上了一个紧箍咒。
这说明在很多场景下,我们并不能仅仅使用单向散列函数来解决数据的完整性问题。要想去掉这个紧箍咒,扩大单向散列函数的适用场景,我们还需要其他技术,比如加密技术。
那加密技术是怎么帮助单向散列函数解决完整性问题的?这个疑问立即就来到了我们面前。不过不用担心,我们需要一点时间来了解这个问题,以及解决问题的办法。
今天,我们先来讨论第一类加密技术:对称加密技术。
## 什么是加密?
在讨论对称加密技术之前,我们要先了解加密、解密和密钥这几个概念。
其实这几个概念还是很容易理解的。把信息或者数据伪装、隐藏起来,转换成难以解释的信息或者数据,这个过程叫做**加密**。和加密这个过程相反的过程,就叫做**解密**。
一般来说,加密产生的那个难以解释的信息或者数据,我们把它叫做**密文Ciphertext**。对应的,加密前的数据,我们通常把它叫做**明文Plaintext**。
密文信息通常看起来都是晦涩难懂、毫无逻辑的,所以我们一般会通过传输或者存储密文信息,来保护私密数据。当然,这建立在一个假设基础上:没有经过授权的人或者机器,很难通过密文计算出明文;经过授权的人或者机器,才能够通过密文计算出明文。
那经过授权的人或者机器,是怎样通过密文计算出明文的?对,就是使用**密钥**。
在现代密码学里,**密钥是在加密和解密运算里,决定运算结果的一段信息**。因为,加密要使用密钥把明文信息转换为密文;解密要使用密钥把密文复原为明文。
也就是说,加密运算需要两个输入:密钥和明文。
<img src="https://static001.geekbang.org/resource/image/d1/51/d1642ccc8074434a09d41cf8c360af51.jpg" alt="">
解密运算也需要两个输入:密钥和密文。
<img src="https://static001.geekbang.org/resource/image/6b/1a/6bff070fd3a2c96cc7b4caed4052d61a.jpg" alt="">
如果没有密钥,我们就没有办法执行解密运算,也就很难把密文转换成明文。同理,如果只有授权的人或者机器才知道密钥,那么没有授权的人也很难通过密文计算出明文。
你可能会觉得,密钥太重要了!但现代密码学之前的加密,其实不是这样设计的。
历史上的加密,是没有密钥的。数据的保密性,依赖于算法的保密性。一旦算法被破解,数据也就被破解了。如果有一天,时光穿梭机真的实现了,我们穿越回去,偷听一下、偷看一下当初设计者的算法设计,算法就被破解了(时光穿梭机的梗,你可以自己搜索一下)。
其实也用不着这么科幻,就算时光穿梭机实现不了,我们也还有很多更有效的办法:
- 当初算法的设计者还健在吗?
- 当初算法的实现者还健在吗?
- 算法实现的代码还在吗?
- 算法运行的环境还在吗?
解决掉其中任何一个问题,我们就能破坏掉算法的保密性。而且,这些破解办法通常没有什么难度,比制造时光机有效率多了。除此之外,还要说一点,虽然算法保密看起来很安全,但是这也意味着只有很少的人知道算法,这样的算法质量也是值得担忧的。
**到了<strong><strong>现代密码学,加密数据的安全性就**</strong>依赖于加密算法的质量和密钥的保密性这两个因素</strong>。密钥部分,是私有的部分,需要严格保密;算法部分,变成了公开的部分,要接受公开讨论、评测,接受各种分析和攻击。**一个算法,如果<strong><strong>在**</strong>接受**<strong>了**</strong>公开的分析、评测和各种各样的攻击**<strong>之后**</strong>,还依然被认为是安全的,**<strong>我们才能说,这个算法**</strong>的安全性是真的经得起考验的。</strong>
为什么算法一定要公开?不公开不行吗?为了可以让你更直观地了解使用公开算法有多重要,我们一起来看看公开算法的遴选过程是怎样的。
<img src="https://static001.geekbang.org/resource/image/fd/a1/fd0074a5351eb4be97c2ea3e795aa3a1.jpeg" alt="">
所以你看仅仅单向散列函数的遴选就花费了7年时间还聚集了世界上最出色的密码学家和密码分析专家。在遴选标准中有一个重要指标就是有没有足够多的密码分析。
什么是密码分析?**密码分析,指的是分析、评测一个密码学算法,有没有安全缺陷和适用场景的限制。如果一个算法,没有人对它展开分析、评测,或者缺少足够的分析,它的安全性很难获得信任**。63个落选的算法中不乏知名密码学专家或者知名团队和组织的撑腰。
我相信,这些算法在提交之前,它的发明者都是信心满满的。可是一旦接受了公开的分析和评测,很多意想不到的安全缺陷就暴露出来了。
但是,保密的算法,如果没有经过大量密码分析专家的分析,是很难给人信心的。可如果经过了大量的、不同的密码分析专家的分析,保密算法也算不上保密了。的确,这是一件很矛盾的事情,有时候却又不得不这样。
所以,渐渐地,**使用公开的算法<strong><strong>是**</strong>密码学领域的**<strong>一个**</strong>基本常识。**<strong>不过,一个**</strong>现代密码学算法的安全性,都是基于密钥的保密,而不是算法保密要求</strong>。遗憾的是,仍然有很多保密算法的存在和使用。对于这样的使用,我们很难有信心相信它的安全性。
为什么我们要花费这么大篇幅去讨论公开算法的遴选过程呢?
其实是因为,我想让你对以下两个密码学常识留下深刻的印象:
- **不要自己发明密码算法,尤其是<strong><strong>在**</strong>没有经过充分讨论、充分分析的情况下</strong>。大部分情况下,我们自行发明的密码学算法都是灾难。
- **不要把安全性寄托在算法的保密上**。大部分情况下,保密的算法都是无法保密,并且是不堪分析的。
在这个部分里,我最后再强调一下:**现代的密码学算法的安全性,都是基于密钥的保密,而不是算法保密要求**。管理好密钥,做好密钥的保密,才是密码学系统最关键的任务。
## 什么是对称密钥?
讨论完加密和密钥,我们就要来看看对称密钥技术。
说起来对称密钥就不得不提它的对立面非对称密钥。1976年惠特菲尔德·迪菲Whitfield Diffie、马丁·赫尔曼Martin Hellman发表了基于非对称密钥技术的密钥交换算法但是在这之前并没有对称密钥、非对称密钥的说法。
1976年之前密码学就是一门研究对称密钥的学问。所以我们看的二战时期的谍战片如果里面提到了发报机和密钥用的肯定不会是非对称密钥技术。这种影响到现在还有比如当我们使用密钥这个词汇时一般指的就是对称密钥。
**对称密钥,顾名思义,就是每一个参与者都持有相同的密钥,使用相同的密钥。**
<img src="https://static001.geekbang.org/resource/image/e6/c3/e6eb19e271ecce7bedf629792d7ddfc3.jpeg" alt="">
**非对称密钥,<strong><strong>就是指**</strong>每一个参与者都持有不同的密钥,使用不同的密钥。</strong>
<img src="https://static001.geekbang.org/resource/image/ba/eb/bab7b6ae0c35d2a5dbb56c32d3f139eb.jpeg" alt="">
经常有人问我,密码属于对称密钥技术吗?如果属于的话,为什么密码学不叫密钥学?密码和密钥有什么区别吗?所以,在这里,我要稍微地强调一下。
密钥和密码是两个特别容易混淆,而且经常混淆的概念。比如密码学明明是研究密钥的,偏偏叫“密码”学;密码分析明明是研究加密算法的,偏偏叫“密码”分析。怎么理解呢?
有一个技巧,就是**借助英语词汇**这两个概念一下子就会清晰。通常地密码的英语词汇是Password加解密算法的英语词汇是Cipher密钥的英语词汇是Key。
密码Password使用中文里的“口令”更为贴切。比如三国时期的曹操据说就使用过“鸡肋”作为口令。口令该怎么用呢如果执勤的士兵问“口令”杨修回答“鸡肋”这就可以获得通行许可了。如果回答的不是“鸡肋”就不能获得通行还可能被逮起来进一步审查。
和对称密钥类似,需要每一个参与者都知道相同的口令,使用相同的口令。
而密钥Key和密码Password的区别在于它们的用法。口令的用法是对照口令本身。士兵知道口令是“鸡肋”然后对照他人的回答是不是“鸡肋”。
我们上网输入的用户名和密码,也是系统要直接地或者间接地进行对照,登录者是不是使用了系统记录的口令。而密钥的用法,则是参与加密运算或者解密运算。
通常地,我们也不用纠结别人是不是能准确地使用好“密码”和“密钥”这两个词汇。我们只要从它们的使用场景来判断,到底是用作对比的口令,还是用来运算的密钥就好。
另外在生活中我们总是在记口令比如我们登录网站的密码。和自然界其他生物相比人类的记忆能力值得自豪。我们通常可以记住六位的数字或者学过的单词。不过即便如此我们还是倾向于选择“123456” “888888”或者生日这样的简单口令。
稍微复杂的口令,就超越我们的记忆能力了,更别提要记住很多网站的很多口令了。在现代计算机的眼里,这样的记忆能力实在太渣了,用最不讲究技巧的蛮力攻击也就是分分钟钟的事情。
密码需要记但是一般来说我们不需要记住密钥事实上我们也记不住。现代的密钥通常需要至少128位没有规律的字符而且频繁更换。比如下面的5个密钥其实是质量不太好的、便于记住的128位的密钥你可以挑战挑战看看能不能记得住
```
密码1: Yq3t6w9z$C&amp;F)J@N
密码2: gVkYp3s6v9y$B&amp;E)
密码3: NdRgUkXp2s5v8y/B
密码4: -JaNcRfUjXn2r5u8
密码5: C&amp;F)J@NcQfTjWnZr
```
我们当然记不住这么复杂的密钥,除非是不世的天才。所以,我们才要拜托计算机替我们记住这么复杂的信息,并且自动地更换。
## 密钥管理的烦恼
如果我们拜托计算机替我们管理密钥,方便是方便了,但是也会立即衍生出很多现实的问题:
- 计算机替我们记住了密钥,计算机能够保持密钥的保密性吗?
- 计算机会不会出卖我们?
- 使用密码的程序会不会泄漏密钥?
- 运行算法的环境能不能泄漏密钥?
- 退役的机器里,会不会有密钥存留导致密钥泄漏?
- ……
无论哪个问题没有处理好,密钥的保密性可能都只是空谈。
**既然现代的密码学算法的安全性依赖于密钥的保密,那么,管理好密钥,做好密钥的保密,就是密码学系统最关键的任务**。不过,密钥的管理,部分内容已经超出了密码学的范畴,我们需要在计算机基础的操作系统和编程语言里找答案。
比如,在[《代码精进之路》](https://time.geekbang.org/column/intro/100019601)里,我们提到的管理敏感信息的原则,同样适用于密钥的管理。要把密钥当作超级敏感的信息来看待,在我们的代码里保护好密钥,不要泄漏密钥信息。
之后,我们还会讨论对称密钥的管理,以及怎么使用密码学的技术降低密钥管理的难度和风险。
像单向散列函数一样,不同的加密算法也有不同的安全强度。那么,到底哪些对称密钥的算法是我们可以信赖的呢?这是我们下一次要讨论的问题。
## Take Away今日收获
今天,我们讨论了什么是加密解密、什么是对称密钥、密钥保密的必要性,以及密钥管理的困难。通过今天的讨论,我们知道要把密钥当作超级敏感的信息来处理。
这需要我们在编写代码的时候要特别留意密钥的无意识泄露以及在内存、硬盘里的长时间驻留。比如说用完密钥后我们的代码一定要把密钥占用的内存清零而不要依赖类似Java垃圾收集器这样的机制。再比如说我们千万不能把密钥写到系统或者应用的日志里日志可是泄露密钥的最便捷路径之一。
通过今天的讨论,我们要:
- 理解什么是对称密钥;
- 知道对称密钥的安全性,取决于密钥的保密性和算法的安全性,而不是算法的保密性。
- 要把密钥当作超级敏感的信息来处理,做好密钥的保密。
## 思考题
你还见过哪些坑?处理敏感信息,你有哪些经验?另外,你有没有发明过密码算法?你了解的项目有没有发明过密码算法?这些算法有能够替代的公开算法吗?
欢迎在留言区留言,分享你的经验。参与讨论的人越多,我们互相学习、互相启发的就越多。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,100 @@
<audio id="audio" title="07 | 怎么选择对称密钥算法?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/77/fb/7761a053fb4cf17b8d4552490ebcyyfb.mp3"></audio>
你好,我是范学雷。
上一讲,我们讨论了什么是对称密钥,你还记得决定对称密钥系统安全性的两个关键因素吗?**密钥的保密性和算法的安全性**。和单向散列函数一样,我们也要分析如何选择对称密钥算法。
所以,有哪些算法是值得我们信任的呢?这就是我们这一次要解决的问题。首先,我们还是先来一起看看曾经流行的和现在流行的对称密钥算法。
## 数据加密影响性能吗?
像单向散列函数一样,我们可以把对称密钥算法也按照退役的、遗留的以及现行的算法来分类。
在下面的表格里我给你总结了常见的一些算法以及一些相关的信息。其中计算性能参考的是ECRYPT性能基准测试在2020年7月对较长数据的运行结果。
对于不同的系统,这个计算性能数据差距可能很大,不过,它足以让我们有一个大致的感受。
<img src="https://static001.geekbang.org/resource/image/31/0d/31111yyb2864f3a0a0f93d5caccfcf0d.jpg" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/e4/89/e4c551767a3225181d750f229c45bc89.jpg" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/3d/fc/3d328a4af3d39ea490a09f1cc0812afc.jpg" alt="">
(文末附各个算法的参考文献链接)
如果我们关注一下上表里的计算性能每个字节的加密、解密运算需要大约0.5个时钟周期。你看这个数字,对比我们在前面提到的单向散列函数的计算性能,我们可以感受到,加密、解密运算是一种很快的运算。之后,我们还会交代非对称密钥的计算性能,你也会有同样的感受。
很快的运算也是需要额外的运算的。对比数据不加密和数据加密计算性能的影响到底有多大呢这个答案当然依赖于具体的环境。不过我们可以看一个最常见的场景感受一下数据加密给计算性能带来的影响。这个场景就是我们常用的互联网Web服务。
现在的Web服务大部分都是运行在HTTPS协议上的。和HTTP协议相比在HTTPS协议通道上传输的应用数据都是加密的数据。有测试数据表明和HTTP相比一个合理配置的HTTPS服务客户端的响应时间没有明显的变化服务器的吞吐量大约减少了5%到8%CPU的使用大约增加了2%到5%。
我们可以看到数据加密会影响性能但是这个影响并不显著。这也是近年来绝大多数Web服务迁移到HTTPS协议的背后技术支撑。十多年前大部分Web服务还是运行在HTTP协议上传输数据没有加密用户的数据经常就莫名其妙地丢失了Web服务网页动不动就被黑了。
现在如果一个Web服务还没有转换到HTTPS协议上这个服务的安全性是不能让人放心的。
接下来我们看下面的这张图显示的是在不同的地域使用HTTPS的网页的百分比。这张图是由Google根据Chrom浏览器加载Web页面的数据统计绘制的。
我们能看到在2015年不足50%的Web页面使用HTTPS协议。到2020年领先的几个地域已经有大约95%的Web页面使用了HTTPS协议。总的来说在短短的五年时间里Web服务整体的安全性有了巨大的提高。
也许再过五年就很难找到不使用加密通道的Web服务了。这是互联网消费者的幸事
<img src="https://static001.geekbang.org/resource/image/cd/77/cdfe5af9554994738c5515afe78d4877.png" alt="">
## 序列算法和分组算法
不知道你注意到没有在上面的表格里RC4和ChaCha20没有数据分块数据而3DES和AES有数据分块数据。这是因为RC4和ChaCha20是序列算法3DES和AES是分组算法。那么问题来了序列算法和分组算法有什么不一样的呢
我们前面讲过,为了能够处理任意大小的数据,并且输出结果长度固定,单向散列函数需要对数据进行分组,然后按数据组进行运算。在对称密钥算法里,因为输出结果的长度没有限制,对数据的处理方式,也就有了更多的想象空间。
如果我们从数据是否分组这个角度考虑,就有两种处理方式。
- 进行数据分组,然后按数据组运算,这就是分组算法;
- 不进行数据分组,按照原始数据的大小进行运算,这就是序列算法。
那么,怎么计算序列算法呢?
序列算法的基本思路,就是从对称密钥里推导出一段和明文数据相同长度的密钥序列,然后密钥序列和明文进行亦或运算得到密文,和密文进行异或运算得到明文。
<img src="https://static001.geekbang.org/resource/image/4c/d2/4cbebbcebbb39yy413861a9637ce81d2.jpg" alt="">
序列算法的关键,是怎么从固定长度的对称密钥推导出参与运算的任意长度的密钥序列。一般来说,密钥序列的推导和异或运算都是快速的运算。所以,序列算法通常被认为是更高效的算法。
比如说在类似的条件下相同安全强度的对称密钥ChaCha20算法要比AES算法快四到六倍比Camellia算法快近十倍。如果你关注过浏览器底层技术细节你可能会注意到ChaCha20是现代主流浏览器优先选择的加密算法。
由于不需要数据分组,序列算法的安全性主要取决于密钥序列的推导算法,而不用考虑数据分组带来的种种陷阱。对于应用程序而言,这是一个便于使用,不易出错的选择。
分组算法的安全性,除了算法本身之外,还取决于数据分组的策略。应用程序需要同时指定分组算法和分组策略。对于不太了解密码技术的细节的程序员来说,这实在是一个不小的挑战。
**良好的性能,以及皮实的用法,这是我倾向于优先使用序列算法的两个基本原因**。同样,按照这两个标准,同时考虑安全强度,我建议你使用下面的对称密钥算法,按照优先级从高到低排列:
- ChaCha20
- AES-256
- AES-128
不过,这个建议的实用性还欠考虑,因为我们还没有考虑数据分组的影响,以及其他的一些因素。对于分组算法,数据分组到底有什么样的麻烦?对于序列算法,还有什么因素需要考虑?上面的推荐该如何修正?是我们之后要讨论的话题。
## Take Away今日收获
今天,我们罗列了常见的对称密钥算法,讨论了数据加密对应用性能的影响,还知道了分组算法和序列算法这两个概念。
这一讲,我们要:
- 知道什么是序列算法,什么是分组算法;
- 知道现在优先选择的三个算法ChaCha20AES-256AES-128
## 思考题
今天留给大家的也是一个需要动手的思考题。我们罗列了常见的对称密钥算法,其中有退役的、遗留的和现行的算法。
在你正在开发的项目中,或者你关注的开放源代码项目中,试着搜索一下这些算法,看看哪些退役的算法还在使用,哪些遗留的算法还在使用。如果我们发现了退役算法和遗留算法的使用,你有没有什么建议?
这是一个能够帮助你解决现有项目历史遗留问题的好办法。欢迎在留言区留言,记录、讨论你的发现和建议。
好的,今天就这样,我们下次再聊。
**附:本讲表格中的各个算法参考文献链接**
- DES[RFC 18229](https://tools.ietf.org/html/rfc1829)
- RC2[RFC 2268](https://tools.ietf.org/html/rfc2268)
- 3DES[RFC 1851](https://tools.ietf.org/html/rfc1851)
- Camellia-128、192、256[RFC 3713](https://tools.ietf.org/html/rfc3713)
- AES-128、192、256[FIPS 197](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf)
- ChaCha20[ChaCha](http://cr.yp.to/chacha/chacha-20080128.pdf)

View File

@@ -0,0 +1,137 @@
<audio id="audio" title="08 | 该怎么选择初始化向量?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/94/00/94c88053d1bcf9c2f11a9447f9f0b300.mp3"></audio>
你好,我是范学雷。
上一讲我们讨论了对称密钥的常见算法还讲到了序列算法和分组算法。还记得吗当时我建议你优先使用序列算法因为它有着良好的性能和皮实的用法。另外我还向你推荐了AES-256和AES-128。
但是,由于我们还没有考虑数据分组等因素的影响,所以这个建议的实用性还有待商榷。那么,这一讲,我们就来看看对于分组算法,到底有哪些麻烦?我们又该怎么避免这些麻烦?
其实,这是一个解决起来很复杂的问题。不过,今天我们可以先对问题建立一个初步的认知。
要知道分组算法有哪些麻烦,就要先知道该怎么计算分组算法。
## 分组算法怎么计算?
我们在上一讲说过,分组算法要对输入数据进行分组,然后按数据分组来进行运算。一个典型的分组算法,一般要由三个部分组成,数据分组、分组运算和链接模式。
<img src="https://static001.geekbang.org/resource/image/55/2f/55e1d4yy57f6c0afbe91a45ed792192f.jpeg" alt="">
我们先来看看数据分组是怎么一回事。
数据分组在加密时会把明文的输入数据分割成加密函数能够处理的数据块。比如AES算法能够处理的数据块大小是128位那么输入数据就要被分割成一个或者多个128位的小数据块。
如果不能整分就要把最后一个分组补齐成128位。这些分组数据的运算结果组合起来就是**密文数据**。解密时,执行相反的操作,把补齐数据去掉,再把数据分组组合成完整的明文数据。
<img src="https://static001.geekbang.org/resource/image/3e/e4/3eee1786e229e6fb3867931cb43010e4.jpeg" alt="">
理解了数据分组,我们再来看分组运算和链接模式。
**分组运算**,意思就是把每一个明文数据分组通过加密函数,转换成密文数据分组。而**链接模式**,指的是如何把上一个分组运算和下一个分组运算联系起来。
有一点需要说,第一个分组运算并没有上一个分组运算可以使用,这时候,我们就需要引入一个初始化的数据,来承担“上一个分组运算”向下链接的功能。这个初始化的数据,我们一般称为**初始化向量**。
那你有没有想过,我们为什么要把上一个分组运算和下一个分组运算联系起来呢?其实,我们在前面讨论过单向散列函数的链接模式,我们说它是为了确保雪崩效应能够延续。
在分组运算里,链接模式也承担类似的功能:
- 不同的明文数据,它的密文数据应该是完全不同的,即使明文数据里包含相同的数据分组;
- 相同的明文数据,每一次的加密运算,它的密文数据也应该是完全不同的。
## 什么影响算法的安全性?
现在,我们已经梳理了一遍分组算法的运算过程了。这样,我们就能够在其中找到影响分组算法的关键因素。这些因素,也就是影响分组算法安全性的因素。
在数据分组里,把输入数据分割成固定大小的数据块这一部分,除了数据补齐之外,没有什么变数。所以,我们可以发现,数据补齐方案才是影响分组算法的关键部分。
这样,我们就不难找出下面的五个因素:
- 加密函数和解密函数;
- 密钥;
- 初始化向量;
- 链接模式;
- 数据补齐方案。
通过上一讲的讨论,我想我们都了解加密函数、解密函数和密钥在分组算法中的重要地位了。如果加密函数不安全,整个分组算法的安全性也就坍塌了;如果密钥没有做好保密或者密钥质量不好,数据的保密性也就无从谈起。
比如说我们经常看到宣传说什么采用了AES-256算法安全强度有保障说什么只有造一台时光机穿越回历史现场才能破解一个应用。这些说法有它的道理但是仅仅依据这些信息还不能确认一个算法的使用和运算是不是安全的。
另外三个因素,就是经常被我们忽视的因素。那么,它们是怎么影响算法安全性的呢?
## 初始化向量怎么选?
我们今天先讨论第一个影响的因素:初始化向量。
我们要想了解初始化向量对算法安全性的影响,就要先看看第一个数据块是怎么计算的,第一个数据块和初始化向量关系紧密。第一个数据块的计算,它的输入信息包括:
- 密钥;
- 初始化向量;
- 第一个明文数据分组。
<img src="https://static001.geekbang.org/resource/image/00/40/00406ae41ab45bd6bf395d32e7133540.jpeg" alt="">
如果我们能够确定这些输入信息,那么输出的第一个密文数据分组也就确定了。
一般来说,一个对称密钥要使用多次,对多个明文数据进行加密运算。如果存在第一个明文数据分组相同的两段数据,并且使用了相同的初始化向量,那么第一个密文数据分组就是相同的。
也就是说,相同的输入,就会有相同的输出。
对于大部分算法而言分组数据块都比较小比如AES算法的分组数据块大小是16个字节。这样在实际应用中就比较容易构造相同的数据块或者存在相同的数据块。
在现实的应用里也存在大量的、重复的、已知的数据比如HTTP协议的头部数据。如果我们需要保密的数据恰好重复了一段已知的明文攻击者就可以根据密文数据是不是相同来猜测、寻找明文数据。这样的话就破坏了数据的保密性。
但是,我们在使用加密运算时,大部分时候都没有办法确定明文数据会不会有重复数据,以及重复数据会不会是一次加密运算的第一个数据块。所以,如果不想暴露重复数据的机密性,我们只能在初始化向量这一个因素上想办法。因为,密钥是相同的,如果第一个明文数据分组也是相同的,只剩下初始化向量这一个输入信息可以控制了。
**在一个对称密钥的生命周期里,初始化向量不能重复,这是使用对称密钥算法的第一个要求。**
这个要求看似简单,其实做起来并不容易。一个单纯的加密算法的实现,一般没有办法记住一个初始化向量有没有用过。这就需要应用程序的开发者自己想办法,常见的办法有两种:
- 使用安全强度足够的随机数作为初始化向量;
- 使用序列数,下一次的初始化向量的数值,比上一次的数字自动加一或者自动减一。
不过,这两种初始化向量的选择,还是各有各的问题,我们需要注意。
第一种,随机数的获取,有时候不是一个有效率的运算。如果随机数发生器选择不当,还会造成加密运算的阻塞,进一步降低加密运算的效率。另外,由于解密需要相同的初始向量,如何在加密端和解密端同步初始化向量,也是一个需要考虑的问题。
一个常见的解决办法,就是把初始化向量和加密数据一起发送给对方。
第二种的话,使用序列数,需要保持序列数的状态,还需要加密运算的同步。不过,序列数状态的保持和同步,除了效率之外,还会衍生出其他的待解决的问题,比如分布式计算环境下的序列数同步问题,比如攻击者会知道每一个初始化向量的问题。
如果你能够看到的问题无法解决,可以考虑使用随机数作为初始化向量。
你看,初始化向量选择充满了复杂性,一般的密钥算法库都不会提供缺省的、自动的初始化向量。**应用程序需要根据使用场景来制定适当的初始化向量选择方案,这是一个容易忽略的要求**。
## 一个密钥能用多少次?
在这一讲的最后,我们来讨论一个话题,一个密钥有没有使用次数的限制呢?为什么要在这一讲讨论这个话题呢?因为,我想,这是一个恰当的时机。
前面,我们讨论了,在一个对称密钥的生命周期里,初始化向量不能重复。也就是说,对于一个算法来说,初始化向量的长度是固定的。长度固定,也就意味着初始化向量的个数是有限制的。
比如一个128位的初始化向量最多有2^ 128个不重复的数值。进一步的说对于这个算法一个密钥最多只能使用2^ 128次。的确看起来2^128是一个巨大的数字一般的应用程序也没有什么机会使用这么多次加密运算。
当然,还有其他因素限制密钥的使用次数。很多限制因素的叠加,就会使得密钥使用的限制数远远低于初始化向量的许可数目。所以,**我们心里一定要知道,密钥是有使用次数限制的,并且要有检查密钥使用次数限制的习惯**。
这是一个不太引人注意的安全陷阱,也是近几年才受到广泛关注的算法安全问题。我们后面还会讨论其他的限制条件,并且我会罗列出来不同算法的使用限制。
之后的两讲,我们就接着今天的话题,看看除了初始化向量之外,链接模式和数据补齐方案是怎么影响对称密钥算法的安全性的?这两个问题的讨论,需要较大篇幅,不过我会带你一起分析。
## Take Away今日收获
今天,通过解构分组算法的运算,我们讨论了影响分组算法安全性的五个关键因素。然后讨论了选择初始化向量应该注意的陷阱,也就是说,在使用对称密钥加密时,初始化向量不能重复。
最后,我们还讨论了一个不太容易受关注的问题,就是密钥是有使用次数限制的。一般的应用程序,密钥使用次数限制不是问题,但是如果你要设计一个广泛使用的协议,还是要考虑密钥这个限制的。密钥使用次数用完之前,一定要更新密钥。
今天,我们应该理解、记住:
- 分组算法的处理过程;
- 影响对称密钥算法安全性的五个关键因素;
- 在一个对称密钥的生命周期里,初始化向量不能重复。
## 思考题
今天的思考题,也是一个动手题。
在你正在开发的项目中,或者你关注的开放源代码项目中,试着搜索一下初始化向量的使用。看一看对于同一个对称密钥,初始化向量会不会重复,有没有可能重复。如果一个对称密钥使用了重复的初始化向量,有没有潜在的安全风险?你有没有什么建议?
欢迎在留言区留言,记录、讨论你的发现和建议。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,205 @@
<audio id="audio" title="09 | 为什么ECB模式不安全" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/9c/d2/9cf06830e1a1776a34949531207170d2.mp3"></audio>
你好,我是范学雷。
上一讲,我们讨论了对称密钥分组算法的计算过程,我们找到了影响对称密钥算法安全性的五个关键因素,以及初始化向量对算法安全性的影响和选择。
不过,还有一些遗留的问题,我们没有来得及讨论,链接模式和数据补齐方案对算法安全性有什么样的影响?它们是怎么影响分组算法安全性的呢?我们又该怎么避免这些安全陷阱呢?
其实,这都是对称密钥分析的核心问题。因为,可以说,每一种链接模式、每一种数据补齐方案都有着不同的构造,当然也就对应着不同的分析办法,而且分析起来都较为复杂。
这一讲我们先来分析链接模式对安全性的影响同时我们还可以借此机会研究一下ECB模式到底有什么问题。还记得吧我们在开篇词提到过它不是一个安全的加密模式。
在讲ECB模式之前首先我们先来看看链接模式是怎么一回事。
## 链接模式怎么连?
我们上一讲说过,链接模式指的是如何把上一个分组运算和下一个分组运算联系起来,使得上一个分组运算可以影响下一个运算。但是,这个联系是怎么建立起来的,上一个运算到底又是怎么影响下一个运算的,这个描述是模糊的。
<img src="https://static001.geekbang.org/resource/image/25/20/25b0e86ec7352c98f332f2413c6e0220.jpg" alt="">
从道理上来说,上一个分组运算的所有要素,都有可能参与到下一个分组运算里;下一个分组运算的每一个要素,都有可能接收上一个运算的一个要素或者几个要素的组合。
而在这之间,就会形成不同的分配组合,也就形成了不同的链接模式。
当然,你要知道,不是所有的组合都是安全的。其中,有些模式有严格的适用范围。超越了这个范围,这个算法就不再安全了。
我们需要特别关注这些限制,把每个模式的原理理解清楚,使用时不要掉进安全陷阱里。
## ECB模式什么样
我们先来简单地了解一下ECB模式是怎么工作的。
<img src="https://static001.geekbang.org/resource/image/df/27/dfc31523655e5a4c72ccdc041a3d4327.jpg" alt="">
上一次,我们讨论了影响对称密钥算法安全性的五个关键因素。**但是,<strong><strong>ECB模式**</strong>有所不同,它**<strong>不使用链接模式,因此**</strong>它也用不着初始化向量</strong>
不使用链接模式,就意味着上一个分组运算不影响下一个分组运算,每一个数据分组的运算都是独立的。不需要初始化向量、每一个数据分组运算都是独立的,这特性令人振奋。
不需要初始化向量还意味着ECB模式没有初始化向量管理的烦恼有着更简单的代码。很多密码算法的接口设计和实现为了方便使用会使用缺省的数据。
比如说下面的Java代码使用的就是缺省的ECB模式。这段代码看起来还算清爽简单
```
Cipher cipher = Cipher.getInstance(&quot;AES&quot;);
cipher.init(Cipher.DECRYPT_MODE, secretKey);
```
但是,你看,下面的代码,看起来就繁琐的多。因为我们要在加密端和解密端同步初始化向量,而且它实际上的实现要更繁琐:
```
IvParameterSpec ivParameters = new IvParameterSpec(ivBytes);
Cipher cipher = Cipher.getInstance(&quot;AES/CBC/PKCS5Padding&quot;);
cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameters);
```
每一个分组运算都是独立的,这更是一个充满诱惑的特性。因为,能够独立运算,也就意味着可以并行地运算,也不必按照次序运算。
可以并行运算,意味着运算效率的大幅度提高;没有次序要求,意味着每一段加密数据都可以独立地存取、解密、修改、删除。而且,添加、插入新的数据段,也不会对其他数据段产生影响。
所有的这些都意味着良好的运算效率。这是每一个大型数据计算场景渴望的特性比如说数据库、流媒体、视屏会议。遗憾的是ECB模式有致命的安全缺陷。
它的致命的安全缺陷却恰好来源于它令人振奋的特性:初始化向量的缺失和链接模式的缺失。这意味着,如果我们不能拒绝它的这些诱惑,去寻找使用替代的方案,我们就会掉进这些致命的安全陷阱。
## 缺失带来了什么问题?
你可能还不知道初始化向量和链接模式的缺失会带来什么问题,让我们来一起分析下。
还记得初始化向量的特点吗?如果一个加密运算缺失初始化向量,相同的明文分组就会被加密成相同的密文分组。让我们一起来看一个例子,假设有如下一段数据:
```
ABCDEFGHHIJKLMNO0123456789012345
```
当我们使用AES-128/ECB算法加密时我们需要把这段数据分组成两个明文分组
```
ABCDEFGHHIJKLMNO
0123456789012345
```
然后我们加密这段数据得到的密文密钥“1234567890123456”十六进制表示:
```
1389AE9853633EBF3D35F28987FCD1187B4BFC89DD1700154482BC7EB686BB0E
```
我们可以把密文按照块大小分成两段:
```
1389AE9853633EBF3D35F28987FCD118
7B4BFC89DD1700154482BC7EB686BB0E
```
根据ECB加密模式的特点我们知道第一行密文对应的数据是“ABCDEFGHHIJKLMNO”第二行密文对应的数据是“0123456789012345”。
如果我们知道数据块对应的密文,我们就可以通过寻找重复的密文,在没有密钥,也不执行解密操作的情况下,知道对应的数据块。比如下面的密文:
```
7B4BFC89DD1700154482BC7EB686BB0E1389AE9853633EBF3D35F28987FCD118
```
对应的数据块是:
```
0123456789012345ABCDEFGHHIJKLMNO
```
你可能会有疑问数据都是加密的攻击者怎么会事先知道数据块和对应的密文呢其实互联网世界里已知重复的、位置确定的数据非常多HTTP的头部数据HTTPS数据包的头部URL等都是重复频率很高的数据。
只要通过定位数据块,锚定对应的密文,就可以利用已知数据块和密文寻找、推断未知数据了。
而且很多应用的场景尤其是互联网的应用场景注入特定明文数据、获取对应密文信息的攻击也是轻而易举的事情。如果攻击者没有“0123456789012345”的密文信息他可以构造一个这样的明文然后让密钥持有者加密然后他就可以获得对应的密文分组。
攻击者也许并不满足于看看数据、窥视隐私。所以ECB模式更严重的问题是由链接模式的缺失带来的。链接模式的缺失会让每一个分组运算都是独立的密文数据当然也会是独立的。
我们一起来看下面这段数据:
```
张三于二零二零年八月二十二日向李四借款人民币三十圆整,立此为证。
```
假设我们加密运算时,这段数据被分割成如下的四个分组:
```
张三于二零二零年
八月二十二日向李
四借款人民币三十
圆整,立此为证。
```
还有下面这段数据:
```
王二于二零二零年八月二十二日向李四借款人民币三十亿四千五百万六千圆整,立此为证。
```
假设在加密运算时,这段数据也被分割成如下的五个分组:
```
王二于二零二零年
八月二十二日向李
四借款人民币三十
亿四千五百万六千
圆整,立此为证。
```
如果有了明文,还有了对应的密文,我们就可以重新编排密文数据,以获得想要的结果。
如果使用了ECB我们可以把“亿四千五百万六千”这段明文分组对应的密文分组删除那么解密的结果就是
```
王二于二零二零年八月二十二日向李四借款人民币三十圆整,立此为证。
```
我们也可以把“亿四千五百万六千”这段明文分组对应的密文分组插入到第一段数据的密文数据里。那么,修改的密文数据解密结果就是:
```
张三于二零二零年八月二十二日向李四借款人民币三十亿四千五百万六千圆整,立此为证。
```
这样的结果,和原始数据相差甚远。这种攻击方式,就是常说的“分组重放”攻击。
## 什么时候使用ECB模式
现在你知道了ECB模式是一个很不安全的模式了吧既然ECB模式这么不安全为什么安全应用程序接口还要提供ECB模式的编程接口呢这主要是因为ECB模式是分组算法的基础。
分组算法的最基本运算就是转换一个固定长度的数据块。这个单个数据块的转换就是ECB模式下的运算。有密码学专业知识的算法工程师可以通过合理地使用ECB模式来构造更复杂、更安全的算法。**不过我们不应该在一般的应用程序使用ECB模式。**
ECB从诞生开始就被认为是一个不安全的加密模式有密码学基础知识的工程师一般不会使用它。下一节我们会讨论一个被广泛使用的模式以及它现在面临的挑战。
## Take Away今日收获
今天通过解构ECB模式我们讨论了在分组运算里链接模式和初始化向量在分组运算里承担的角色以及初始化向量缺失和链接模式缺失带来的问题。
最后我们还讨论了ECB模式的实际用途以及一个实用性的建议我们不应该在一般的应用程序使用ECB模式。
通过今天的讨论,我们要:
- 了解分组算法里,初始化向量缺失可能带来的问题;
- 了解分组算法里,链接模式缺失可能带来的问题;
- 知道ECB模式不是安全的模式应用程序不应该直接使用ECB模式。
## 思考题
今天的思考题,可能是一个稍微需要撕扯一下的挑战题。
我们前面说过我们不应该在一般的应用程序使用ECB模式。可是如果需要加密的数据小于一个数据分组也就是说数据太短不需要使用链接那么我们可以使用ECB模式吗有什么好处有没有风险
欢迎在留言区留言,记录、讨论你的想法。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,169 @@
<audio id="audio" title="10 | 怎么防止数据重放攻击?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/83/5b/83390e4c7c02b336f55dca0b7bf02e5b.mp3"></audio>
你好,我是范学雷。
上一讲我们讨论了对称密钥分组算法的链接模式从链接模式出发我们还分析了ECB模式初始化向量和链接模式的缺失导致了ECB模式的安全缺陷尤其是数据重放攻击。
我们说有密码学基础知识的工程师都应该知道ECB模式的安全问题并且不会在应用程序中使用它。这一讲我们来讨论一个更广泛使用的加密模式CBC模式。
CBC模式可能是2018年之前最常用、最常见的加密模式。和ECB模式不同由于初始化向量和链接模式的使用CBC模式解决了数据重放攻击的问题。可是从2018年开始由于它的安全问题CBC模式开始退出历史舞台尽管这一进程可能需要十数年甚至数十年。
## 为什么还要学习CBC模式
不知道你是不是已经有了一个问题既然CBC要退出历史舞台了我们还学习它干什么呢
第一个原因CBC的退出进程可能需要十数年才能完成。你现在工作的项目种可能还存在CBC模式的大量使用。我们学习了CBC模式有助于你解决现存项目的安全问题。
第二个原因学习针对CBC的攻击方案 是我们深入理解加密算法安全问题的最好的切入点。了解这些安全缺陷和攻击方案有助于你更好地使用密码学的算法。因为这些缺陷也可能换个面孔出现在应用程序层面。如果你能够说清楚CBC模式的攻击办法也就意味着你已经试着走入了算法的细节。
第三个原因也是最重要的原因就是我们要进一步地理解初始化向量和链接模式对加密算法的影响。学习CBC模式会为我们将来讨论更高级的协议和更安全的算法打下基础。
那么CBC模式是什么样子的它是怎么解决数据重放攻击的它存在哪些安全陷阱这是我们这一次要解决的问题。
## CBC模式什么样
和其他模式不同的是在CBCCipher Block Chaining模式中明文分组在加密之前要与前一组的密文分组进行异或运算异或运算的结果会参与加密函数的运算。
也就是说,上一次的密文分组要参与下一次的加密运算,每一个密文数据不仅依赖于它对应的明文分组,还依赖于上一次的密文分组。
这样的话,每一个密文分组,都依赖于前面所有的明文分组,包括初始化向量。
<img src="https://static001.geekbang.org/resource/image/3b/2d/3b93c00b26a591bde63b50a1f2f1b52d.jpeg" alt="">
所以我们能够知道CBC模式是需要初始化向量的。最显而易见的原因就是第一个明文分组还不存在所谓的“上一个密文分组”。所以我们需要一个外部引入的初始化向量来替代“上一个密文分组”参与运算。
不过我们需要注意的是在加密过程中加密函数的输入数据是明文分组Mi和上一次的密文分组Ci-1的异或运算的结果Mi ^ Ci-1
在解密过程中解密函数的输出数据也是明文分组和上一次的密文分组的异或运算结果Mi ^ Ci-1。我们要是想得到解密的明文分组就需要把明文分组和上一次的密文分组分离开来。
我们现在可以确定的是上一次的密文分组Ci-1是已知的。所以只要我们把上一次的密文分组和解密函数的输出数据进行异或运算就把明文分组分离出来了。
```
(Ci-1) ^ (Mi ^ Ci-1) = Mi
```
所以,我们可以发现,对于解密过程来说,如果我们把解密函数的运算结果与上一次的密文分组进行异或运算,就可以获得对应的明文分组。
<img src="https://static001.geekbang.org/resource/image/6e/7e/6e44f9b084b3233d0e5fb6bed0d5687e.jpeg" alt="">
不难想象,在解密过程中,我们要想获得第一个明文分组,用来替代“上一个密文分组”的初始化向量就必须要参与解密的过程。也就是说,**加密过程的初始化向量和解密过程的初始化向量必须是一样的,否则,我们就没有办法得到第一个明文分组**。
**注意一点,初始化向量只影响第一个明文分组,并不影响后续的解密过程和明文分组**
**类似地,一个密文分组,只影响它的下一个明文分组,并不影响更后面的解密过程和明文分组。**
**而在加密过程中,每一个密文分组,都依赖于前面所有的明文分组,包括初始化向量。**
所以,我们在这里要注意的就是加密过程和解密过程的区别。这是一个重要的特征,我们先在这里打个伏笔,后面我们会再讨论这个特征有什么用。
总的来说理解CBC模式我们要把握以下三个关键点
- 加密和解密要使用初始化向量;
- 加密和解密的初始化向量是等同的;
- 上一次的密文分组参与下一次的加密和解密运算。
## 初始化向量需要保密吗?
我们讨论过初始化向量的选择问题,就是在一个对称密钥的生命周期里,初始化向量不能重复。
**如果每一次运算,初始化向量都<strong><strong>能**</strong>不重复,即使是相同的明文数据,它的加密结果也是不同的。但是,如果初始化向量重复使用,相同的明文就会有相同的密文。重复使用的初始化向量,**<strong>会**</strong>消解密文反馈的作用使得CBC模式和ECB模式一样脆弱</strong>
所以,初始化向量的唯一性在加密运算的安全性中至关重要。
那你会问了,既然初始化向量这么重要,那我们需要对它进行保密吗?**初始化向量并不需要保密**。如果你对这一点有疑问,不妨换个角度想一想:每一个分组加密的初始化向量都是上一次加密运算得到的密文分组,而密文分组是可以公开的信息。
初始化向量不需要保密,这是我们要打的第二个伏笔。
## 异或运算会不会有问题?
我在上面的讲解中提到了异或运算,其实,它在密码算法里有广泛的应用,为什么它如此广泛?
第一个原因是**异或运算是按位运算,所以在相同的计算环境下,异或运算时间只和数据的位数相关,和数据的实际数值无关**。放在密码学算法的世界里,如果运算时间和实际数值无关,那简直再好不过了。
换句话说,如果运算时间和数据数值相关,而且别人还了解到这种相关性,他就可以通过统计学的方法,通过观察、测算运算时间,找到运算时间和数据数值之间的关联,来破解密码。
第二个原因同样是**按位运算,在相同的计算环境下,异或运算的复杂度,也就是需要的算力,只和数据的位数相关,和数据的实际数值无关**。而且一个运算需要的算力在计算机环境中可以通过占用的CPU周期数以及消耗的内存空间来衡量。
同理如果占用的CPU或者消耗的内存和数据数值相关别人就可以通过统计学的办法然后观察CPU的占用、电力消耗或者内存的消耗来破解密码。一般来说这种相关性也会影响运算时间从而使得基于测算运算时间的攻击方式同样有效。
不光如此,如果运算的复杂度和数据数值相关,密码破解的办法可就是千奇百怪的了。记录、测算计算机的噪音、温度、辐射、反应时间等等,都有可能成为有效的攻击手段。
如果让一个一流的黑客,拿着手机进入数据中心,录一段服务器发出的声音,说不定你的服务器就被攻陷了。之所以没有说一定会被攻破,是因为近几年的密码学进展,已经发展出了具有防范能力的算法和实现。
但是,如果你的服务器使用的是十年前的技术和软件,黑客得手的概率还是有的。我们后面会讨论这些新技术和新算法。
第三个原因和异或运算的运算特点有关,也就是相同的数据归零,不同的数据归一。
- 归零律:如果两段数据完全相同,它们的异或运算结果,就是每一位都是零的数据;
- 恒等律:如果一段数据和一段全是零的数据进行异或运算,前一段数据中是零的位运算后还是零,是一的位运算后还是一。也就是说,和零进行异或运算,不改变原数据的数值。
正是异或运算的归零律和恒等律CBC模式才能成立解密才能进行。这两个性质还使得解密运算和加密运算具有相同的运算效率。
然而CBC模式的主要安全问题也来源于异或运算的这两个性质。
如果两段数据中只有一位不同,它们的异或运算结果,就是只有这一位的数据是一,其他的数据都是零。那是不是我们就可以通过构造明文分组或者密文分组,一次改变一位数据,然后把数据交给加密运算或者解密运算来处理,通过观察加密或者解密的结果展开攻击了?
比如说一个128位的密钥它的强度能承受2^128次的运算是一个强度的指数级别的量级。
- 如果我们一次改变一位数据的攻击方式得逞最多需要128次的运算
- 如果我们一次只能观测一个字节一次一位的改变需要2^8 = 256次这样的攻击方式得逞最多需要255 * 16 = 4080次的运算。
这样的运算强度和设计的理论值2^128相差太远了一次有效的破解也就是分分钟的事情。
还别说这样的攻击方式在实践中真的是可行的。这种攻击方式把CBC模式变成了一个充满陷阱的模式。用的好它就是安全的用的不好它就会惹来麻烦。这实在不符合密码算法要皮实、耐用的要求。
**阻断一个攻击的方式之一,就是破坏攻击依赖的路径或者条件**。对于上面的攻击方式,其实只要攻击者没有办法一次改变一位数据或者少量的数据,这样的攻击就可以被有效破解了。
也就是要保证攻击者在展开攻击的时候没有办法一次改变不少于一个数据分组的数据。对于AES来说数据分组大小是128位攻击者需要运算2^128次才可以攻击得逞。
计算量这么大,攻击者的攻击方式就无效了。那我们怎么做才能让攻击者没办法呢?
密文分组、密钥、加密算法、解密算法,这些都是固定的数据或算法,没有考量的空间。剩下的变量,就只有明文分组和初始化向量了。要想解决掉这个安全问题,该怎么控制明文分组和初始化向量?异或运算又是怎样带来麻烦的?
要想深入地了解这些问题,有点烧脑。下一次,我们集中精力来讨论、分析其中的细节和办法。
## 密钥少一位会有影响吗?
不知道你有没有注意到,我们上面的讨论,提到了数据的位数。
因为分组加密是按照固定的分组进行加解密运算所以每一次的分组运算数据的位数都是固定的。比如AES算法的分组大小都是128位。所以我们不用担心分组运算的数据位数的变化。
在分组运算中,初始化向量、密文分组和明文分组密钥的数据位数也都是固定的。所以,我们也不需要担心它们的位数的变化。加密算法和解密算法不涉及数据位数,所以我们也不担心算法。剩下的一个变量,就是密钥了。密钥的位数会变化吗?密钥的位数变化有影响吗?
一般来说我们也不太关心密钥的位数变化密钥少一位似乎也不是什么无关紧要的事情。所以出于互操作性的考虑很多标准和协议包括应用最广泛的TLS 1.2协议)需要把密钥的高位的零清除掉,然后再参与运算。
原来128位的密钥可能就被清除成了127位或者126位的密钥了。2018年发布的的TLS 1.3版本不再需要清除密钥高位的零。少一位密码当然会带来计算性能的差异以及由此引发的计算时间偏差。可是似乎2020年之前没有人担心这件事。
直到2020年9月8日当我正在写这一篇稿的时候一个名字叫做“浣熊攻击”的安全研究成果发布了。浣熊攻击可以利用密钥高位清零造成的运算时间差通过观察、测算运算时间运用统计学的技术破解运算密钥。这实在是一个了不起的发现。
目前来看,这种攻击方式还比较复杂,不容易执行。但是,一旦发现攻击方法,如果业界没有采取及时的措施,攻击技术的改进速度是惊人的。“浣熊攻击”出现,再一次敲了敲大门,**警告我们要尽量避免计算时间偏差和计算算力偏差,谨慎地处理不可避免的计算时间偏差和算力偏差。**
## Take Away今日收获
今天通过解构CBC模式我们讨论了在分组运算里一个典型的链接模式是什么样子的以及重申了初始化向量的唯一性要求。使用唯一的初始化向量和恰当的链接模式可以帮助我们防范数据重放攻击。
还有,通过异或运算和密钥位数的讨论,我们要小心计算时间偏差和计算算力偏差对算法安全性的影响。一般来说,这是一个特别容易忽视的问题。不仅仅是密码学算法,对所有私密数据的运算,都要小心处理计算时间偏差和计算算力偏差。否则,都有数据泄漏的危险。
另外为了后面更进一步地讨论CBC模式的安全问题我们还在这一次埋了不少的伏笔暗线比如初始化向量不需要保密异或运算的特点等。
通过今天的讨论,我们要:
- 理解CBC模式的三个关键点。
- 了解计算时间偏差和算力偏差对算法安全性的影响。
## 思考题
今天的思考题,是一个动手题,也是一个简单的密码算法漏洞扫描的思路。
通过上面的讨论我们知道密钥的位数很关键一位也不能多一位也不能少。找一个你熟悉的密码算法库这个算法库可以是Java Script的也可以是Java的也可以是你熟悉的项目使用的算法库。
然后调用它的对称密钥生成接口试着产生很多128位的密钥。你看一看有没有可能返回127位或者129位的密钥。 如果你找到了不是128位的密钥这个算法库就有潜在的安全问题。
如果你恰好学过统计学还能使用统计学的软件你可以试着多做一道思考题。我假设你知道RSA非对称密钥算法也了解它的调用接口。同样的找一个你熟悉的RSA算法实现生成一对1024位RSA非对称密钥用公钥加密大量的1024位的不同数据然后用私钥解密这些数据统计解密消耗的时间。
如果解密时间不是大致相同的这个RSA实现就是有问题的。破解起来可能就是分分钟的事情。这是一个让我们了解计算时间偏差和计算算力偏差的练手题也是个常见的分析RSA实现漏洞的攻击办法。
欢迎在留言区留言,记录、讨论你的发现。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,184 @@
<audio id="audio" title="11 | 怎么利用解密端攻击?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/75/dc/754750b96b783f0e788c959166a652dc.mp3"></audio>
你好,我是范学雷。
上一讲我们讨论了对称密钥分组算法的CBC链接模式还提到了异或运算的好处以及它的风险。但是我们又留了一个小尾巴异或运算是怎样带来麻烦的它是怎么影响CBC链接模式的安全性的我们又有什么有效的办法可以规避异或运算的风险
这两次讨论,有点烧脑,我们姑且把它当做一次打怪升级的过程。如果弄清楚这些问题,你就可以更得心应手地使用分组算法,而且毫无疑问地超越了大部分人对对称密钥分组算法的理解。
鉴于这两次讨论的难度,我觉得最好的学习方式,就是拿上纸笔,跟着我的思路,自己画一画攻击的流程。学习一段内容,就画一画这一段的思路,扎扎实实把这一段理解下来。
其实,这也是我们学习、理解所有密码学算法攻击的一个办法。密码学算法的攻击,大部分都超乎想象,意想不到。读一段、画一段,理解一段,是一个实用的方法,也是我自己使用的方法。
好,你准备好了吗?我们开始了。
## 怎么利用解密端攻击?
还记得CBC模式吗攻击者既可以攻击它的解密端也可以攻击它的加密端。针对解密端的攻击最常见的方式就是给定密文数据观测解密运算是成功还是失败。
那么,解密端是怎么知道解密是成功还是失败呢?到目前为止,在我们已经讨论过的密码学技术中,我们唯一能够利用的,就是数据补齐方案。我们先来看看,数据怎么补齐?
### 数据怎么补齐?
简单来说CBC模式的补齐数据就是一个数据分组缺失的数据的长度。比如对于AES来说数据分组是16个字节。如果一段数据只有15个字节补齐的数据就是一个1如果有14个字节补齐的数据就是两个2以此类推如果只有一个字节补齐的数据就是十五个15 。
<img src="https://static001.geekbang.org/resource/image/95/fe/953c67a8a6fb6fc9aeddbcc6bf7074fe.png" alt="">
不过,这个数据补齐方案有明显的缺陷。
当数据分组最后一个字节是1解密端不太好判断这个字节到底是补齐数据还是有效数据。所以很多标准和协议使用最后一个数据分组的最后一个字节来标明补齐数据。
如果一段数据只有15个字节补齐的数据就只有这个标识字节数值为0表示没有额外的补齐数据如果有14个字节标识字节为1表示还有一个字节的额外补齐数据额外的补齐数据的数值也是1如果有13个字节标识字节为2表示还有两个字节额外补齐数据额外的补齐数据的数值也是2以此类推。
需要注意的是如果一段数据有16个字节也就是刚好一个数据分组大小这时候就需要一个全是补齐数据的数据分组它的每一个字节都是15。
<img src="https://static001.geekbang.org/resource/image/82/bd/82198699be86df8f3b0c23fe3aa192bd.png" alt="">
为了方便,下面的讨论中,我们都使用有标识字节的数据补齐方案,除非特别声明。
### 补齐怎么校验?
那你会不会有疑问,我们如何在解密端,判断一个数据分组中的数据是不是补齐数据?
可以检查最后的几位数字是不是相同。比如说如果最后一位是2但是倒数第二位和倒数第三位不是2解密就失败了。失败的原因可能是由于篡改的密文分组或者是篡改的初始化向量或者是篡改的密钥。不管哪种原因都可能导致补齐数据的校验失败。
那我们怎么利用数据补齐方案展开攻击呢?
### 攻击怎么展开?
我们回顾一下CBC模式的解密过程。一个密文分组的解密需要如下的输入数据
- 上一次的密文分组Ci-1
- 这一次的密文分组Ci
- 加密和解密共享的密钥K。
这一次的密文分组Ci和密钥K通过解密函数的运算可以产生一个中间结果Ti也就是解密函数运算的中间结果Ti然后解密函数运算结果Ti和上一次的密文分组进行异或运算获得明文分组PiPi = Ci-1 ^ Ti。
<img src="https://static001.geekbang.org/resource/image/4c/6b/4c1b630e4be11354cf37e3da500b516b.jpeg" alt="">
接下来我们利用数据补齐方案的攻击通过修改、构造最后一个密文分组Cn的前一个密文分组Cn-1也就是倒数第二个密文分组然后让解密端解密构造后的密文数据观察修改了密文数据的解密能不能成功。
<img src="https://static001.geekbang.org/resource/image/27/d6/27124b457a8a65f1eae06636974db6d6.jpeg" alt="">
那倒数第二个密文分组该怎么构造呢?首先,修改该密文分组的最后一个字节,该字节可以从零开始。毫无疑问,解密端并不能正确解密该密文分组,得到的明文分组也不是期望的明文数据。
但是,正像我们前面讨论的,解密端并没有办法知道这一个密文分组解密失败。要判断解密是否成功,还需要判断最后一个明文分组的补齐数据是不是符合规范。
如果我们修改倒数第二个密文分组的最后一个字节,那么解密后的最后一个明文分组应该只有最后一个字节受到影响。如果这个解密后的字节是零,其他的数据会被解读为有效数据,这样就会通过补齐数据的校验。如果这个解密后的字节是其他数值,补齐数据的校验通过的概率就很小。实际的攻击过程,这么一点小小的概率也可以优化过滤掉。
如果解密后的这个字节不是零,那么,补齐数据的校验就会失败,解密端就会报错。观察解密端的行为,攻击者就能了解到,这个构造的字节没有通过解密过程。如果观察到失败,攻击者就会调节最后一个字节的数值,比如数值加一,然后再次让解密端尝试解密。
<img src="https://static001.geekbang.org/resource/image/85/69/85c1d2c0991b930147e5580849fbb069.png" alt="">
一个字节只有256种可能性最多尝试255次一定会有一次尝试补齐数据是零。
<img src="https://static001.geekbang.org/resource/image/86/79/860a6ccfa7175c1f424acf27d3535b79.png" alt="">
当攻击者观察到解密成功时,他能够掌握哪些数据呢?他能够直接得到的数据包括:
- 未修改密文分组的最后一个字节的数值Cn-1[15]
- 修改后密文分组的最后一个字节的数值Cn-1'[15]
- 解密端明文分组的最后一个字节的数值Pn'[15]也就是0x00
通过计算,他还可以得到解密函数的运算结果的一个字节:
- 解密函数运算结果的最后一个字节的数值Tn[15])。
由于明文字节Pn'[15]是密文字节Cn-1'[15]和解密函数运算中间结果字节Tn[15]的异或运算结果那么知道了明文字节Pn'[15]和密文字节Cn-1'[15]使用异或运算的归零律和恒等律计算出解密函数运算中间结果字节Tn[15])就是一件简单的事情了。
```
&gt; Pn'[15] = Tn[15] ^ Cn-1'[15]
&gt; =&gt;
&gt; Pn'[15] ^ Cn-1'[15] = Tn[15] ^ Cn-1'[15] ^ Cn-1'[15] = Tn[15]
```
既然知道了解密函数运算中间结果字节Tn[15])和原始密文分组的字节,真正的明文字节也就被破解出来了。
```
&gt; Pn[15] = Tn[15] ^ Cn-1[15]
```
你看非常简单的运算运算次数不多于255次攻击者就破解了一位字节。接下来的目标就是破解倒数第二位字节。
破解倒数第二位字节要使用的补齐数据是一0x01。首先既然攻击者已经知道了最后一个字节的明文、密文以及解密函数运算的中间结果他就可以计算、修改上一次的密文分组的最后一个字节使得解密后的明文分组的最后一个字节是一。
```
&gt; Tn[15] ^ Cn-1'[15] = 0x01
&gt; =&gt;
&gt; Tn[15] ^ Cn-1'[15] ^ Tn[15] = 0x01 ^ Tn[15]
&gt; Cn-1'[15] = 0x01 ^ Tn[15]
```
这样,解密端就确认了最后一个字节是一,也就是补齐数据的标识字节是一。只要倒数第二个字节也是一,补齐数据的检验就通过了。倒数第二个字节破解方式和第一个字节的破解方式相同。
每个字节的破解运算次数都不会多于255次。从倒数第一位开始依次破解直到第一个字节破解结束总的计算量是255乘以字节数。对于AES而言一个数据分组有16个字节破解计算只需要255 * 16 = 4080次。
这么小的破解计算量是没有什么安全性可说的。预测补齐数据的这一类破解方案都可以叫做“补齐预言攻击”。2002年塞尔吉·沃德奈Serge Vaudenay公布了补齐预言攻击。
有一些2002年之前制定的安全协议不能很好地应对这种攻击方案。相应地也有一些成功的破解方案。那么2002年以后的年月呢CBC模式该怎么规避补齐预言攻击
有效的方式有两个。第一个方式,也是最有效的方式,就是和数据完整性校验的算法结合起来,而不是依赖补齐数据是否合法,来判断解密数据是不是有效。这种方式,我们讨论数据完整性校验算法的时候,还会再讨论。另外一种方式,就是在初始化向量上想办法。
## 初始化向量就用一次吗?
初始化向量,现在你应该很熟悉了,就是加密或者解密运算开始的时候使用的向量。这个变量不是就使用一次、改变一次而已吗?怎么还使用它来阻断补齐预言攻击呢?
如果这个变量就使用一次、改变一次,它的确起不到作用。我们要做的,就是要改变这种一次性使用的方式,变成多次使用的方式。是不是听起来有点难度?是有难度的。
如果加密端和解密端每次运算(一次运算可以包含多个数据分组)都重置初始化向量,并且使用不一样的初始化向量。只要使用了不一样的初始化向量,每一次运算的最后一个密文分组都是不同的。这样,就不能重复地使用这个密文分组反复运算,从而切断了补齐预言攻击的攻击路径。
稍微有点麻烦的是,解密端怎么才能判断每一次运算都使用了不同的初始化向量。这不是一个容易的事情。所以,如果仅仅依靠一次运算一个初始化向量的办法,也很难有成熟的解决方案。
一次运算一个初始化向量的办法,除了可以用来阻断补齐预言攻击之外,它还有一个很有吸引力的特点。不过,让我们下一次再接着讨论。
## Take Away今日收获
今天通过讨论怎么在CBC模式的解密端利用数据补齐方案来展开补齐预言攻击我们知道了数据补齐方案可能存在的缺陷。
另外,我们还讨论了阻断补齐预言攻击的办法,那就是,每一次加密/解密计算,都使用初始化向量,而不是仅仅就使用一次初始化向量。
通过今天的讨论,我们要:
- 知道CBC模式存在补齐预言攻击
- 知道使用不同初始化向量的来阻断补齐预言攻击。
## 思考题
今天的思考题,我们去看一看协议的设计。
在TLS 1.2的设计中使用CBC模式加密的数据包是由一个明文的初始化向量和密文的加密数据组成的。明文的初始化向量和密文的加密数据可以通过不安全的网络传递给信息接收方。
**第一个问题是:为什么<strong><strong>我们**</strong>可以传递明文的初始化向量?</strong>
**第二个问题是,为什么要传递初始化向量?**
另外如果你仔细看下面的数据结构就会注意到在TLS 1.2的设计里,待加密的数据块,已经补齐到算法要求的数据块的整数倍了。这似乎也就意味着,上一个数据包和这一个数据包,以及这一个数据包和下一个数据包,都可以无缝地衔接,而不需要使用数据补齐,也不需要每次都使用一个初始化向量。
**既然已经补齐了,为什么还要每一次加密都使用一个初始化向量?这是第三个问题。**
理解了这三个问题你就算学会使用CBC算法了。CBC算法要被扫进垃圾箱的主要原因就是因为这个算法太难用对了虽然它是用途最广泛的加密模式。如果你可以用对它在很多场景它还是能够再延续使用一段时间等待你升级到更皮实的算法。
```
struct {
opaque IV[SecurityParameters.record_iv_length];
block-ciphered struct {
opaque content[TLSCompressed.length];
opaque MAC[SecurityParameters.mac_length];
uint8 padding[GenericBlockCipher.padding_length];
uint8 padding_length;
};
} GenericBlockCipher;
```
欢迎在留言区留言,记录、讨论你的理解和看法。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,214 @@
<audio id="audio" title="12 | 怎么利用加密端攻击?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/1e/b5/1e63d980aa72a4827e4cb3505036d5b5.mp3"></audio>
你好,我是范学雷。
上一讲我们讨论了怎么利用解密端攻击CBC模式的对称密钥分组算法。那么加密端的攻击方案是怎么进行的有什么办法可以规避加密端的攻击这是我们这一次要解决的问题。
虽然我们之前说CBC模式应该退休了但是现实中它还有广泛的应用。我们花了很多篇幅来讨论CBC模式除了提醒你CBC模式的缺陷外还希望你了解缺陷修补的办法这样你就可以修复现有的应用了。
这一次的讨论,比起上一次,稍显烧脑。因为,这是一个相对较新的攻击技术。不过,我可以保证,如果你了解了这种攻击技术,将会有一个巨大的收获。
## 怎么利用加密端攻击?
我们先来看看是怎么利用加密端攻击的其实CBC模式针对加密端的攻击和针对解密端的攻击方案一样最常见的攻击方式也是通过异或运算展开的。
### 攻击面应该怎么选?
要想了解针对加密端的攻击方案我们先来回顾一下CBC模式的加密过程。在这个过程中一个明文分组的加密需要如下的输入数据
- 上一次的密文分组Ci-1
- 这一次的明文分组Pi
- 加密和解密共享的密钥K。
这一次的明文分组Pi和上一次的密文分组Ci-1进行异或运算获得中间结果Ti。
- 异或运算获得的中间结果TiTi = Ci-1 ^ Pi
然后异或运算获得的中间结果Ti和密钥K通过加密函数的运算产生密文分组
- 密文分组Ci
而加密端的异或运算,发生在明文分组和上一次的密文分组(对于第一次运算,上一次的密文分组就是初始化向量)之间。针对加密端的攻击,同样,我们也是在明文分组和上一次的密文分组或者初始化向量上想办法。
<img src="https://static001.geekbang.org/resource/image/e7/c2/e739603982e283d7976d95a4fca682c2.jpeg" alt="">
### 攻击理论上可行吗?
假设目标明文分组是Pi但是攻击者并不知道Pi的明文内容而他攻击的目的就是解密出Pi的明文数据。由于密文数据不是保密的所以攻击者可以知道所有的密文分组包括参与Pi加密运算的上一次密文分组Ci-1和这一次的密文分组Ci。
如果攻击者可以使用加密接口,攻击者就可以构造明文分组,然后调用加密运算,观测加密运算后的密文分组。如果攻击者观测到密文分组和目标明文分组对应的密文分组相同,那么他就可以确定目标明文分组的具体数据了。
那明文分组该怎么构造呢像解密端的攻击方案一样我们还是要利用异或运算的归零律和恒等律。假设已经知道了Pi当前的加密运算需要使用上一次的密文分组Cj-1如果攻击使用的明文可以构造成下面的形式那么攻击就能够成功
>
Pj = Pi ^ Ci-1 ^ Cj-1
加密运算后的结果,就是目标明文分组对应的密文分组。
>
Tj = Pj ^ Cj-1
>
= Pi ^ Ci-1 ^ Cj-1 ^ Cj-1
>
= Pi ^ Ci-1
>
= Ti
>
=&gt;
>
EkTj= EkTi
>
=&gt;
>
Cj = Ci
>
Tj ^ Ci-1 = Ti ^ Ci-1 = Pi ^ Ci-1 ^ Ci-1 = Pi
>
Pi = Tj ^ Ci-1 = Pj ^ Cj-1 ^ Ci-1
假设终归是假设攻击者要是知道明文分组也就不需要实施攻击了。似乎这个美好的运算并不能够执行。对于AES算法来说一个明文分组有16个字节也就是128位。如果一位一位地尝试需要2^128次运算。这种数量级的运算现在的计算能力还是无能为力的。
### 攻击实践上可行吗?
那么既然完整破解有16个字节的明文分组不可行能不能只破解一个字节
怎么破解一个字节呢我们还要继续从假设开始。假设攻击者已经知道了明文分组里的15个字节只剩下最后一个字节未知。攻击者能不能破解出这个字节这样的破解太简单了。
攻击者只需要改动最后一个字节按照上述的构造明文分组的方式他最多尝试255次最后一个字节就可以破解了。原则上只要有一个字节能够被破解这个算法就算是被破解了。
不过我们还需要验证这个假设有没有实际的可能性也就是说一个明文分组里攻击者有没有可能会知道15个字节只有1个字节是未知的
遗憾的是网络里的数据大部分都是格式化的比如HTTP的数据。下面的数据是我在写这些文字的时候从geekbang.org登录页面记录下来的HTTP请求的头部数据。
这里面的数据除了Cookie之外大部分都是重复的数据。
```
POST /account/sms/login HTTP/1.1
Host: account.geekbang.org
Connection: keep-alive
Content-Length: 371
sec-ch-ua: &quot;\\Not;A\&quot;Brand&quot;;v=&quot;99&quot;, &quot;Google Chrome&quot;;v=&quot;85&quot;, &quot;Chromium&quot;;v=&quot;85&quot;
Accept: application/json, text/plain, */*
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36
Content-Type: application/json
Origin: https://account.geekbang.org
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: https://account.geekbang.org/signin
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: &lt;deleted&gt;
```
以及HTTP的请求内容。请求的内容除了电话号码cellphone和手机验证码之外code其他的数据也都是重复的。
```
{&quot;country&quot;:86,&quot;cellphone&quot;:&quot;12345678901&quot;,&quot;code&quot;:&quot;123456&quot;,&quot;ucode&quot;:&quot;&quot;,&quot;platform&quot;:3,&quot;appid&quot;:1,&quot;remember&quot;:1,&quot;source&quot;:&quot;&quot;,&quot;sc&quot;:{&quot;uid&quot;:&quot;&quot;,&quot;report_source&quot;:&quot;Web&quot;,&quot;utm_identify&quot;:&quot;&quot;,&quot;utm_source&quot;:&quot;&quot;,&quot;utm_medium&quot;:&quot;&quot;,&quot;utm_campaign&quot;:&quot;&quot;,&quot;utm_content&quot;:&quot;&quot;,&quot;utm_term&quot;:&quot;&quot;,&quot;share_code&quot;:false,&quot;original_id&quot;:&quot;1746eb1327656f-05d10c17083f0f-f7b1332-2073600-1746eb132773ba&quot;,&quot;refer&quot;:&quot;极客时间&quot;}}
```
所以攻击者知道15个字节甚至整个明文分组这种情况在实际中是经常出现的。
我们真正关心的是敏感数据,比如手机验证码,是不是能够被破解。那么,首先,我们要了解的是,攻击者能不能破解手机验证码的第一个字节呢?
如果手机验证码的第一个字节刚好是一个明文数据分组的最后一个字节前面我们已经讨论过了这个字节就是可以破解的。由于手机验证码只能是数字破解这个字节最多需要9次计算。比起255次9次计算就更简单了。
<img src="https://static001.geekbang.org/resource/image/72/af/72aa42496807b1270209e2b1820d3caf.png" alt="">
如果手机验证码的第一个字节不是明文数据分组的最后一个字节,该怎么办?思路也很简单粗暴,只要我们想办法把手机验证码的第一个字节挪到明文数据分组的最后一个字节就行。
该怎么操作呢?也很简单,通过更换顺序、删减或者增加手机验证码的前面的字节,就可以了。
<img src="https://static001.geekbang.org/resource/image/bd/87/bdb098af1d93e93624fd4c3dab9b5587.png" alt="">
既然可以想办法把手机验证码的第一个字节挪到明文数据分组的最后一个字节,当然也可以想办法把手机验证码的第二个字节、或者第三个字节,或者任意一个字节挪到明文数据分组的最后一个字节。
每当这个字节是明文数据分组的最后一个字节时最多9次就可以破解这个字节了。一个6位数字的手机验证码攻击者破解起来最多需要6 * 9 = 54次。
<img src="https://static001.geekbang.org/resource/image/87/3b/879549d87906224e1d38a1d204dfcb3b.png" alt="">
是的如果加密使用的是CBC模式54次运算6位数的手机验证码就可以破解出来了
2011 年 9 月两位天才般的研究人员Juliano RizzoThai Duong公开了上述的攻击方案。并且表示只要给他们几分钟时间他们就可以利用该漏洞入侵你的支付账户。他们给这个攻击技术取了一个超酷的名字叫做BEAST。
2011年9月之前的岁月CBC模式一直是加密算法的主流模式几乎所有的网络加密都选用CBC模式。2011年以后CBC模式就开始正式办理退休手续了。新的协议或者应用不应该继续使用CBC模式了现有的协议或者应用也需要采取措施防范BEAST攻击。
## 有什么防范措施?
那现有的协议或者应用,要怎样采取措施来防范攻击呢?
其实防范措施还是要在初始化向量上想办法。BEAST攻击起作用的关键就是要使用上一次加密运算的最后一个密文分组。
<img src="https://static001.geekbang.org/resource/image/54/85/544ba69455899ca1866a5765c3907d85.jpg" alt="">
如果这一次的加密运算不使用上一次加密运算的数据BEAST攻击就无法运算了。该用什么替换上一次加密运算的最后一个密文分组呢你可以先思考一下。
和防范补齐预言攻击的办法一样,替换的办法就是每一次加密运算,都使用不同的初始化向量。
<img src="https://static001.geekbang.org/resource/image/7f/8f/7fd1fc3275ae0946fb0bd3e884dd658f.jpg" alt="">
比较麻烦的是,对于每一次解密运算,解密端需要加密端使用的初始化向量,然后才能执行解密运算。可是,初始化向量的同步不是一件容易的事情,特别是在数据包可能被有意无意丢弃的环境下。
## 有没有改进的防范措施?
一个改进的办法,就是继续使用上一次加密运算的最后一个密文分组,同时把每一次运算的初始化向量当做第一个明文分组来处理。
这个办法的好处,就是解密端不需要知道加密端选择的初始化向量,就可以执行解密运算。这个办法的坏处,就是解密端需要丢掉初始化向量这一段数据,不能把它当做应用数据来处理。
<img src="https://static001.geekbang.org/resource/image/38/63/388460ebc9d50a121441e61510946f63.jpg" alt="">
如果新的应用不应该再使用CBC模式那么现在推荐使用的加密模式是什么呢在讨论这个新模式之前我们还需要补充一块知识那就是消息验证。下一次我们讨论消息验证的机制然后我们就有足够的知识去了解这个新的加密模式了。
## Take Away今日收获
今天我们讨论了怎么在CBC模式的加密端通过挪移重构明文分组来展开攻击以及针对攻击的防范措施。
除了知道加密端攻击手段和防范措施之外我们还要了解这种挪移重构明文分组的攻击办法。这种攻击办法自BEAST攻击发布以来已经被成功运用于很多密码学算法的破解。如果你需要设计应用协议要小心应对明文数据的挪移以及重构会不会构成威胁的问题。
今天,我们应该:
- 知道CBC模式存在BEAST攻击
- 知道使用初始化向量来阻断BEAST攻击。
## 思考题
今天的思考题,稍微有点和以前不同。这一次,我们去挑战一下怎么给标准协议打补丁。
标准协议一旦成为标准也就意味着它的行为模式有了规范不能随意更改。我们假设一个标准协议已经使用了CBC模式加密它的通信数据而且只有第一次加密运算使用了初始化向量。
后续的加密运算都不再使用初始化向量。也就是说这个协议存在安全漏洞是BEAST攻击的目标。毫无疑问由于是现成的标准我们不能够修改这个协议让每一次的加密运算都使用一个不同的初始化向量。
那么,还有没有办法修复这个协议的漏洞,使得修复后的协议实现,还能够和旧的协议实现通信,而没有明显的兼容性和互操作性问题?
这个问题稍微有点难度。在BEAST攻击发现之前业界已经知道CBC加密端攻击存在理论上的可能性。BEAST攻击的出现简化了攻击方案使得CBC加密端的攻击成为现实。
业界尝试了很多年都没有找到理想的办法BEAST攻击发现之后也经过很长一段时间才找到了解决的办法。最后的解决方案简简单单又出乎意料。
你来试一试,能不能找到这个简单的方案,或者有没有更好的解决方案?
欢迎在留言区留言,记录、讨论你的发现。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,146 @@
<audio id="audio" title="13 | 如何防止数据被调包?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/d4/cb/d44aa304bebf007d1550c5a761e5a2cb.mp3"></audio>
你好,我是范学雷。
还记得我们在前面讨论过CBC模式补齐预言攻击吗当时我们谈到了一个问题就是解密端是无法判断解密得到的数据是发送者发送的数据还是被人掉了包的数据的。
这就好比,牛郎要送的信是,“七夕今宵看碧霄,牛郎织女渡鹊桥”。织女拆开一看,却是一堆乱码,或者是变成了“我断不思量,你莫思量我。将你从前与我心,付与他人可”。
如果这封信真的变成了一堆乱码,就意味着信息没有被有效地送达,这样会给织女愁上加愁;而如果内容是“我断不思量”,简直就是一支穿心箭,这可一点都不好玩。
那么,织女看到“我断不思量,你莫思量我。将你从前与我心,付与他人可”的信件,除了靠坚贞不渝的信念这个不靠谱的办法之外,她有没有其他的办法来判断这是一封伪造的信件?牛郎除了坚信织女有坚贞不渝的信念之外,他有没有其他的办法来预防信件的伪造?
其实,**解决<strong><strong>问题**</strong>的思路**<strong>也**</strong>很直观,就是要能够验证发送的信息</strong>。牛郎发送信息时,心里要想着意外情况,要给织女提供验证信息的办法。织女收到信息时,要有办法验证信息的真伪,不能只依靠心里的信念。
那么,我们今天这一讲,就来分析一下如何防止数据被调包这个问题。
## 怎样有效地验证一段信息?
首先,我们来分析下,要想有效地验证一段信息,需要满足什么条件呢?
第一个条件就是,**我们要有额外的信息**。只有要验证的信息本身,是没有办法验证这个信息的。也就是说,信息本身不能自己验证自己。这个额外的信息,我们暂且称之为验证信息。
第二个条件就是,**验证<strong><strong>信息和待验证的消息**</strong>之间要有关联</strong>。如果没有关联,也就意味着如果我们替换掉待验证的信息,验证信息并不受影响,这显然起不到验证的作用。
如果待验证的信息有变动,验证信息也要变动,而且验证信息的变动结果要不可预测。如果可以人为构造一段信息,它的验证信息和待验证的信息是一样的,也起不到验证的效果。
第三个条件,就是**验证信息的计算要快,数据要小**。不知道这一点,你能不能理解?这点说的就是,计算效率要高,要不然验证信息的实用和推广价值就会大打折扣。
那么有同时满足上述三个条件的一个方案吗消息验证码Message Authentication Code, MAC就是最常用的满足上述三个条件的一个方案。
<img src="https://static001.geekbang.org/resource/image/d5/c9/d5eee6ab3fe89befdd25f8cfbe521ec9.jpeg" alt="">
## 消息验证码是怎么工作的?
既然消息验证码可以同时满足我们的三个条件,那么消息验证码是怎么工作的?
首先,我们来看使用消息验证码的前提,就是信息的发送方和接收方要持有相同的密钥,这和我们前面讨论的对称密钥的条件是一样的。能够使用对称密钥的场景,都能够满足这个前提。
另外,信息的发送方和接收方要使用相同的消息验证函数。这个函数的输入数据就是对称密钥和待验证信息。信息的发送方使用消息验证函数,可以生成消息验证码。
接下来,信息发送方把待验证信息和消息验证码都发送给信息接收方。信息接收方使用相同的消息验证函数和对称密钥,以及接收到的待验证信息,生成消息验证码。
然后,信息接收方对比接收到的消息验证码和自己生成的消息验证码。如果两个消息验证码是一样的,就表明待验证信息不是伪造的信息。否则,待验证信息就是被篡改过的信息。
这听起来是一个不错的方案。不过,消息验证函数需要使用对称密钥,输入任意大小的数据,输出为一小段数据。什么样的消息验证函数能够承担起这样的任务呢?
通常的对称密钥算法,密文数据不会小于明文数据,这样的话,就不能满足验证数据小的要求。因此,通常的对称密钥算法,我们并不能当做消息验证函数使用。
那么,我们到底该怎么选择消息验证函数?
## 该怎么选择消息验证函数?
我刚才提到,消息验证函数的输出应该是一小段数据,这一点有没有让你想起,我们前面提到的单向散列函数?我们再来回顾一下单向散列函数的三个特点:
- 正向计算容易,逆向运算困难;
- 运算结果均匀分布,构造碰撞困难;
- 给定数据的散列值是确定的,长度固定。
如果你再回头看看,我们上面讨论的消息验证的三个条件,单向散列函数就能够完美地满足这三个条件。那么,对称密钥怎么和单向散列函数结合起来,构造出消息验证函数呢?**最常见的方案就是基于单向散列函数的消息验证码。**
基于单向散列函数的消息验证码Hash-based Message Authentication Code HMAC这个名字是不是听起来太长了所以我们通常使用它的简称HMAC。
在HMAC的算法里单向散列函数的输入数据是由对称密钥和待验证消息构造出来的。到目前为止这种构造方法还没有明显的安全问题我们不再讨论构造细节。如果你感兴趣可以查阅1997年发表的RFC 2104。
一个密码学算法经历了二十多年还没有明显的安全漏洞,这真的是很难得!
不过你需要注意的是HMAC算法并不使用我们前面讨论过的链接模式。所以对称密钥链接模式的各种安全问题并不会影响HMAC算法的安全性。
## 为什么需要对称密钥?
到这里,你是不是早就有了一个疑问,消息验证函数为什么还需要对称密钥呢?我们前面提到,单向散列函数也可以验证数据的完整性。为什么它不直接使用单向散列函数呢?
还记得吗?我们前面在讨论单向散列函数解决数据完整性问题的时候,还有一个遗留的问题,就是怎么获得原始数据的散列值。对称密钥就是用来解决这个问题的。
我们先来看看,如果没有对称密钥的加入,消息验证码还能不能工作。
信息发送方把待验证信息和消息验证码都发送给信息接收方。假设存在一个中间攻击者,能够解开待验证信息和消息验证码。由于单向散列函数是公开的算法,中间攻击者就可以篡改待验证信息,重新生成消息验证码。
然后,中间攻击者把篡改的信息和篡改的验证码发给信息接收方。篡改的信息和篡改的验证码能够通过信息接收方的信息验证。也就是说,这样的话,信息接收方就没有办法识别出这个信息是不是原始的、没有篡改的信息。这样,信息验证就失效了。
<img src="https://static001.geekbang.org/resource/image/3b/02/3ba5851332a0fcc8ea9630473c5c8502.jpeg" alt="">
可是,如果对称密钥参与了消息验证码的运算,由于中间攻击者并不知道对称密钥的数据,攻击者就很难伪造出一个能够通过验证的消息验证码。换一个说法,**对称密钥的参与,是为了确保散列值来源于原始数据,而不是篡改的数据。**
有了对称密钥这个私有数据的参与,消息验证码的算法是不是就没有安全漏洞了呢?
## 怎么计算HMAC算法的强度
HMAC算法与对称密钥和单向散列函数息息相关所以对称密钥的安全强度和单向散列函数的安全强度都会影响HMAC算法的安全强度。该怎么计算HMAC算法的安全强度呢
严格的来说HMAC算法的安全强度是由对称密钥的安全强度和两倍的散列值长度之间较小的那个数值决定的。比如如果我们选择256位的对称密钥以及散列值长度是160位的SHA-1。
两倍的散列值长度就是320位。那么在256位和320位两个数值之间256位是较小的数值。那么这个HMAC运算的安全强度就是256位。
一般来说两倍的散列值长度通常大于流行对称密钥强度。所以HMAC算法的强度通常也是由对称密钥决定。简单起见**对于流行的HMAC算法我们只需要考虑对称密钥的安全强度**。
## 有哪些常见的HMAC算法
HMAC算法是由单向散列函数的算法确定的。下面的表格我列出了一些常见的算法。同样的我们把HMAC算法也按照退役的、遗留的以及现行的算法来分类。
<img src="https://static001.geekbang.org/resource/image/54/20/5409851byy6b8c39102fee2b98aaa720.jpeg" alt=""><br>
<img src="https://static001.geekbang.org/resource/image/6f/c8/6fe58983135d71e44afe36cf2b8797c8.jpeg" alt="">
其中,**HmacSHA256和HmacSHA384是目前最流行的两个HMAC算法**。和以前的讨论一样,为了最大限度的互操作性和兼容性,我们应该选择当前最流行的算法。
如果单独的加密并不能解决信息的有效传递问题,有没有加密算法,能够综合考虑信息的机密性和完整性?如果存在这样的算法,我们就不需要额外设计消息验证码了。下一次,我们来讨论这个问题。
## Take Away今日收获
今天我们讨论了防止数据被调包的技术也就是消息验证码。我们讨论了消息验证码要解决的问题以及消息验证码的工作原理。我们还谈到如何选择消息验证函数最常见的方案就是选择基于单向散列函数的消息验证码也就是HMAC。
为什么我们需要对称密钥?其实是为了解决单向散列函数的遗留问题,因为对称密钥的参与,可以确保散列值来源于原始数据,而不是篡改的数据。
最后我们还研究了怎么计算HMAC算法的强度还列出了目前常见的HMAC算法。我们应该选择当前最流行的算法而对于流行的HMAC算法我们只需要考虑对称密钥的安全强度。
通过今天的讨论,我们要:
- 了解消息验证码要解决的问题;
- 尽量选用现行的、流行的算法HmacSHA256和HmacSHA384。
## 思考题
今天的思考题,是一个复习题,也是一个改进的题目。
我们前面讨论过的牛郎织女的约会问题。我们再来看看现在,我们有没有更好的办法解决这个问题。如果牛郎要给织女发信息,七夕相约鹊桥会。
>
<p>织女:<br>
七月初七晚七点,鹊桥相会。不见不散。<br>
牛郎</p>
你能够帮助牛郎想想吗?该怎么使用单向散列函数和消息验证码,来防范约会信息被恶意修改?你建议的办法还有没有其他的问题?
欢迎在留言区留言,记录、讨论你的想法。
好的,今天就这样,我们下次再聊。
**附:表格中算法参考文献链接**
- HmacMD5[RFC 1321 ](https://tools.ietf.org/html/rfc1321)
- HmacSHA1[FIPS 180-1](https://csrc.nist.gov/publications/detail/fips/180/1/archive/1995-04-17)
- HmacSHA224[FIPS 180-3](https://csrc.nist.gov/publications/detail/fips/180/3/archive/2008-10-31)
- HmacSHA256、HmacSHA384、HmacSHA512 [FIPS 180-2](https://csrc.nist.gov/publications/detail/fips/180/2/archive/2002-08-01)
- HmacSHA512/224、HmacSHA512/256 [FIPS 180-4 ](https://csrc.nist.gov/publications/detail/fips/180/4/final)
- HmacSHA3-224、HmacSHA3-256、HmacSHA3-384、HmacSHA3-512[FIPAS 202](https://csrc.nist.gov/publications/detail/fips/202/final)

View File

@@ -0,0 +1,142 @@
<audio id="audio" title="14 | 加密数据能够自我验证吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7e/bc/7e033e52fbfae2954222303ee056f0bc.mp3"></audio>
你好,我是范学雷。
还记得上一讲,我们讲的消息验证码吗?我们讨论过可以使用消息验证码来验证消息的真伪。但是,不知道你有没有注意,在上一次讨论中,我们并没有讨论该如何安全地传递待验证消息。
而且,待验证信息的传递还是通过明文的方式进行的,这种方式,信息的私密性会受到影响。我们前面讨论过,单独的加密并不能解决信息的有效传递问题,总是存在这样或者那样的问题。
那么,我们能不能把消息验证码和信息加密结合起来,既保持信息的私密性,也保持信息的完整性呢?这就是我们这一次要解决的问题。
## 先加密还是后加密?
想要保持信息的私密性,我们可以在信息传输之前,把明文数据加密成密文数据,然后传输密文数据。如果我们还想要保持信息的完整性,我们就要使用消息验证码。
第一个来到我们面前的问题是消息验证码和信息加密该怎么结合起来或者换一种说法就是怎么构造可认证的加密Authenticated Encryption (AE))呢?
加密和验证组合起来的方式不外乎三种方案。
### 加密并验证
第一种方案就是加密明文数据计算明文数据的消息验证码输出密文数据和验证码。这种方案我们简称为加密并验证。安全外壳协议SSH就是采用加密并验证的方案。
<img src="https://static001.geekbang.org/resource/image/d9/8c/d9b581e531aa5d05ece275a302f4168c.jpeg" alt="">
这个方案的消息验证码,保护的是明文信息的完整性,而不是密文信息的完整性。如果明文信息相同,它的消息验证码也是相同的。从攻击者的角度看,如果发现两个相同的消息验证码,就可以猜测明文信息大概率是相同的。
我们前面反复讨论过,为什么要使用初始化向量来避免重复的明文生成重复的密文。这个方案,又一次把这个缺陷暴露了出来,只不过现在,我们是通过消息验证码来判断的。
### 加密后验证
第二种方案加密明文数据计算加密数据的消息验证码输出密文数据和验证码。这种方案我们简称为加密后验证。IPSec协议采用的就是加密后验证的方案。
<img src="https://static001.geekbang.org/resource/image/48/a9/481b4ff097445e77b857ed393365a3a9.jpeg" alt="">
这个方案的消息验证码,保护的是密文信息的完整性,而不是明文信息的完整性。由于密文是从明文演算过来的,也就间接地保护了明文的完整性。
另外,只要加密算法不把相同的明文信息加密成相同的密文信息,它的消息验证码也就是不同的。所以,这个方案没有上面的加密并验证方案的安全问题。
### 验证后加密
第三种方案则是计算明文数据的消息验证码加密明文数据和验证码输出密文数据。这种方案我们简称为验证后加密。SSL协议采用的就是验证后加密的方案。
<img src="https://static001.geekbang.org/resource/image/f5/a8/f5c74b157fe850fb7d2c0d8yye1219a8.jpeg" alt="">
这个方案的消息验证码保护的是明文信息的完整性而不是密文信息的完整性。如果我们把明文信息和消息验证码看作是一个数据我们前面提到的CBC攻击方案是不是似乎又回来了实际的攻击方案比我们前面讨论的复杂如果你有兴趣可以看看[这篇论文](http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.106.5488&amp;rep=rep1&amp;type=pdf)。
每一种方案都有重量级的协议支持。重量级的协议都能使用这些方案,我们能不能任意选用哪一款方案呢?我觉得答案是很明显的。
## 该选用哪一个方案?
有了重量级协议的支持,给了我们一个很好的借口。似乎,我们可以选用这其中任何一个方案,把它用到我们的应用程序里。不过,重量级的协议也会有安全问题,有重量级协议撑腰,并不意味着它就是安全的。
由于新的密码分析技术的进展尤其是前面我们讨论过的BEAST攻击这种新技术的出现加密并验证以及验证后加密这两种方案都受到了很大的挑战和质疑。
接下来在2014年以后无论是SSH协议还是TLS协议都提供了加密后验证的选项用来提高协议的安全性。其中2018年新发布的TLS协议甚至完全抛弃了CBC模式也不再使用上述的任何一个方案。
**所以,如果我们只能从上述三个方案中间选择,加密后验证这个方案目前来说,是最安全的方案**。截止到我准备这一讲的时候,就我自己的知识范围里,只要算法选择得当,加密后验证方案还不会出现致命的安全漏洞。
答案似乎很清楚了,结论就是,在应用程序里,我们可以放心使用加密后验证方案。 在你决定要把这个结论记下来之前你想不想知道新的TLS协议采用了什么样新的加密方案这个新的加密方案应用程序也可以使用吗
## 带关联数据的加密
到目前为止,我们都没有讨论,信息是怎么传递给对方的。无论是在互联网里,还是现实生活中,我们要传递给对方的信息,并不单纯只有信息本身。
生活中信件的传递,需要信封,信封上还要贴邮票、盖邮戳;快递的传递,要有包裹,包裹上要贴快递单。信封和包裹,虽然是用来携带具体信息的,但其实也是信息的一部分。信封里的信件是要保密的,但是信封上的信息是公开的。信封上的信息虽然是公开的,但是同样不可更改。
网络上的信息也是这样,而且更复杂。比如一段网络数据,除了要携带应用要传递的信息外,它的信封上一般还要有版本号,信息类型以及数据长度等信息。
<img src="https://static001.geekbang.org/resource/image/ae/4d/aee58eaa5cdb1b9ec7a63df590f6324d.png" alt="">
如果一段信息的版本号或者数据类型被篡改,接收方就没有办法正确解读接收到的信息;如果信息的数据长度被修改,接收方就没有办法判断接收的数据是不是完整的,从而影响系统的读写效率,甚至进而存在拒绝服务攻击的风险。
那有没有办法保护信封上的信息,也就是公开部分信息的完整性?
带关联数据的加密就是用来解决这个问题的。在解决公开信息的完整性问题的同时一般的算法设计也会同时解决掉私密信息的完整性问题。所以这一类算法通常也叫做带关联的认证加密Authenticated Encryption with Associated DataAEAD
<img src="https://static001.geekbang.org/resource/image/e5/46/e5b3fb7f799d6ae2d8fc4276572a0546.jpeg" alt="">
不同于我们前面讨论过的加密函数,带关联的认证加密的加密函数需要三个输入数据:
- 加密密钥;
- 明文信息;
- 关联信息。
输出结果包含两段信息:
- 密文信息;
- 验证标签。
一般来说,验证标签可以看做是密文信息的一部分,需要和密文信息一起传输给信息接收方。
如果改变明文信息,密文信息和验证标签都会变化,这一点,就解决了明文信息的验证问题。如果改变关联信息,至少验证标签会不一样,这一点,解决了关联信息的验证问题。
对应地,带关联的认证加密的解密函数需要四个输入数据:
- 解密密钥;
- 关联信息;
- 密文数据;
- 验证标签。
而输出的是明文信息。在解密过程中,如果密文信息或者关联信息验证失败,明文信息不会输出。换句话说,只有明文信息和关联信息的完整性都得到验证,才会有明文信息输出。
<img src="https://static001.geekbang.org/resource/image/a9/73/a9920f861a3fd6103f3d325ddac8b573.jpeg" alt="">
这么一看,带关联的认证加密既解决了需要保密信息的私密性和完整性,也解决了关联信息的完整性问题,这使得带关联的认证加密算法成为目前主流的加密模式。
带关联的认证加密算法的广泛使用也使得曾经占据主导地位的CBC算法可以从容地退出历史舞台。因为**带关联的认证加密算法能够进行自我验证**。
自我验证,就意味着解密的时候,还能够同时检验数据的完整性。这无疑减轻了应用程序的设计和实现压力。有了带关联的认证加密算法,应用程序再也不需要自行设计、解决数据的完整性问题了。
现在有哪些流行的带关联的认证加密算法呢?下一次,我们接着聊这个话题。
## Take Away今日收获
今天,我们讨论了使用消息验证码构造可认证加密的三种方案。它们分别是加密并验证、加密后验证以及验证后加密。其中,加密并验证以及验证后加密这两种方案存在安全缺陷;加密后验证是一个更安全的方案。
另外我们还讨论了带关联的认证加密的基本思路_。_带关联的认证加密除了提供可认证的加密保护私密数据的完整性之外还通过关联数据保护公开数据的完整性。**能同时保护私密数据的完整性和公开数据的完整性,这是带关联的认证加密算法要解决的主要问题**。
通过今天的讨论,我们要:
- 知道加密并验证以及验证后加密这两种方案存在安全缺陷,尽量不要使用这两种方案;
- 了解带关联的认证加密,以及它要解决的问题。
## 思考题
今天的思考题,我们继续挖掘牛郎织女的约会问题。
这一次讨论完,我们再来看看现在有没有更好的办法解决这个问题。为了方便,我把描述部分又抄写一遍。如果牛郎要给织女发信息,七夕相约鹊桥会。
>
<p>织女:<br>
七月初七晚七点,鹊桥相会。不见不散。<br>
牛郎</p>
你能够帮助牛郎想想吗?该怎么来保证约会信息的私密性和完整性?传递的信息该怎么构造?当然,按照惯例,我们还是要想一想,你建议的办法还有没有其他的问题?
欢迎在留言区留言,记录、讨论你的想法。
好的,今天就这样,我们下次再聊。
祝你圣诞节快乐!

View File

@@ -0,0 +1,132 @@
<audio id="audio" title="15 | AEAD有哪些安全陷阱" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f4/ca/f42251b0d84107f8a8f265cdf5940aca.mp3"></audio>
你好,我是范学雷。
上一讲我们讨论了加密数据如何才能够自我验证自我验证就是指解密的时候还能够同时检验数据的完整性。我们还谈到了带关联数据的认证加密AEAD是目前市场的主流思路。
我们有了带关联的认证加密算法,应用程序再也不需要自行设计、解决数据的完整性问题了。但问题是,如果我们要在应用程序中使用带关联数据的认证加密,有哪些算法可以使用?
带关联数据的认证加密算法,有没有需要小心的安全陷阱?这是我们这一次需要解决的问题。
## 有哪些常见的算法?
还是老规矩我们先来看看有哪些常见的算法。现在常见的AEAD模式有三种
- GCM
- CCM
- Poly1305。
一般地我们可以把带关联数据的认证加密看做一个加密模式就像CBC模式一样我们可以和前面提到的AES等加密算法进行组合。但ChaCha20和Poly1305通常组合在一起Camellia与AES通常和GCM以及CCM组合在一起。
由于AEAD模式相对较新而3DES/DES等遗留或者退役算法又存在明显的安全缺陷所以我们一般不会使用遗留或者退役算法的AEAD模式。
如果我们重新整理一下,综合考虑加密算法和加密模式,那么,当前推荐使用的、有广泛支持的、风险最小的算法是:
- AES/GCM
- ChaCha20/Poly1305
- AES/CCM。
如果你的应用程序使用上述三个算法里的任何一个,就可以规避掉我们前面讨论的很多风险。经过了十多次的讨论,不知道你有没有变得小心谨慎?**我们在使用任何算法之前,都要先存疑,后求证**。这时候,你应该要问了,那这三个算法有没有风险?
## AEAD有风险吗
还记得吗我们上面提到的AEAD算法都需要使用初始化向量。虽然ChaCha20/Poly1305不属于分组密码的分类但是这个算法也需要使用初始化向量。
我们前面特意讨论过初始化向量选择的一个原则:**在一个对称密钥的生命周期里,初始化向量不能重复**。所以对于AES/GCM算法同样的对称密钥一定不要使用重复的初始化向量。否则的话就存在安全漏洞。
你发现了,**初始化向量的重复问题,<strong><strong>就**</strong>是使用AEAD算法的最大风险也是最难处理的风险</strong>。没有密码学经验的开发者可能会忽视这个风险,相应的应用程序也可能因此存在严重的安全问题。
在前面讨论分组对称密码的时候,我们讨论过两个初始化向量选择的方案:使用随机数以及使用序列数。然而,这两个方案也是各有各的缺陷。
## 单相思的随机数
我们先来看看,使用随机数作为初始化向量有什么问题呢?
谈到使用随机数作为初始化向量,我们指的是信息发送方的行为。既然信息的发送方使用的是随机数,信息的接收方怎么能知道这个随机数的数据呢?
不使用同样的初始化向量,信息接收方是没有办法解密加密数据的。**该怎么解决初始化向量在信息发送方和接收方之间的同步问题?这是使用随机数作为初始化向量必须要考虑清楚的问题。**
幸运的是对于上述我们提到的AEAD算法初始化向量并不需要保密。一个常用的方案信息发送方发送加密信息的时候可以把明文的初始化向量一起发送。信息接收方直接使用接收到的明文初始化向量就可以解密加密数据了。
不过随之而来的问题是如果有攻击者修改了明文传递的初始化向量信息接收方能够察觉吗这个答案是肯定的。不同于没有验证的加密算法AEAD有自我验证的能力如果初始化向量被篡改解密过程中数据验证这一关是通不过去的。
也就是说,解密不会成功,篡改可以被检测出来。
还有一个问题,我们需要注意随机数的大小。对于这个问题,我们要再次考虑初始化向量选择的原则,**在一个对称密钥的生命周期里,初始化向量不能重复。**也就是说,随机数必须有足够的容量,使得在对称密钥的生命周期里,都不太可能出现重复的随机数。
最常用的经验数据是使用64位的随机数。64位的初始化向量支持2^64的加密运算。大部分的应用程序都难以达到这么大的运算量。
不过,需要注意的是,虽然随机数的文字里带有数字,我们也不能把随机数看做是整数。在密码学的范畴里,随机数指的是随机的数据,不是随机的数字。
所以千万不要移除随机数头尾的零要保持随机数完整的位数。64位的随机数就一定要有64位的数据。即使头尾可能是零也不能掐头去尾变成63位或62位或者画蛇添足变成65位。
使用随机数作为初始化向量,还有一个常被忽视的小缺陷。随机数的初始化通常是一个费时费力的过程。在应用程序里,大量的随机数实例的初始化可能会造成应用程序的性能问题,甚至包括应用程序的停顿。
不过,对于有经验的程序员来说,这是一个好解决的问题。只要选择好随机数发生器、减少随机数实例数就可以了。具体的情况,我们后面再讨论。
除此之外,使用随机数作为初始化向量,还有一个小小的缺陷,就是需要额外传递初始化向量。不过,对于大部分应用来说,这点小缺陷,并不是什么问题。
## 难断舍的序列数
在部分场景中随机数的产生是一个费时费力的过程。但是对于AEAD算法只要初始化向量不重复就行并不要求初始化向量不可预测。避免昂贵的随机数是我们在使用密码学技术时要经常考虑的问题。**使用序列数作为初始化向量,就是一个最流行的方案。**
如果使用序列数,你需要注意的是,使用静态的对称密钥,特别需要注意序列数的静态化。比如说,对称密钥存在磁盘上,每次启动程序,都加载该密钥。那么,如果序列数没有对应的保存下来,每次启动的应用程序就有可能使用重复的序列数,从而带来严重的安全问题。
如果序列数也保存在磁盘上,并且每次程序启动时,加载密钥的时候,也加载序列数,我们也需要注意多线程的同步问题,其实也不省心。
使用静态的对称密钥的问题很多我们后面还要讨论其他的安全问题。根本的办法就是避免使用静态的密码。对称密钥最好使用的时候再生成它使用完立即销毁不保存不长期使用不超过7天
理想地,如果信息发送方的每一个信息,信息接收方都能够收到,中间没有攻击者的干扰,使用序列数的初始化向量就不需要传递。信息发送方和接收方各自独立维护一个序列数计数器,就可以保持初始化向量的同步问题了。
但是,这种状况太理想了,有很强的局限性,只能在严格的、没有序列数错配的场景使用。
在网络环境中,信息可能丢失,或者被丢失,或者被攻击者插入攻击信息,或者被攻击者丢弃部分数据,这些都会引起序列数的错配问题,打乱信息接收方的节奏,错乱信息接收方的计数器。
怎么能够获得序列数开销小的好处,同时避免序列数错配的问题呢?
- 序列数的开销小,直接受益方是信息发送方;
- 序列数错配,直接受害方是信息接受者。
如果信息接收方不需要保持序列数的状态,不需要匹配序列数,也就没有序列数错配的烦恼了。
其实,答案也很明显。信息发送方可以像使用随机数初始化向量一样,发送加密信息的时候,把明文的初始化向量(也就是序列数)一起发送。信息接收方直接使用接收到的明文初始化向量,而不用理会这个初始化向量是一个随机数,还是一个序列数,或者是其他的什么数据。
**传送明文的序列数这一个方案,兼顾了信息发送方的效率,规避了信息接收方的顾虑,是目前最常用的初始化向量选择方案**
不过,还是要说一点注意的,既然信息接收方不再需要判别接收到初始化向量是一个随机数还是一个序列数,信息发送方就要兼顾到信息接收方的处理便利。
即使序列数是1也要按照固定的位数传输比如我们前面讨论过的固定的64位数据。如果序列数较小可以前面补零直到满足64位的数据要求。
到目前为止我们已经知道了新的应用程序里应该优先使用AEAD算法。AEAD算法需要初始化向量明文信息关联信息还有密钥。
你有没有想过这样的问题:密钥从哪里来的?密钥应该是什么样的?回答这些问题之前,我们要先讨论一个对称密钥依赖的技术,随机数。我们下次聊聊随机数以及随机数发生器。
## Take Away今日收获
今天,我们讨论了常见的带关联数据的认证加密算法,以及使用带关联数据的认证加密算法的常见问题,也就是初始化向量的问题。
我们还列出了当前推荐使用的、有广泛支持的、风险最小的算法。它们是:
- AES/GCM
- ChaCha20/Poly1305
- AES/CCM。
通过今天的讨论,我们要:
- 知道常用的三个AEAD算法
- 知道AEAD算法初始化向量不能重复的要求
- 知道AEAD算法常用的初始化向量选择方案。
## 思考题
今天的思考题,是一个需要动手的题。
在不同的章节,我们花了很大的精力来讨论初始化向量的问题。这是一个特别容易忽视的环节。
在你正在开发的项目中,或者你关注的开放源代码项目中,试着搜索、统计一下初始化向量的使用状况。加密端有没有使用重复的初始化向量?解密端有没有办法检验初始化向量的重复?初始化向量选择的是随机数还是序列数,或者随机数和序列数的组合?
你有没有发现不恰当的初始化向量使用方案?
欢迎在留言区留言,记录、讨论你的发现。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,171 @@
<audio id="audio" title="16 | 为什么说随机数都是骗人的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f6/5f/f669dd40f56b474b3b1cc1461fd5635f.mp3"></audio>
你好,我是范学雷。
今天,我们来讲一个之前总提到的知识点:随机数。比如,我们在讲初始化向量时,提到可以使用随机数来避免重复。事实上,随机数还有更广泛的用途。可以这么说,**只要使用密码学的技术,我们一定会用到随机数。**
说起来也很巧,当我准备随机数这篇文章思路的时候,朋友圈里有一条感慨,说道:
>
“人能做出两种随机数。一种是欺骗自然的,算的很快,状态域超大,统计上随机;一种是欺骗人的,通常无法预测下一个是多少,通常算的慢”。
其实,欺骗自然的随机数,也是用来骗人的。这么说来,难道所有的随机数都是骗人的?为什么说随机数是骗人的?骗人的随机数还有现实的意义吗?这是这一次我们要讨论的问题。
今天的内容有点多,不过我给你多分了几个小模块,详细地分析了随机数的每个问题,这样会让你更清楚地、一次性地解决随机数的相关知识点。好,我们开始!
## 真的有随机数吗?
第一个问题就是,随机数真的存在吗?
数这个东西是人类的发明,不是自然界产物。所以,当我们说随机数的时候,一定是人造的东西。随机是自然世界的主旋律,你不可能找到相同的两片叶子,也不可能两次踏进同一条河流。
但是,在科学的世界里,随机从来就不存在,或者说随机都被当做噪音简化过滤掉了。因为科学研究的是确定性、可重复的科学。不确定的、不可重复的随机数,不属于科学的范畴,也走不进科学的领域。
>
冯·诺伊曼:若人们不相信数学简单,只因他们未意识到生命之复杂。
想一想计算机吧。计算机之所以存在,就是因为它的确定性。同一台计算机,相同的数据输入,一定要产生相同的结果。两台不同的计算机,相同的数据输入,也一定要产生相同的结果。
这是计算机存在的基础。怎么会有随机的数据和计算结果呢?所以说,在计算机的世界里,随机数的确是不存在的。
>
冯·诺伊曼:任何考虑使用数学产生随机数的方法都是不合理的。
这是不是在开玩笑?那我们前面多次提到过的随机数算是怎么回事?其实,我们能够创造出来的,都不是真的随机数。这些所谓的随机数,要么是骗机器的,要么是骗人的,或者两者都骗。严格一点,我们把它们叫做伪随机数。
既然没有真正的随机数,当然也就没有必要区分真的随机数和假的随机数。所以,我们通常把伪随机数简单地叫做随机数。只要我们明白,没有真正的随机数,一个更顺溜的名字节省了很多口水,却不会引起太多的麻烦。以后没有特别声明,我们讨论的随机数,都是伪随机数。
那么,既然没有真随机数,假随机数的边界在哪里呢?什么样子的伪随机数能够骗得过机器、骗得过人呢?要想回答这个问题,我们先要了解什么样的数才算是随机数。
## 随机数怎么来的?
先来问你一个问题“123456”是一个随机数吗那“123456”比“654321”更随机吗我们很容易有这样的问题。遗憾的是这样的问题在密码学里是没有实用意义的。一个数的随机性说的永远是还不存在的、未来的那个数的不可预测性。
**即便我们知道了随机数产生的机制,以及所有的已经产生的随机数,我们也无法预测下一个随机数是什么。**这就是随机数的第一个特点:不可预测。
那怎么检查未来的那个数是随机的呢?未来的那个数还不存在,当然也没有直接的方法。间接的办法,就是产生很多随机数,然后检测这些随机数能不能通过所有的随机性统计检验。在数学上,有很多种随机性统计检验的理论和方法,我们就不去讨论这么折磨人的数学问题了。
记住一点,只要有一个随机性统计检验方法没有通过,这个随机数产生的机制就是有问题的。换句话说,对于合格的随机数,我们没有办法验证下一个随机数不随机。这就是随机数的第二个特点:无法证伪。
到这里,不知道你有没有找到一对矛盾的地方。我们上面不是说过吗,“同一台计算机,相同的数据输入,一定要产生相同的结果”,那怎么才能够产生不可预测的随机数呢?
产生随机数的诀窍,就是把“相同的数据输入”拆成两部分。一部分是私密的数据,一部分是公开的数据。如果我们能保护住私密的数据,不让人知道,也不让机器拷贝,那么下一个数据是什么,对于别人或者别的机器来说,可能就是无法预测的。
当然,对于秘密的持有者,下一个数据是什么是确定的,不是不可预测的。那么,随机性统计检验呢,当然也得假装这个私密数据不存在。
这个私密数据的质量和计算输出的算法,就决定了随机数的质量。
幸运的是,现在几乎所有主流的计算机,都内嵌了随机数发生器;主流的操作系统或安全类库,都提供了随机数的接口。一般来说,我们不需要自行设计随机数算法,保守私密数据的秘密。
但是这并不意味着我们就可以高枕无忧了。随机数的应用,我们还需要注意两点:
- 第一点是,随机数的产生可能会阻塞;
- 第二点是,随机数的强度要匹配。
## 阻塞的随机数有什么麻烦?
随机数也是有质量要求的为了保证随机数的质量随机数发生器的设计需要收集随机信息比如计算机的噪音周围的温度CPU的状态硬盘的状态用户的行为等等。
这些信息收集,是需要时间的,所以有的时候,产生下一个随机数的时候,就会阻塞。阻塞的时间还不确定,有的可能是纳秒级别的,有的可能是秒甚至分钟级别的。而对于一个高吞吐量的系统,微秒级别的阻塞可能都是不能忍受的。
随机数阻塞会带来什么麻烦呢?一般来说,在一个应用程序里,密码学算法要保护的都是关键的信息或者流程,比如说身份认证或者数据加密。如果随机数有阻塞,这个应用程序就会停顿。应用程序的停顿,会使得占有的资源不能及时地释放,降低系统的效率,增加用户等待时间。
更要命的是,随机数阻塞的时间不确定,有时候时间长到无法忍受,有时候短到毫无影响。除了影响系统的效率和吞吐量之外,还会影响用户体验的一致性,影响程序运行的一致性,也使得出现的问题难以排查。所以,除非万不得已,**我们尽量不要使用阻塞的随机数发生器**。
## 非阻塞的随机数还能随机吗?
不使用阻塞的随机数发生器,也就意味着有非阻塞的随机数发生器。那么,难道非阻塞的随机数发生器就不需要收集随机信息了吗?不是的,非阻塞的随机数发生器也需要收集随机信息。**区别在于随机信息怎么使用,以及使用的频率。**
阻塞的随机数发生器的每一个随机数,都要损耗随机信息;而非阻塞的随机数发生器,可能仅仅在开始的时候就损耗随机信息(比如说一台计算机开机的时候),然后,随机数的发生就不再损耗随机信息了。只要不再损耗随机信息,就不会有收集随机信息带来的阻塞了。
不再损耗随机信息,还能保证随机数的随机性吗?
## 什么是确定性的随机数发生器?
有一类随机数的算法叫做确定性的随机数发生器Deterministic Random Bit Generator DRBG使用的就是单向散列函数。
怎么解释确定性?确定性说的就是相同的输入,有相同的运算结果。这也就意味着,对于确定性的随机数发生器来说,它的下一个随机数是确定的,是可以计算出来的,当然也是可以预测的。
计算结果可以预测,这还算什么随机数?
我们上面说过,产生随机数的诀窍,就是持有一部分私密的数据。随机数发生器持有私密的数据,所以计算结果对它来说,是确定的。如果我们不知道私密的数据,从随机数发生器外面看起来,下一个随机数还是能够做到貌似不可预测的。
怎么做呢?
## 怎么使用哈希函数实现随机?
你还记得我们前面讨论过的单向散列函数的性质和消息验证码的工作原理吗?**使用单向散列函数实现非阻塞的随机数发生器的关键,就是把这部分私密的数据,当做单向散列函数输入的一部分,然后再加入不会重复的数据**,比如序列数。
为什么这个办法是可行的呢?使用单向散列函数和有限的随机信息,随机性能够满足“不可预测”和“无法证伪”这两个检验指标吗?这需要我们回头再看看单向散列函数的特点。
第一个特点,单向散列函数正向计算容易,逆向运算困难。我们使用了私密数据作为单向散列函数输入的一部分,由于逆向运算困难,只要这部分私密数据保护得好,攻击者是没有办法通过运算结果计算出私密数据的。否则,也就意味着破解了单向散列函数。
第二个特点,单向散列函数运算结果均匀分布,构造碰撞困难。我们使用了不会重复的数据作为单向散列函数输入的另外一部分,由于运算结果均匀分布(也就是严格的雪崩效应),如果不知道私密数据,运算结果就是无法预测的。
同时严格的雪崩效应也就意味着每一次计算输出数据的每一位都有50%的概率会发生变化。如果不知道私密数据,运算结果的随机性也是无法证伪的。
所以,你看,即使只损耗有限的随机信息,使用好单向散列函数,也能做出来合格的随机数发生器。
既然使用单向散列函数,也逃不掉单向散列函数的种种陷阱。所以,一般来说,我也不建议你自己设计、实现随机数发生器。现在大部分的密码学类库里,都应该支持了非阻塞的随机数发生器。**我们要优先使用这些已经经过验证的、成熟的设计和实现。**
使用这些类库时,有没有需要注意的事情呢?这是我们要重点关注的问题。其中,最重要的,就是安全强度的匹配。
## 随机数也有强度吗?
对于阻塞的随机数发生器来说。随机数每一位损耗的都是计算机收集的随机信息。**随机数的位数也就是它的安全强度。**所以,通常的,我们不担心它的安全强度。
但是对于非阻塞的随机数发生器,它只损耗有限的随机信息,通过单向散列函数来计算随机数。这也就意味着两个问题。
第一个问题是单向散列函数本身是有安全强度的。相应的随机数发生器的安全强度不会超过单向散列函数的安全强度。比如说SHA-256的安全强度是128位那么使用它的随机数发生器的安全强度一般也不会高于128位。
第二个问题是单向散列函数的输出长度是固定的而随机数的长度可能并不匹配散列值的长度。SHA-256的散列值长度是256位如果要产生一个512位的随机数就需要至少两个散列值的拼接。
这会带来什么问题呢通常地一个256位的随机数就应该有256位的安全强度一个512位的随机数就应该有512位的安全强度。但是由于单向散列函数自身的限制基于SHA-256的随机数发生器它的安全强度不会超过128位无论这个随机数是256位还是512位。这就带来了很大的困扰。
需要多长的随机数,我们就使用随机数发生器生成多长的随机数,而不需要考虑随机数的强度够不够,这是人们的使用习惯。 由于这样的习惯,要想漂亮地解决上面的问题就变得很棘手。
甚至很多密码学类库都没有准备好解决这样的问题的接口其中包括Java语言。Java语言默认使用很高的安全强度这样应用程序就不用担心当前会有实质性的安全问题了。但是从长远看这样的解决方法还不够灵活没有给应用程序提供更高安全强度的选项。
## 有哪些常见的随机数算法?
目前有两类常见的随机数算法它们是基于单向散列函数的Hash-DRBG和基于HMAC的HMAC-DRBG。一般来说Hash-DRBG算法初始化的时候需要使用有限的随机信息。而对于HMAC-DRBG算法来说这个有限的随机信息被保密的对称密钥取代了。
无论是随机信息还是已知的对称密钥,都是需要保密的信息。所以,使用保密的对称密钥取代随机信息,并没有安全上的妥协。
不过,使用保密的对称密钥取代随机信息还有一个好处。一般来说,对于应用程序来说,随机信息是透明的,只有机器知道,应用程序并不知道随机信息是什么。
所以这样的随机数是应用程序无法复制的。而使用对称密钥只要知道了对称密钥应用程序就有复制随机数的办法。这一点对于很多应用场景都有着重要的意义。可以说HMAC-DRBG给了应用程序更多的灵活性。
无论Hash-DRBG还是HMAC-DRBG它们的安全强度都是由所使用的单向散列函数的安全强度决定的。**使用的时候,要注意安全强度的匹配问题,确保随机数能够提供足够的安全强度。**
上一讲结尾的时候,我们说过随机数是对称密码依赖的技术。除了初始化向量以外,随机数也是生成对称密钥的基础。该怎么使用随机数生成对称密码?我们下一次讨论这个话题。
## Take Away今日收获
今天,我们讨论了和随机数概念相关的话题,比如,真的随机数存不存在?伪随机数有什么特点?我们还讨论了阻塞的随机数发生器和非阻塞的随机数发生器,以及怎么使用单向散列函数来实现随机函数。
我们还讨论了随机数的强度随机数的位数也就是它的安全强度。还有两类常见的随机数算法也就是Hash-DRBG还是HMAC-DRBG。我们使用的时候要注意安全强度的匹配问题确保随机数能够提供足够的安全强度。
通过今天的讨论,我们要:
- 知道随机数发生器可能会阻塞,我们应该尽量使用非阻塞的随机数发生器;
- 知道随机数的算法也有安全强度的限制,我们要小心安全强度错配的问题。
## 思考题
今天的思考题,是一个动手题。
在你正在开发的项目中,或者你关注的开放源代码项目中,试着搜索一下随机数的使用。重点关注如下的问题:
- 随机数发生器使用了什么算法?
- 随机数的安全强度足够吗?
- 随机数发生器会不会阻塞?
如果你发现了问题,有没有改进的建议?
欢迎在留言区留言,记录、讨论你的发现和建议。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,146 @@
<audio id="audio" title="17 | 加密密钥是怎么来的?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/bf/05/bfba9a7f7a86a357d1fa23680933a805.mp3"></audio>
你好,我是范学雷。
到目前为止,你已经跟我一起走了很长的路了。不知道这一路上,你有了哪些心得和体会?对密码学是不是多了很多新的认知和想法?这一讲,我们继续上路,踏足密码学的世界。
前几讲,我们花了很长时间讨论了对称密钥的算法,以及使用对称密钥算法要注意哪些陷阱。但是,不知道你有没有注意到,一直有一个悬而未决的问题。
我们要使用对称密钥算法,总得有对称密钥吧。那么,对称密钥是从哪儿来的?这是我们这一次要讨论的问题。
# 合格的对称密钥什么样?
对称密钥从哪里来的?在讨论这个问题之前,我们先要弄清楚另外一个问题。一个合格的对称密钥,应该满足什么样的条件呢?只有知道了需求,我们才能有解决的方案。
## 对称密钥的长度
还记得我们之前提到过的AES-128和AES-256算法吗
其中的128和256指的是密钥的长度。也就是说AES-128需要128位的密钥AES-256需要256位的密钥。一般来说一个对称密钥算法的密钥长度是固定的。这就是对称密钥的第一个要求**对称密钥的长度是由对称密钥算法确定的。**
当然这并不意味着一个对称密钥只能用于一个加密算法。一个对称密钥一般可以用于任意一个对称密钥算法只要这个对称密钥满足算法要求的密钥长度。比如一个256位的对称密钥既可以用于AES-256也可以用于ChaCha20。
可是,一个对称密钥用于两个不同算法,这不是我们推荐的用法。因为,我们要考虑算法破解的风险。如果一个算法被破解了,那么它使用的对称密钥可能也就被破解了。我们不希望一个算法的失败连累另一个算法要保护的数据。
所以大部分的应用程序接口都不会限制一个对称密钥只能用于一个算法。但是我们要有意识地避免这种情况。比如说如果一个对称密钥已经用于AES-256的加密计算了就不要再把它用于ChaCha20或者其他的加密算法了。
## 对称密钥的强度
说完了对称密钥的长度,我们来看对称密钥的强度。
有印象的话你应该记得AES-128算法的安全强度是128位AES-256算法的安全强度是256位。可是如果没有高质量的对称密钥这样的安全强度就没有意义。
举个例子如果密钥只能是阿拉伯数字那么128位的密钥就只有10^16种可能性。也就是如果使用蛮力攻击的话最多需要10^16次尝试加密密钥就能够找到加密数据就能够被破解。
如果我们把10^16转换成按位表示的安全强度也不过就是53位的安全强度这离128位的安全强度可相差太远了。所以**对称密钥的强度一定要和加密算法的强度匹配。**比如说吧AES-128算法需要128位的密钥这个密钥就要有128位的强度。
对于任意给定的密钥我们并不一定能够判断它的强度是不是足够。比如说我们并不能判断“123456”是不是比“135246”强度更大。所以当我们说密钥强度的时候其实我们关注的还有产生密钥的机制。
**首先,产生密钥的机制要有匹配的强度。**如果产生密钥的机制只有128位的安全强度它就不能提供256位安全强度的密钥。简单地说攻击产生密钥的机制就可以了。
**其次密钥在它的长度上要均匀分布。也就是说这个密钥的每一位是0还是1的概率都是50%。如果不能做到均匀分布,就会降低密钥的安全性。**比如说,我们前面提到的阿拉伯数字的密钥,就是密钥没有做到均匀分布,导致安全强度降低的例子。
还有,**密钥生成机制产生的密钥要随机**。也就是说下一个密钥要均匀分布而且不可预测。如果下一个密钥不是随机的那么下一个密钥的安全性就没有保障。如果上一个密钥是“123456”下一个密钥是“123457”只是简单地递增那么这两个密钥都是不合格的密钥。
总结起来就是,**一个合格的对称密钥,它的长度和强度要与对称密钥算法相匹配。**
## 对称密钥的秘密
在[第6讲](https://time.geekbang.org/column/article/316802),我们提到,密钥的保密性和算法的安全性是对称密钥算法安全的两个关键因素。
既然密钥需要保密,那当然也就意味着密钥有秘可保。没有秘密的密钥当然谈不上保密,不能保密的密钥也没法保护数据的机密性。总之,对称密钥要有秘密。
不过,需要注意的是,秘密也是有安全强度的。比如说,很多地方的民俗,有“猜有无”的酒令。猜的人猜测对方握紧的手里有没有东西。
对于出酒令的人来说手里有没有东西当然是一个秘密。但是这个秘密被猜中的几率是50%。如果换算成密码学的指标也就是只有1位的安全强度。1位的安全强度当然简单好玩适合于饮酒助兴。可是只有1位的安全性并不适合在计算机系统中保护我们的机密数据。
**一个合格的对称密钥,要有足够的秘密,并且它的长度和强度要与对称密钥算法相匹配。**
这三个需求看起来简单、直观,但是用起来很容易就掉进强度错配、无秘可保的坑里。好了,有了这三个需求,对称密钥从哪里来这个问题,我们就可以来一起讨论它了。
# 对称密钥从哪里来?
那么,对称密钥是从哪里来的呢?你可能觉得这个问题有点怪怪的,其实这个问题,换个说法,就是我们去哪里才能够找到长度和强度都符合要求的秘密?
先看秘密的来源,来源主要有两类:
- 一类是计算机用户持有的秘密;
- 另一类是计算机持有的秘密。
对应地,也就是对称密钥的两种来源。
## 用户持有的秘密
计算机用户持有的秘密,主要表现为只有该用户知道的秘密和只有该用户拥有的秘密两种。比如,我们能够记住的用户口令,是只有我们知道的秘密;我们的指纹,是只有我们拥有的信息。
遗憾的是,我们能够记住的密码很短,一般来说,满足不了对称密钥强度的需求;我们拥有的指纹、面容、虹膜信息,都可以复制,保守这样的秘密是一个极具挑战的任务。陌生的场合,我们摸一摸杯子,睁一睁眼睛,露一下面颊,这些所谓的秘密就都不再是秘密了。
我们拥有的生物特征不可靠,我们能够记住的又太少,那为什么指纹识别和用户密码还这么流行呢?主要原因是还是没有更好的、更简单的办法。
从我接触密码学开始,就已经有人喊口令要消亡了。二十多年了,口令依然活得有模有样,指纹/面部识别也越来越流行,尤其是需要身份认证的时候。
## 使用口令生成对称密钥
既然口令的强度不够,那如果一段加密数据,只有用户参与才能解密,那该怎么办呢?**解决的办法就是分级:使用弱的口令来保护强的密钥,然后使用强的密钥来保护私密数据。**
如果加密数据泄漏了,由于保护它的对称密钥有足够的强度,我们不用担心破解的问题。
而口令,主要用于身份认证和衍生密钥。身份认证和密钥衍生,都不是高频次的运算。 从口令推导出来的对称密钥也不保存、不长留。这些措施,都降低了口令破解带来的数据泄漏风险。
<img src="https://static001.geekbang.org/resource/image/2d/b3/2d9ff1f0dc3c3cedb0186c129ccb6fb3.jpeg" alt="">
使用口令生成对称密钥的办法通常成为“基于口令的密钥推导”。现在常用的基于口令的密钥推导算法是PBKDF2。我不在这里讲这个算法的细节了你可以自己去找一找相关的规范。
不过,我们需要注意的是,由于口令的安全强度不够,很容易被破解,我们需要经常地变换口令,有些公司是强制性要求。如果口令变换了,从它衍生出来的对称密钥当然也就随着变换了。
如果我们直接使用衍生出来的对称密钥加密数据,每次的口令变化,都需要把已经加密的数据重新加密一遍。这可不是好事情!
<img src="https://static001.geekbang.org/resource/image/8e/99/8e2yyc1d93e05379e3b83b4yy43a8699.jpeg" alt="">
所以,通常地,我们也不推荐使用口令推导出来的密钥直接加密需要留存的数据。有什么办法克服这个障碍呢?解决这个问题,我们还要再添加一个环节。也就是使用推导出的密钥保护一个使用时间更长的密钥;而使用时间更长的密钥用来保护私密数据。
<img src="https://static001.geekbang.org/resource/image/f6/19/f6dee390ea9e5971e31857b18768ef19.jpeg" alt="">
多了一个环节以后,如果口令发生变更,我们只需要重新加密留存的对称密钥就行了,而不需要改动已经加密了的数据。那么,这个使用时间更长的密钥是怎么来的呢?
这就需要我们讨论下一个秘密的来源了:计算机持有的秘密。
## 使用随机数生成对称密钥
计算机持有的秘密,主要用的是我们上一节所讨论的随机数。
由于随机数是不可预测的,我们只要把随机数当做秘密信息来保护,就没有其他的人或者其他的计算机能够知道这个秘密。这样看起来,随机数能够完美地契合对称密钥的需求,无论是长度强度,还是保密要求。
需要一个对称密钥的时候,如果我们知道需要的长度、强度,找对强度匹配的随机数发生器,生成一个对应长度的随机数,就可以获得一个对称密钥了。这是一个看起来很简单、直观的办法。
但是,计算机持有的秘密也有一个缺陷,就是没法转换成我们人脑能够记住的东西。我们没有办法记住随机数,更没有办法记住从随机数衍生出来的对称密钥。既然我们没有办法,解密还需要对称密钥,那就需要计算机替我们记住它。
计算机该怎么管理对称密钥,怎么保守密钥秘密?这些问题也就一下子都冒出来了。
正像我们讨论过的一样,使用口令推导出的密钥可以保护使用时间更长的密钥,当然也包括使用随机数生成的密钥。不过这种保护方式,也有很大的局限性。这种局限性又在哪里呢?还有没有其他的方式?这些问题,我们下一次再讨论。
# Take Away今日收获
今天,我们讨论了一个合格的对称密钥应该满足什么条件,以及对称密钥的两个主要来源。
通过今天的讨论,我们要:
- 了解对称密钥要满足的三个条件:长度、强度和秘密。
- 了解产生对称密钥的两个主要办法:使用随机数,或者是基于口令的密钥推导。
- 知道使用基于口令的密钥推导来保护数据的常用办法。
# 思考题
好的,又到了留思考题的时候了。
今天的思考题,我们稍微加大一点难度。不过也不用紧张,我相信只要你去反复撕扯这个问题,不管结论是什么,你都会有收获的。
我们反复强调过,我们能够记住的东西很少,我们能够记住的口令的安全强度也远远不够。那为什么从口令推导出来的密钥就能够更安全呢? 如果口令只有6位数字猜中口令的可能性是10^6。
转换成按位表示的安全强度也不过就是20位的强度。这样的安全强度我们应该担心吗如果这样的担忧是合理的我们应该怎么提高口令的安全性如果使用从口令推导出来的密钥需要注意哪些问题
欢迎在留言区留言,分享你的经验。参与讨论的人越多,我们互相学习、互相启发,能够得到的就越多。
好的,今天就这样,我们下次再聊。
元旦快乐!

View File

@@ -0,0 +1,109 @@
<audio id="audio" title="18 | 如何管理对称密钥?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/3a/dc/3a531db72c5782f411d0a45fac2693dc.mp3"></audio>
你好,我是范学雷。
上一讲,我们讨论了合格的对称密钥需要具备什么条件,以及对称密钥是怎么产生的。但是,了解对称密钥是怎么产生的,是远远不够的,我们还要了解怎么管理这些对称密钥。
在我们讨论怎么管理之前,我们还要再给对称密钥分个类,划分的标准就是对称密钥要不要留存。那么,哪种密钥不需要留存呢?我们该怎么管理呢?这是我们这一次要讨论的问题。
## 对称密钥要不要留存?
你是不是有些迷惑,难道对称密钥不需要留存?如果不留存,为什么还要做密钥管理?
为什么我们要讨论对称密钥要不要留存?因为最好的管理,就是不管理或者少管理。按照这个思路,我们可以把对称密钥分成两类:即用即弃的对称密钥和需要留存的对称密钥。
### 即用即弃的对称密钥
即用即弃的对称密钥,就是用的时候才生成出来,不需要保存,用完了就扔掉的对称密钥。
那这种对称密钥的适用场景是什么呢?**即用即弃的对称密钥可以用在加密数据不需要保存的场景**。比如说, 像HTTPS这样的端到端传输协议它的网络传输数据是加密的而加密的网络传输数据是不需要保存到硬盘里的。
这种情况下对称密钥可以使用计算机持有的秘密也就是使用随机数来生成。还记得什么是计算机持有的秘密吗你可以复习一下17讲来回顾一下这个知识点。
当然,有的时候,加密数据需要保存的场景,也可能要使用即用即弃的对称密钥。但是,对称密钥不是被扔掉了吗?没有对称密钥,怎么解密加密数据呢?我知道你可能会有这样的疑问。
其实,思路很简单,我们在解密的时候,再生成一个完全相同的对称密钥就行了。这种情况下,对称密钥可以使用用户持有的、不需要存储的秘密,比如口令,在需要对称密钥的时候,即时地把它推导出来。
即用即弃的对称密钥,是我们推荐使用的对称密钥。下一节,我们再来聊推荐原因。
### 需要留存的对称密钥
和即用即弃的对称密钥相对的,就是用完了不能丢弃的对称密钥,也就是需要留存的对称密钥。
**需要留存的对称密钥,大部分出现在用户无法参与的计算环境里**,比如自动启动的服务器。因为,在用户能够参与的计算环境里,不应该使用需要留存的对称密钥。取而代之的,应该是由用户持有的、不需要存储的秘密推导出来的即用即弃的对称密钥。
要知道,对称密钥需要保密。毫无疑问地,留存的对称密钥需要得到额外的照顾,避免对称密钥的泄漏。比如说,保存对称密钥的文件,它的权限需要设置成只有它的拥有者才能阅读、修改。在高度保密的环境下,甚至,我们需要把对称密钥保存到专用的芯片里。
需要注意的是,在我们设计的软件架构里,应该尽量避免使用需要留存的对称密钥。无论对称密钥是保留在专用的芯片里,还是保密的文件里,随着时间的推移,留存的密钥都有泄漏的风险。
另外,我们前面已经讨论过,既然对称密钥需要保密,我们就要把对称密钥当做是超级敏感的信息来处理。这些处理方式,包括但不限于,要及时清理内存里的对称密钥,而不能依赖系统的内存回收机制;不要把对称密钥有意无意地泄漏出去,比如把对称密钥写到系统日志里。
## 对称密钥有什么麻烦?
接下来,我们来看看对称密钥会有什么麻烦?这会利于我们分析对称密钥的管理问题。
我们已经知道,所谓的对称密钥,就是加密和解密都使用相同的密钥。如果加密和解密都是同一个参与者,自己加密的数据自己解密,那么只要持有一份对称密钥就行了。
可是,如果牵涉到两个或者两个以上的参与者,那么,每一个解密的参与者就都要持有和加密的参与者相同的对称密钥,解密才能成功。这就带来了很多麻烦。
让我们来看一个例子假设一个系统有三个参与者A、B和C。
如果每两人之间的通信都使用不同的密钥那么A和B、B和C、A和B之间都需要不同的对称密钥。也就是说三个参与者的系统需要3对不同的对称密钥。类似地我们可以计算出
- 5个参与者的系统需要10对不同的对称密钥
- 10个参与者的系统需要45对不同的对称密钥
- 100个参与者的系统需要4950对不同的对称密钥。
随着参与者的增加,需要的对称密钥数量急剧地膨胀。这种膨胀的速度,就给密钥的管理带来了很多麻烦,也会使得系统的效率急剧地下降。显然,大量的这种一对一的密钥的设计,不适合有众多参与者的应用。
但是,如果无论参与者有多少,都使用一个相同的对称密钥呢?如果这些参与者之间,是可以信任的,这样做的问题不大。比如说,一个公司内部的远程视频会议,就可以使用同一个对称密钥加密视频数据,然后把加密后的视频数据分发给每一个参会者。
这样,每一份视频数据,就只需要一份加密,每一个参会的都能够解密,看到会议的内容。那要是每一个参会者之间都使用不同的对称密钥呢?那也就意味着,视频的发送端,需要给每一个参会者都发送不同的加密数据。
一个有100个人参与的视频会议每一份视频都需要有99份的加密。你可以想象一下和只使用一份加密数据的方案相比这么大的计算量会给这个视频会议系统带来多大的性能麻烦。
而且,也不是每一个场景里,它的参与者都是可以信任的。比如说,我们可以想一想即时通信系统里记录的联系人。这些人,有时候也被叫做朋友圈。可是,虽然叫做朋友圈,圈子里的不一定都是熟人,更不一定都是见过面的人。
当然,朋友圈有我们可以信任的朋友,也有我们不能信任的陌生人。如果我们和不同的联系人通信,都使用相同的对称密钥加密通信数据,那我们不信任的陌生人,就也知道了这个对称密钥。
他们就都能解密我们和每一个联系人的通信数据。即使我们可以信任的朋友,也不意味着他们之间就不能有两两之间的秘密。**所以,每个人之间都使用相同的对称密钥是不行的。**
如果每两个人之间的通信数据都使用不同的、只有这两人才知道对称密钥加密也就是所谓的端到端的加密技术了。想一想如果有2000个联系人使用端到端的加密也就意味着需要2000个对称密钥。
这是不是意味着需要保存2000个对称密钥呢通过前面的讨论我想你已经有了答案。
在即时通信的场景里,还有一种用户不会喜欢,但是厂商会喜欢的加密方式。那就是每一个用户都把数据加密传递到通信的服务器,然后再由服务器分发给数据的接收方。
这种方式最大的优点,就是服务器知道用户发送的数据明文。服务器知道了数据的明文,就可以做很多事情了。有些事情,我们也许会喜欢;有些事情,我们可能不会喜欢。这种方式还有一个不太重要的优点,就是每一个用户只需要一个用于和服务器通信的对称密钥就够了。这无疑降低了系统设计的复杂度。
但是我们不会喜欢服务器窥视我们的隐私因为机器的背后站着不可预测的人。我们也不希望保存2000个对称密钥毕竟密钥的管理不是一件轻松的事情。有没有办法我们可以和成千上万的人通信每一个通信都使用不同的对称密钥但是又不需要在本地保存这些对称密钥呢
当然是有答案的。Kerberos就是一个仅仅使用对称密钥系统就可以支撑这种通信方式的协议。更直观的方法就是使用基于非对称密钥的密钥交换技术。
Kerberos协议的使用场景目前还在逐渐萎缩。除了单点登陆的系统之外至少在我的认知范围内使用Kerberos协议的新系统已经不太常见了。不过Kerberos协议是一个设计优雅的协议。在不使用非对称密钥技术的情况下它依然可以做到支持大规模用户的端到端加密这是一个很了不起的成就。
**对称密钥的规模化是使用对称密钥的一个大麻烦**,这也给对称密钥的管理带来了很多挑战,不过也催生了很多成熟的解决方案。下一次,我们讨论对称密钥的另外一个麻烦,尤其是量子计算时代来临的时候,这个麻烦可能更要命。
## Take Away今日收获
今天,我们讨论了生成对称密钥的时机,介绍了两种不同生存周期的对称密钥,也就是,即用即弃的对称密钥和需要留存的对称密钥。即用即弃的对称密钥是我们推荐使用的方式。如果对称密钥需要留存,一定要把它当做超级敏感的信息来处理。
另外,我们还讨论了对称密钥在规模化通信场景下的麻烦。使用场景不同,解决这些麻烦的办法也是不同的。更通用的解决方案,需要了解更高级的协议,或者非对称密钥系统。如果还有机会,我们以后再来讨论这些解决方案。
通过今天的讨论,我们要:
- 有意识优先使用即用即弃的对称密钥;
- 有意识去保护好需要留存的对称密钥;
- 知道对称密钥在规模化通信场景下的麻烦,能够有意识地去寻找、学习相应的解决方案。
## 思考题
好的,又到了留思考题的时间了。
这一次的思考题我们再加大一点难度留一个延伸阅读题。我们前面说过Kerberos协议是一个设计优雅的协议能够用来解决对称密钥在规模化通信场景下的问题。
今天的思考题就是去阅读Kerberos协议去了解这个协议是怎么工作的是怎么解决对称密钥的规模化通信问题的。如果让你使用Kerberos协议去设计一个即时通信软件的数据加密框架你觉得会有哪些优点会有哪些缺点
欢迎在留言区留言,分享你的阅读体验和见解。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,123 @@
<audio id="audio" title="19 | 量子时代,你准备好了吗?" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/a2/54/a2403905eea3yy183bc17075235aea54.mp3"></audio>
你好,我是范学雷。
上一讲,我们讨论了怎么管理对称密钥,强调了要优先使用即用即弃的对称密钥。我们还一起分析了对称密钥的一些问题,相信你也感受到了,其规模化背后的麻烦。
不过,我们又留了一个小尾巴,就是对称密钥能不能应对未来量子计算时代的计算能力?
也就是说,现在使用对称密钥加密的数据,在量子计算时代能不能被破解?我们又该怎么保护我们的敏感数据,不受量子计算时代的算力影响?这是我们这一次要讨论的话题。
## 量子算力有多强?
要讨论量子计算的影响,我们首先要有一个概念,那就是量子算力有多强。
2020年12月4日中国量子计算原型机“九章”问世。理论上这个计算机比目前最快的计算机还要快一百万亿倍。也比2019年谷歌发布的量子计算原型机“悬铃木”快一百亿倍。
**你看看,现在量子计算的性能提升,是百亿倍这个数量级的。**
还记得我们总提到的安全强度吗那时候我们有一个粗略的判断使用10亿台1纳秒计算一次的计算机破解128位的安全强度需要一千万个十亿年。还有同学留言估算使用目前最快的计算机Fugaku破解128位的安全强度需要2500个十亿年。
而“九章”比目前最快的计算机还要快一百万亿倍按照这个计算能力如果我们对照Fugaku计算机破解128位的安全强度只需要2.5%的年也就是大约9天。
量子计算机的研发还在原型阶段成熟的量子计算机性能可能还会有大幅度的提高。到了量子计算机成熟的时候破解128位的安全强度可能只是微秒或者纳秒级别的计算。
所以量子计算时代128位的安全强度也就不再安全了。
我们再回顾一下第3讲欧洲的ECRYPT-CSA的密码安全强度建议。ECRYPT-CSA建议128位的安全强度的密码学算法可用于2028年之前2028年之后就要使用256位的安全强度了。这个建议就是考虑到了量子计算的影响。
2028年离现在已经不是很远了。
## 那256位的算法安全吗
是不是说256位安全强度的密码学算法在量子计算时代都是安全的
想要得出答案我们还可以使用“九章”量子计算原型机的数据来估算一下破解256位安全强度的密码学算法需要多长时间。为了简化计算现在我们假设量子计算机一纳秒就可以破解128位的安全强度我们有10亿台这样的量子计算机。
借用我们前面对128位安全强度算法的估算数据破解256位的安全强度要一千万个十亿年。
所以我们可以有这样一个印象在量子计算时代256位的安全强度大致相当于今天128位的安全强度。今天128位的安全强度是足够安全的。**在量子计算时代256位的安全强度也是足够安全的。**
我相信你应该已经有这样的意识了攻击者不会只按照算法设计者设计的路线攻击他们有的是千奇百怪、出乎意料的办法。那么对于256位的安全强度有没有密钥算法承受不了量子计算时代的算力的这个答案是肯定的。
尤其让人遗憾的是,所有现在流行的非对称密码算法,都不能抵御量子计算时代的算力。
不过这和现在的非对称密码算法的设计思路有关系业界现在也在紧锣密鼓地设计、遴选量子计算时代非对称密码算法。让人欣慰的是256位安全强度的对称密钥算法和单向散列函数包括AES算法在量子计算时代还是安全的。
非对称密码算法不能抵御量子计算时代的算力,那么由非对称密码推导出来的对称密钥,是不是也不能抵御量子计算时代的算力?回答这个问题之前,我们先来了解一个概念:前向保密性。
## 什么是前向保密性?
什么是前向保密性呢?
在密码学里,前向保密性指的是即使用来协商或者保护数据加密密钥的长期秘密泄漏,也不会导致数据加密密钥的泄漏。换个角度看,虽然数据加密密钥是由长期的秘密衍生出来的或者保护的,但是数据加密密钥不能再一次通过长期秘密推导出来。
是不是感觉有点绕口,不知道该怎么理解?
我们来看一看前面讨论过的,从用户口令推导出数据加密对称密钥的算法。每次需要使用这个对称密钥的时候,都可以通过用户口令再次推导出来,而不需要把这个对称密钥保存到硬盘上。
这个过程就不是前向保密的。如果用户口令泄漏了,这个用来加密数据的对称密钥能够再次被推导出来,对应的加密数据也能够被破解。
数据加密密钥可以从长期秘密衍生出来,但是又不能再一次推导出来,这听起来有点别扭。
关键就在于使用用完就丢、而且用完就不能再找回的秘密,比如说随机数。如果随机数也参与数据加密密钥的衍生,只要这个衍生算法是安全的,那这个数据加密密钥也就具有了随机性。
只要生成这个数据加密密钥的随机数被干干净净地丢弃了,别人就不太可能重新找回这个随机数,也就不能再次推导出同样的数据加密密钥了。
那单纯地使用随机数生成的数据加密密钥,具不具备前向保密性呢?比如说,如果我们就使用随机数生成的对称密钥,然后保存到读写权限受到限制的保密文件里。每次加密或者解密需要使用对称密钥的时候,我们就把它从文件里读取出来。那么,这样的密钥能不能前向保密呢?
能反复长时间使用的密钥,就是人们常说的静态密钥,也是我们前面提到过的需要留存的密钥。上面说的,就是一个静态密钥的例子。**静态密钥不能前向保密**,因为,静态的密钥一旦泄漏,数据保密性也就无从谈起了。
我们把需要留存的密钥排除出去后,那具备前向保密性的密钥一定只能是即用即弃的密钥了。而且,这个密钥的衍生,一定要有即用即弃的随机数的参与。
这样,我们就找到了具备前向保密性的对称密钥的两个特点:
- 密钥的产生需要有即用即弃的随机数的参与;
- 密钥的本身是即用即弃的密钥,而不能是静态的密钥。
具备了这两个特点以后,即使协商或者保护数据加密密钥的长期秘密泄漏,也不会导致数据加密密钥的泄漏。这就大大降低了长期秘密泄漏带来的数据泄密的风险。
这就是前向保密性要解决的问题。好了,我们讨论了前向保密性。接下来的问题是,前向保密性和抵御量子计算时代的算力,有什么关系呢?
## 怎么使用前向保密性?
要想现在就开始筹划抵御量子计算时代的算力的事情,有两件事是一定要考虑的。
**第一件事,就是使用量子时代依旧安全的密码学算法**。我们要使用256位安全强度的对称密钥算法和单向散列函数这样的密码学算法来确保量子计算时代的算力无法蛮力地破解加密数据。
**第二件事,就是使用具有前向保密性的对称密钥来保护数据,特别是密文能够泄漏的数据**,来确保量子计算时代的算力无法获得加密使用的密钥。
我们提到,现在流行的非对称密码算法,不能抵御量子计算时代的算力。那基于非对称密码的密钥交换或者密钥协商算法,当然也不能抵御量子计算时代的算力。那么,是不是基于非对称密码的密钥交换或者密钥协商算法衍生出来的对称密钥,也不能抵御量子计算时代的算力呢?
**这要看对称密钥衍生算法的细节**。如果对称密钥的衍生算法里,有即用即弃的随机数的参与,或者用来衍生对称密钥的非对称密钥也是即用即弃的,而且这个对称密钥还是即用即弃的密钥,那使用它加密的数据,也能够抵御量子计算时代的算力。
具体的算法细节,依赖于我们对非对称密码算法的理解,我们以后有机会再聊。
到目前为止,这个专栏就接近尾声了。我们从专栏之初就开始操心的牛郎织女约会问题,下一讲来看一看,我们能不能通过在这个专栏里讨论的算法,来解决这个约会难题。
## Take Away今日收获
今天我们讨论了量子计算时代的算力相信你对量子计算时代的算力有了一个大致的印象。然后我们还提到了256位安全强度的对称密钥算法和单向散列函数包括AES算法在量子计算时代还是安全的而非对称密码算法不能抵御量子计算时代的算力。
量子计算时代的非对称密码算法目前还在遴选中,而主流的密钥交换算法一般都是建立在非对称密码技术之上的。在后量子时代的非对称密码算法普及之前,我们有没有办法抵御量子计算时代的算力呢?**这个问题的答案,就是要使用具备前向保密性的对称密钥**。
当然,前向保密性不仅仅是用来应对量子计算时代的算力的。它也是我们现在协议设计和实现里,实现深度防御的一个重要考虑因素。不具备前向保密性的对称密钥,不能使用在加密数据有可能外泄的场景里,这几乎会断送加密的意义。
**所以,对于现在的安全协议来说,要求具备前向保密性是一个硬指标。**
通过今天的讨论,我们要:
- 了解量子计算时代的算力;
- 知道256位安全强度的对称密钥算法和单向散列函数在量子计算时代还是安全的
- 知道现有的非对称密码算法,不能抵御量子计算时代的算力;
- 了解前向保密性要解决的问题和它的特点。
## 思考题
今天的思考题,是一个动手题。
在你正在开发的项目中,或者你关注的开放源代码项目中,试着搜索一下其中的对称密钥是如何产生的、有没有留存、具不具备前向保密性?对称密钥的强度能不能应对量子计算时代的算力?你有什么改进的建议?
这是一个能够帮助你提前评估量子计算影响的好办法,也能够帮助你为量子时代的计算能力做好准备。欢迎在留言区留言,记录、讨论你的发现和建议。
好的,今天就这样,我们下次再聊。

View File

@@ -0,0 +1,45 @@
<audio id="audio" title="加餐 | 密码学,心底的冷暖" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/7a/34/7a0c83e629ff328f18cb3a518aa29434.mp3"></audio>
你好,我是范学雷。
今天是我们的一次加餐,我们不讲知识,我要跟你分享一点关于密码学的小感慨。
2011年的时候因为要参与解决BEAST安全漏洞的国际合作我得以和一些顶尖的密码学家一块儿工作。当时幸运的是我提议的解决方案被广泛采纳成为了事实上的标准给TLS 1.0续了十年的命。
不幸的是,十多年来,我对密码学一直都很崇拜,但是,它的很完美的印象在我的心底被彻底击碎了,这也彻底改变了我对密码学应用市场的认识。
为了更好地帮助我找到解决方案,当时的一位密码学家分享给我一些他的调查数据。这些数据研究了全球最知名的几十万家公司的公开网站,帮助他们发现了很多和密码学相关的安全问题。
问题很严重,国内公司的问题尤其严重。按理说,大公司有钱有人有技术,能够养得起、请得动密码学专业领域的工程师,信息系统应该没有突出的密码学问题。
很遗憾的是2011年的数据表明那时候的国内大公司虽然有钱有人有技术却依然霸占了问题最突出、威胁最严峻的榜单前列。
## 现实和理想
这是我第一次被密码学现实和理想之间的差距震撼到了。因为这些问题,其实都不是什么技术门槛的问题。技术都是公开的,也都是随手可取的。大部分的安全问题,都来源于这些应用的设计者和实现者没有意识到这些麻烦的存在。
他们没有意识到问题的存在,当然也就不可能解决掉这些问题。在密码学业者眼里的常识,也许是普通软件工程师意识之外的存在。更严重的现实是,由于意识不到这些安全问题,这个公司当然也就不知道这些安全漏洞。
什么时候,他们能知道这些问题呢?要么有善意的研究者告诉他们,要么有恶意的攻击者警告他们。更多的时候,恶意的攻击者已经击破了系统,盗取了数据,也没有留下痕迹,当然也没有敲响警钟。这些被盗取的数据,随时都会是威力巨大的暗雷,不知道什么时候就会引爆!
从BEAST安全漏洞开始随后的几年里我的工作时间几乎就被不断颠覆的密码算法霸占了。三五年的时间里几乎所有的主流密码学算法都爆出了或多或少的漏洞。昨天还占据主流地位的算法今天就被宣布有破解办法明天就要被扫进历史的垃圾箱了。
大家都忙着给算法打补丁,找替代品,更新产品。这种连环式的暴雷,过了五六年才算消停。 但是能够消停也是因为业界几乎把2010年之前主流的密码学算法都换了一个遍。
## 隐忧依然存在
2020年一切似乎算是可以喘口气了。可是隐忧依然存在。
第一个隐忧就是大家都知道老算法有问题那使用新算法了吗答案是令人难堪的并没有。比如2020年Zoom就被研究者披露使用了二三十年前就已经不安全的加密算法。
第二个隐忧是,如果使用新算法,数据就安全了吗? 答案还是令人难堪的:也不一定安全。如果有心者记录了历史数据,如果加密历史数据的算法有一天被破解,历史数据还是有可能被破解。历史数据里有价值的信息,比如用户名和密码,再比如知名人士的行程。尴尬的是,有人认为,不仅存在这样的有心者,而且还有钱有权有势。
第三个隐忧是,如果是新系统、新算法,数据就安全了吗?答案稍微让人欣慰:还有一点风险。这点需要防范的风险就是,如果新算法未来被破解,加密数据能不能被解密? 我们要确保即使未来算法被破解,特别是量子时代到来后,数据也没有办法解密。要做到这一点,还是需要有点密码学领域的专业知识积累的。
看看这些隐忧,我心里只有凉凉两个字。因为,大部分的软件工程师还没有掌握密码学的基础知识,当然也不会担心这些问题。不担心这些问题,当然就更不会有人去想解决问题的方法。
不过我还是听说有很多学校在大学一二年级开设了类似于“密码学101”这样的基础课即便学生的专业并不是计算机。但令人欣慰的是即使不是软件工程师像密码学101这样的基础课程也可以帮助我们更好地保护自己的隐私保护自己。
**十年后,也许有一个新模样。**
极客时间的《实用密码学》这个专栏,就是我为十年后的新模样,添的一块砖。