系列文章最后一篇将为大家介绍当今计算机和网络的安全基石之一: 公钥加密体系 。不过这部分内容无法像前几期那样通俗易懂,需要读者具备一些基础知识。公钥加密体系在计算机和网络体系的各个方面都在发挥作用:每一次在游戏机上运行游戏、每一次网购、每一次移动支付、甚至每一次电脑开机,以及此时此刻通过机核APP或者网页阅读这篇文章,公钥加密体系都参与其中,实现身份验证和数据加解密,保障信息安全。
公钥加密算法和公钥基础设施是现代密码学的综合应用,因此有必要先对当今的密码学进行讲解。
古典密码指的是在现代密码学之前普遍使用的一种加密方式,其形式往往以转盘、字母表、密码本等形式出现。但归根结底,其加密实现的底层逻辑只有两种: 替换 和 变换 。
替换 指的是将明文的每个字符按照约定的方式替换成另一个字符。这种替换可以是在原文语言字符范围内(如凯撒密码),也可以替换成完全不同的另一套符号(共济会密码); 变换 指的是将明文内容按照事先约好的规则”埋进“大量干扰信息里,只有按照正确的方式才能将明文从中提取出来。上文提到的棱柱密码就是典型的变换加密,只有将包含明文的皮革条以正确的方式缠绕到正确的棱柱上,明文内容才会显露出来。此外,古典密码也会将替换和变换结合使用,就比如四方密码。
虽然古典密码因为其孱弱的安全性,早就没人将其用于真正的加密用途,但反而因其容易破译,在游戏中经常以虚构文字的形式出现,作为异世界的世界观的一部分,用来表现一些次要文本或者彩蛋。这样可以在不破坏整体观感的前提下,给核心粉丝留下一些值得挖掘的内容。机核就有不少相关的内容沉淀,感兴趣的朋友可自行阅读,这里就不再举例了。
古典密码的特点在于其安全性很大程度上依赖于加密的方式方法,一旦加密方法被知晓,明文基本上就已经呼之欲出了。而现代密码学的特点在于加密的方法是完全公开的,其破解的困难性经过数学证明,只取决于密钥长度。
对称加密指的是加密和解密使用相同密钥(或者密钥的简单变换)的加密算法。常用的加密算法有:
数据加密标准(Data Encryption Standard,DES,1975年公布,1977年成为标准,2018年弃用)
三重DES(Triple DES/TDES,1981年公布,2019年弃用)
RC5(1994年公布)
国际数据加密算法(International Data Encryption Algorithm,IDEA,1990年申请专利)
高级加密标准(Advanced Encryption Standard,AES,原名为Rijndael,1997年公布,2001年成为标准: FIPS 197)
国密SM4加密算法(2012年发布,2016年成为标准:GB/T 32907-2016)
大部分对称加密算法加密和解密时使用完全相同的密钥,但加密和解密的程序略有不同。其中比较特殊的是国密SM4,它的加密密钥和解密密钥互为字节倒序,但加密程序和解密程序是完全相同的,固化成硬件芯片时较其他对称加密算法成本更低。2011年上大学的时候,我的密码学专业课作业就是根据算法文档使用C语言实现SM4加密算法(当时还没成为国标,名字叫SMS4)。对称加密算法通常会涉及到多轮次重复进行的二进制位移、变换和异或(XOR)等操作,非常复杂。而且不同的对称加密算法的机制也略有不同,节约篇幅起见,本文就不过度深入了。
终于,我们来到了现代密码学最为重要的部分: 非对称加密算法 。非对称加密算法会生成两个密钥,这两个密钥有这样的性质:
两个密钥都可以用于加密或者解密;
使用其中一个加密时,只能使用另一个解密;
其中一个密钥可用于生成另一个密钥,但反之不行,反向生成密钥的计算难度等同于破解加密数据本身。
我们将这两个密钥中具备生成能力的称为“私钥”,由它生成的则称为“公钥”。公钥加密算法最早由美国计算机学家Whitfield Diffie和Martin Hellman于1976年公开发表,它的主要用途是在不可靠信道上安全地交换通信密钥。算法名称取两人姓氏首字母,名为Diffie-Hellman算法,也叫 D-H密钥交换协议 。DH加密算法的安全性来自离散对数的难解性。
一年后的1977年,美国麻省理工大学的三名计算机科学家 Ron Rivest、Adi Shamir 和 Leonard Adleman 设计了一种划时代的加密算法。这种加密算法的安全性基于对大素数乘积结果进行因式分解的困难性,即已知两个大素数 p 和 q 的乘积 n ,在仅知道 n 的情况下将其因式分解成 p 和 q 是困难的,但计算 p 和 q 的乘积很容易。这个算法的名字取三位发明人姓氏的首字母,叫做 RSA加密算法 。
(我知道肯定有好多人想进一步了解RSA加密算法的原理,这个算法不仅涉及大素数乘积结果因式分解的困难性,还会涉及离散对数、欧拉函数和费马小定理等数论知识。我个人虽然很感兴趣但并不擅长讲解,除了将算法原理照搬过来没有更好的讲解手段。各位如果对这方面有兴趣还请自行搜索相关的介绍和科普,见谅。)
在RSA算法之后,又有一种新的非对称加密算法被发明出来,这就是现在已经被广泛使用的椭圆曲线密码学(Elliptic-curve cryptography,ECC)。相对于RSA,ECC的优点在于可以用更少位数的密钥提供更强的安全性,我国的国密标准里的SM2公钥加密算法就是基于椭圆曲线密码学设计的。ECC的特性和涉及的数学原理更为抽象,推荐大家直接上B站搜搜讲解视频,我自认没有能力把这玩意讲好,就不浪费大家时间了。
哈希算法不是加密算法!
哈希算法不是加密算法!!
哈希算法不是加密算法!!!
好了,我们继续。所谓“哈希”(hash)算法(也叫杂凑算法),指的是拥有以下特性的一类函数:
输出长度固定 :对于同一个哈希函数,无论输入的数据长度如何(输入甚至可以为空,零长度),输出都会是一段长度固定的数据;
单向性: 无法通过计算仅根据哈希的值来还原输入数据;
唯一性: 对于相同的输入数据,哈希函数都会给出相同的结果;但是——
扩散性: 如果输入数据稍有变化(比如只改变一段很长的数据中的一个字节甚至一个比特),哈希的结果就会产生变化;并且这种变化——
均匀分布: 在统计上,由于输入数据变化造成的输出变化是均匀分布且无规律的。
其中第二条单向性就表明哈希函数不是加密算法,因为无法通过计算从哈希的值还原输入数据。我们常说的“ MD5 ”就是一种典型的哈希函数,名字“MD5”指的是“ 消息摘要算法-5号 ”(Message-Digest Algorithm 5) 。它可以将任意输入数据转化成128位(16字节)的哈希值。比如以ASCII编码的字符串" hello, world! "的MD5值为 3adbbad1791fbae3ec908894c4963870 。
哈希算法在现代计算机和网络中有很多用途,比如发现重复文件等,很多Web服务端程序会将用户上传的文件使用文件内容的哈希值作为文件名保存,这样就可以避免重复存储完全相同的文件,因为完全相同的文件的哈希值都是相同的。常见的哈希算法有大家耳熟能详的MD5、SHA-1、SHA-256、SHA-384、SHA-512等。在我国的国密标准里也有一个哈希算法,名为SM3。
哈希算法在实践中最常用的领域是” 数字签名 “,在下文会详细讲解。
很显然,古典密码很容易被统计攻击破解:如果明文的语言是已知的(比如明确知道明文是英语),那就可以针对这种语言的字母出现频率、单词特点和语言结构进行推测,用很短的时间完成破解。这里我打算直接引用伽莫夫所著的经典科普作品 《从一到无穷大——科学中的事实和臆测》 中讲解统计学部分的篇章:
这让我想起十几年前和朋友一起玩《Fez》的情景。玩过这个游戏的朋友都知道,游戏内有一套虚构文字,以碑文的形式出现在世界各处,主角Gomez回到过去的村庄时会发现这里的村民说的就是这套语言。
当时第一次玩到这里的时候,我们俩都很想弄清楚这些文字到底说的是什么。我想到小时候读过的《从一到无穷大——科学中的事实和臆测》里破译海盗手稿的方式,打算用相同的方式破译。因为样本量较少,我面临和破解基德船长手稿时相同的问题,直接用字母统计频次会有很大误差,所以当时用的是单词结构和词频统计结合英语语法结构的方式破译的:
单字母单词:只有 a 和 i
双字母单词:an, as, at, be, do, if, of, in, on, me, my, go...
三字母单词:the, for, all, and, are, let, you, can, off, any...
四字母单词:what, they, them, like, your, this, that, over...
通过这些常见单词,配合环境内容和上下文,我们两个人愣是用游戏中遇到的碑文和村民对话把整个字母表还原出来了。当然了,二周目的时候找到游戏里的“罗塞塔石碑”的时候我俩才恍然大悟:就说这游戏的解谜不可能那么硬核,让人手算破译加密语言嘛!
统计攻击能够成立的根本原因是简单的替换式加密无法抹除语言结构、语法规则和字母频次等信息,这也是古典密码最致命的脆弱性。
唯密文攻击指的是攻击者在只知道密文的情况下对加密方法进行破解的攻击,这也是所有破解方式中难度最高的一种。大部分情况下的破解都不是严格意义上的唯密文攻击,毕竟这样完全无法对破解结果进行验证(计算熵值倒是一种办法)。穷举法(暴力破解法)是最常见的唯密文攻击的方式。
已知明文攻击指的是攻击者知道若干密文及其对应的明文,但并未掌握加密机制。已知明文攻击更接近大众认知里的“破解加密”,即攻击者预先知道了部分明文内容并将其用作验证手段。举例来说,我们知道Windows PE文件(Portable Executable,可移植的可执行文件,也就是常见的.exe、.dll和.ocx等文件)有确切的格式定义,它会固定以“ MZ ”两个ASCII字符开头,还包括一个含有固定内容的可读字符串“ This program cannot be run in DOS mode. ”的DOS文件头。如果要解密一个包含.exe文件的加密压缩包,那只要在解密的结果里找到了这个字符串,就表示解密成功了。对于其他文件也可以用类似的办法。
2014年上映的电影《模仿游戏》讲述了英国情报机构破译德军密码机“恩尼格玛”(Engima)的故事。影片中的一个情节是这样的:虽然英国已经制作出了用于破译密文的计算机“克里斯托弗”,但由于恩尼格玛的加密变换仍然有非常多的可能性,他们还是无法在一天之内完成对当日截获的密电的破译。打破这一瓶颈的关键是想到德军在电文中会固定在开头写上“Mein Führer.”(我的元首),这成为了提升破译效率的关键:只要在解密后的密文中找到这个开头,那这个密文一定是正确的,使用这种办法大大提升了破译效率。这种破解方法就是已知明文攻击。
选择明文指的是攻击者掌握加密机制,可以构造任意的明文,通过加密机制获得对应的密文。选择明文攻击通常用于破译公钥加密体系:在现代加密体系中,所有的加密算法都是公开的,而且公钥加密体系中的公钥是已知的,因此选择明文攻击可以构造任意明文使用已知的加密算法和公钥进行加密,直到发现加密后和已知密文相同的明文。
选择明文攻击的经典例子同样来自二战。日军在珊瑚海战后即将中途岛确定为下一个攻击目标,美国海军情报局由于破译了日军部分密码,了解到了这一计划,但是具体攻击目标未能成功破译(日军密电中代称为“AF”),综合其他因素推断也不能明确“AF”指代的确切含义。一部分人认为是中途岛,另一部分人则认为是阿留申群岛。后来美军想到一个绝妙的主意:他们通过无线电向珍珠港报告,说中途岛上的海水净化装置故障导致岛上缺水,不久从日军截获的密电中即出现了“AF”缺水的内容,从而明确了日军目标即中途岛。
选择密文攻击指的是攻击者掌握解密机制,可以对任意已知的密文进行解密,从而获得加密机制内部的细节(如密钥)。乍一听感觉似乎是完全没有必要的攻击:都可以解密任意的密文了,那还需要攻击吗?直接解密不就得了?选择密文攻击确实是最有利于攻击者的情况,但它的目的已经不再是解密密文,而是获得私钥。例如通过已知的解密用的公钥和数字签名结果逆向推算签名用的私钥。
前面已经说过,哈希算法是单向不可逆的,即无法仅通过已知的哈希值计算之前的值是什么(在密码学里叫“原像”,仅通过哈希值计算原像的攻击方式就叫“原像攻击”)。在实际应用中,用户登录密码等敏感认证数据通常就是以哈希值而非明文存储在数据库中,需要认证的时候比对用户提交的认证信息的哈希值和数据库中的值即可,服务端就能在不掌握密码的明文的情况下完成身份验证。
在密码学里,这种做法叫做“ 零知识证明 ”(Zero-knowledge proof),指的是在不透露知识任何具体细节前提下,证明自己掌握该知识。哈希算法的单向性和唯一性在这个过程中起到了“去知识化”的作用,即除非攻击者刚好知道某个具体的密码明文(即掌握知识本身)及其对应的哈希值,否则无法仅通过计算将从哈希值还原密码明文。这样即便网站遭到渗透,数据库被窃取,黑客也只能获得密码的哈希值,无法得知明文是什么……
真的没有办法知道哈希值对应的原像是什么了么?办法当然是有的,这就是 彩虹表 (rainbow table)。彩虹表是海量的数据及其经过哈希算法计算后的哈希值构成的数据库。如果想要知道某个哈希值的原像是什么,只需要在数据库中查询这个哈希值对应的原像即可。彩虹表里收录的原像大多是常用密码、用户名或者邮箱,这也就是为何即便目前互联网服务不再以明文存储密码,弱密码仍然十分危险的原因之一,因为这些密码的哈希值常年占据各大彩虹表的前几名,攻击者很容易查到它对应的密码是什么。
为了抵抗彩虹表攻击,目前普遍的做法是“ 加盐 ”(Salt):在明文密码的前面或者后边加上一段数据(即“盐”),再计算其哈希值并写入数据库。验证的时候对用户提交的密码执行相同的操作,再和数据库中的哈希值进行对比。“盐”值可以是公开的,也可以是秘密的,其目的就是为了让彩虹表攻击失效。因为虽然用户的密码是可能预测的,但“盐”值以及加盐的方法无法预测,也无法只根据哈希值推算“加盐”之后的值;数据库中每个用户的“盐”都可能不一样,而彩虹表的内容通常只有不加盐的密码明文的哈希值(即便如此,单论MD5这一种哈希算法,大规模的彩虹表也已经达到数个TB),不可能容纳所有密码对于每一种可能的盐值的哈希值的结果,这样就用一个较小的代价避免了彩虹表攻击。
针对哈希的攻击还有一种方式叫“长度扩展攻击”。哈希算法可以看作是一种将任意长度数据转换成定长数据的函数。所以很显然地,消息的内容有无数多可能,而哈希值的范围是有限的,因此根据 抽屉定理 , 一定 可以找到 两个不同的消息 ,它们的哈希值 完全相同 。 这种找一段和已知数据具有相同哈希值的另一段数据,就叫“次原像攻击”,也叫“碰撞攻击”。实际上哈希算法的扩散性和均匀分布特性就是为了对抗碰撞攻击的,但对于长度较短的哈希算法,目前个人能够掌握的算力就已经足够在可接受的时间内找到给定哈希值的碰撞。
次原像攻击中最常用的一种就叫 长度扩展攻击 。长度扩展攻击指的是在已知消息及其对应哈希已知的情况下,构造另一段 开头内容已经被指定 的消息,通过扩展其后缀冗余部分的长度和内容,使其和上一段消息具有相同的哈希值。
长度扩展攻击非常有指向性: 伪造签名 。 第三期文章 里我提到一件事:震网病毒盗用了两个硬件厂商签名实现免杀。而其中一个严格来说并非“盗用”——攻击者并没有拿到签名用的私钥,而是利用了签名认证使用的较弱的MD5哈希算法。不仅是震网,后来的火焰病毒也使用构造哈希碰撞的手段实施过签名伪造。 关于数字签名的内容在接下来会详细讲解,这里只提一下签名验证的核心步骤,是要对比文件内容的哈希值和一个可解密的已知的哈希值是否相同,如果相同就说明签名验证通过,即这个文件就是提交给签名者相同的文件。签名的意义在于向用户证明此文件内容已经过签名者审核。
签名验证最终对比的就是哈希值,而一个经过合法签名的文件的哈希值是已知的。如果签名使用了较弱的哈希算法(比如MD5),攻击者就可以通过寻找哈希碰撞的方式,构造一个哈希值和合法签名文件相同的但内容不同的文件,即可骗过签名验证。Windows 7及之后的Windows操作系统默认只加载有可信签名的驱动程序,没有获得有效签名的驱动无法通过正常方式加载,因此签名伪造在现在成为一个很重要的免杀手段。
现在我们集齐了 对称加密算法 、 非对称加密算法 和 哈希算法 ,那么是时候用这些东西搞点事情了。 如果对这些印象不深了,这里再复习一下:
输出长度固定:对于同一个哈希函数,无论输入的数据长度如何,输出结果的长度都是固定的;
单向性:无法通过计算仅根据哈希的值来还原输入数据;
唯一性:对于相同的输入数据,哈希函数都会给出相同的结果;
扩散性:如果输入数据稍有变化,哈希的结果就会产生变化;
均匀分布:在统计上,由于输入数据变化造成的输出变化是均匀分布且无规律的。
安全套接字层(Security Socket Layer,SSL,现在也叫传输层加密Transport Layer Security,TLS)是讲解公钥加密体系必讲的内容。这是通信双方在不安全信道上传递通信密钥的办法,我们输入网址的时候打的前缀协议名“ https:// ”中的“S”,说的就是“SSL”。在实践中,SSL实现了很多功能,比如身份验证、会话恢复、密钥复用等,但其最基础密钥交换过程很简单。我们假设通信是由客户端(Client,简称C)发起,向服务端(Server,简称S)请求连接。双方开始通信时交换密钥的过程简化之后如下:
C向S发起请求;
S将自己的公钥以明文形式发送给C;
C生成一个通信用的随机密钥,使用S的公钥将其加密,发送给S;
S收到之后使用自己的私钥将其解密,并将其用做后续通信的加密密钥,密钥交换完成。
除去公钥机制具体的实现方式区别,以上过程其实就是D-H密钥交换协议。如果在通信双方之间有一个监听者,在密钥交换阶段ta只能监听到 S的公钥 和 使用S的公钥加密的随机密钥 。而由于非对称加密算法的性质,只知道公钥等于啥也不知道,不能用它算出私钥,也就无法解密用公钥加密的通信密钥,从而保证了密钥交换的安全性。
在现实中我们在签署文件时,实际上是使用自己的笔迹和指纹等个人独有且可以验证的特征对文件进行认证,表示自己已经知晓并认可文件的全部内容。有了公钥加密机制,我们在数字领域里也可以实现相同的操作。数字签名过程分为制作签名和验证签名两部分。假设主体A要对一个文件F进行签名,流程如下:
计算文件F的哈希值,记为H;
A使用自己的私钥对H进行加密,加密后的哈希值记为S,S即为A对F进行的签名,会随文件F一同附送。
文件的接收者如果要对文件签名进行验证,流程也很简单:
接收到文件F后,计算文件F的哈希值,这里记为H
使用公开渠道获得的A的公钥对F附送的签名S进行解密,得到H'
比较H和H'是否完全相同,如果相同,即代表文件F已经过A认证
计算哈希需要读取文件F的完整内容,能够实施这种计算也表明了签名主体A对文件F内容的完全了解;
文件接收者可以使用A的公钥对S进行解密,这表明S确实是由A制作的(私钥加密对应公钥解密),而这个私钥只有A拥有;
S解密后的内容是文件F在A制作签名时哈希值,由于哈希算法的特性,如果文件F在传输过程中被篡改,签名验证时计算文件F的哈希值就和解密后的S对不上,保证了文件的完整性;
如果不只是文件F,就连签名S在传输中也被篡改,同样也无法通过签名验证。因为伪造签名的人不掌握A的私钥,使用自己的私钥加密之后接收方无法使用A的公钥进行解密。
盗取签名用的私钥,这样就可以用这个私钥对任意文件进行签名,让其他人认为这些文件都经过了A的认证;
伪造解密公钥,这个方式需要同时篡改签名S。由于接收方验证签名时需要从公开渠道获得A的公钥,如果攻击者具备蒙骗接收者的能力,能够使用自己的公钥伪装成A的公钥提供给接收者,同时还使用了自己的私钥重新制作了签名S替换掉原件,接收者就会将其认为是A签名的文件,但实际上它是攻击者签名的;
找到文件F的哈希碰撞,由于签名加密的是文件F的哈希值而不是整个文件,因此只要构造另一个和F具有相同哈希值的文件,在签名验证过程中即会被认为和原文件相同。
数字签名技术在当今计算机和网络中发挥至关重要的作用。离我们玩家最近的可能就是游戏机实现封闭生态的方式。我们都知道没有破解的游戏机只能运行正版游戏,这是因为正版游戏和应用里附带了游戏机厂商和发行商的数字签名,而游戏机里预装了这些厂商的数字签名公钥,游戏机在运行应用之前会现进行签名验证,只运行那些能够通过签名验证的应用。
现在我们有了在不安全的信道上可靠传输通信密钥的方法,也有了对数据内容进行认证的方法,那是不是一切都万事大吉了呢?有没有觉得这其中好像有哪里不对劲?
以上文介绍的SSL的密钥传输过程为例,如果攻击者不只能够实施监听,还能有能力截取和篡改Client和Server之间的通信,那ta就完全有能力对Client伪装成Server,而对真正的Server伪装成Client,并且使用D-H密钥交换协议分别对C和S建立安全信道,这样ta就可以在中间解密C和S之间的所有通信,并且通信的两端都无法察觉到ta的存在。在信息安全中,这种攻击被称为 中间人攻击 (Man-In-The-Middle, MITD )。而对于签名验证场景也存在类似的问题,就比如我列出伪造签名方式中的第二种。这些问题在技术领域内已经找不到更好的解决办法了。
为了解决这些问题,基于公钥加密技术的 公钥基础设施 (Public Key Infrastructure,PKI,不是KPI)应运而生。简单来说,PKI实现了密钥和使用者的身份的绑定以及确立信任关系。PKI体系的核心有三点:
私钥持有者有义务保障私钥的安全 (记住这条,后面要考),因为私钥是证明持有者身份的唯一有效数字凭证,如果私钥被盗或者泄露,应将其注销;
在体系内使用X.509证书作为承载和交换用的数据格式,容纳公私钥以及加密算法、哈希算法和有效期等信息;
设立负责签发证书的专业化机构: 证书签发机构 (Certificate Authority,CA)。从技术角度来说,任何人都可以制作和签发证书,但只有CA签发的证书才是被广泛认可和可信任的。
CA的PKI体系的核心,专职负责签发证书,只有由可信任的CA签发的证书才是有效力而且会被广泛认可的。此外还有负责审核证书申请者身份的 注册审核机构 (Registration Authority,RA)以及提供验证证书服务的 证书验证机构 (Validation Authority,VA)。CA、RA和VA共同构成了PKI体系的信任体系,而这个体系实际上是依靠行政手段运作的——毕竟技术能做的都已经做完了,剩下的就只能交给行政手段了。
上面多次提到了一个叫“ 证书 ”(Certification)的东西,或者叫“X.509证书”。这是一种专用的文件格式,以结构化的方式存储PKI体系所需的所有信息,包括但不限于:
公钥,如果是私钥证书的话还会包含私钥
加密算法和密钥参数(比如RSA算法里的e值,通常定为65537,即2^16+1,是个质数)
证书有效时间起始和截止
证书签发者和证书使用者的信息(所属国家/州/省/城市、通用名、机构/公司名等)
证书使用者别名扩展(Subject Alternative Names,即SAN)
签发机构的数字签名
签名使用的哈希算法和加密算法
证书签署关系
证书用途(数据传输加密、身份验证、代码签名、时间戳签名等)
证书吊销列表(CRL)更新URL和在线证书状态协议(OCSP)查询URL
证书是逐级签发的,出于风险控制、勤务性和管理需要,通常情况下CA不是直接给申请者签署用于实际业务的证书,而是先签署一个签发者和使用者都是自己的根证书(Root Certification,记住在CA手里的根证书是带有私钥的),这个根证书有效期通常比较长,比如10年或者20年,它只有一个功能:继续签发次级证书。
通过上面对公钥加密机制的了解就能弄明白如何继续签发证书:把次级证书看成是一个文件,在生成的时候使用根证书的私钥对其签名即可。次级证书的有效期比根证书短,通常是五年。而这个次级证书也并非用于实际业务,而是用来给申请者签发最终用于实际业务的证书。这就构成了我们在证书查看器里看到的证书链/证书层次结构。
目前主流的证书按验证等级分为三个级别,分别是 域名验证 (Domain Validated,DV), 企业团体验证 (Organization Validation,OV)和 扩展验证 (Extended Validation,EV):
DV证书是身份验证最少也是最容易获取的SSL证书,这类证书主要用在中小型网站、个人网站,自媒体以及不包含个人敏感数据的网站(比如我的博客站 武具视觉 ); OV证书需要验证企业身份信息后签发。OV SSL证书是当前最常见的证书类型,适用于行政、企业、科研、邮箱、论坛等各类大中型网站( 机核 用的就是OV证书)。 EV证书安全级别最高,验证审核最严格。EV SSL证书一般应用于金融、银行、电商等安全需求较高的网站(常见的银行网站使用EV证书,比如 招商银行 )。 然后讲讲这套机制是怎么实现信任的。在我们的电脑和手机里都有一个本地存储的根证书库,里面是所有受信任的公钥根证书。当我们需要验证一个证书是否可以被信任时,需要沿着它的证书链向上逐级验证。由于中间每一级证书都包含公钥,所以可以对其签发的证书进行签名验证。如果这个逐级验证最终可以回溯到存储在本地根证书库中的某个证书,我们就认为这个证书是可信的。
举例来说,现有ABC三个证书,A作为根证书签发了中间证书B,中间证书B签发了最终用于SSL的证书C。当拿到证书C的时候要验证它是否可信任,先用证书B的公钥解密它在证书C中的签名并进行验证,如果验证通过,再用根证书A的公钥验证它在证书B中的签名。如果所有的签名验证都通过而且证书链上的所有证书都在有效期内且没有被吊销,那么我们就可以信任证书C。
加上这套证书验证机制,上文提到的SSL传输密钥的过程就变成这样:
C向S发起请求;
S将自己的公钥证书(可以包含完整的证书链,也可以只包含叶子证书)发送给C;
C对S发送的证书进行验证,发现证书有效,可以回溯到可信的根证书 (暂且略过随机数挑战和加密套件协商过程,饶了我吧);
C生成一个通信用的随机密钥并约定通信加密算法,使用S的公钥将其加密,发送给S;
S收到之后使用自己的私钥将其解密,并将其用做后续通信的加密密钥,密钥交换完成。
在这个过程中如果再想实施中间人攻击,在第三步证书验证步骤就会被发现:由于中间人没有掌握证书链里任何一个证书的私钥,无法伪装成可信证书签发机构(即,无法以可信身份签发受信任的证书);而如果中间人用自己签发的证书替换服务端发送的证书,自签发的证书也不在客户端的可信任根证书库里,一旦实施替换就会被立刻露馅。所以也由此可见,这套信任体系能够成立的基石完全取决于证书签发机构(CA),CA应承诺妥善保管所有签发证书的私钥,在任何情况下都不会泄露,也不会主动透露给第三方。
最后说下这套机制是如何更新的。操作系统和软件自带的根证书库一般通过更新机制实现更新,如有必要也可以手动添加或者删除(比如添加内网自建CA的根证书)。而在日常查验证书的时候,除了检查证书链是否有效,还会检查这个证书是否被吊销。如果证书的持有者或证书签发机构不慎泄露了私钥,受到影响的证书就会被签发机构宣布吊销(Revoked)。查验证书的状态目前有两种方式: 证书吊销列表 (Certification Revocation List,CRL)和 在线证书状态协议 (Online Certificate Status Protocol,OCSP)。前者通过定期更新或者主动获取的方式同步到每个终端本地,后者通过发送在线请求的方式实时向证书验证机构(VA)发起状态检查请求,两种方式都有各自的优缺点,目前还没有更好的解决办法。
编写本文期间我查询了我国有数字签名和信任认证相关的法律,出乎我意料的是,我国早在2004年就发布并在2005年4月1日实施了 《中华人民共和国电子签名法》 (作为对比,《中华人民共和国网络安全法》到2016年才颁布,2017年实施)。这部法律明确了电子签名的形式、应用范围和法律效力。《电子签名法》诞生于千禧年之初国内增长强劲的电子商务需求,最新版本于2019年修订。 2010年名震一时的“火焰”病毒爆发,这个病毒通过劫持Windows Update的方式将自己安插进更新包实现传播。但我们都知道,每个Windows更新包都是经过微软签名的,终端在运行更新包之前肯定要对其进行签名验证,只有验证通过的更新包才会安装。那火焰病毒是如何通过签名验证的呢?
答案是 哈希碰撞 。2010年2月,微软使用的次级CA证书中有一个名为“Microsoft Enforced Licensing Registration Authority CA”,这个证书使用了MD5作为签名哈希算法。这给了病毒开发者以可乘之机:即便在2010年,MD5也已经是一种不安全的哈希算法。早在2004年,中国山东大学的王小云教授及其团队就发表了寻找MD5碰撞的方法(并在次年发表了寻找SHA-1碰撞的方法)。
火焰病毒的开发者利用了这一安全隐患,从微软签发过的所有证书里找到了使用MD5作为签名哈希算法的证书,以及使用该证书签名过的数据;然后构造了包含病毒但和该数据哈希值相同的更新包;再使用网络攻击手段劫持区域网络内的Windows Update请求,将其替换为包含病毒的更新包。
实话实话,能够完成以上这些操作真的十分困难,火焰病毒事件之后,微软研究人员估计,即便获得充盈的计算力的情况下,完整实施相同的攻击最短也需要12天。我们来逐项拆解:
要从海量的现存中间证书和根证书里找到使用MD5作为签名哈希算法的证书,这一步本身不难,难的是这个证书不仅能够实施代码签名或者签发新证书,还需要找到使用这个证书签过名的数据包/签发的证书,以提取其哈希值;
Windows更新并不是持续不变的,当网络上大多数主机都更新完毕之后,特定版本的更新包就几乎不会再下载了。火焰病毒的开发者需要在很短的时间内通过长度扩展攻击的方式找到有当下流行的更新包的哈希碰撞。而这个更新包如果不再使用,攻击者还需要另外寻找新的哈希碰撞。虽然在当时MD5已经不再安全,寻找哈希碰撞所需的计算量也不是说有就有的,这一步至少需要9天时间;
通过渗透运营商的方式劫持特定区域内所有的网络流量,并将Windows Update请求分离出来,替换为包含病毒的更新包。说真的相对而言这可能是所有难点中最容易实现的一个了。
火焰病毒碰撞的MD5值是 1d190facf06e133e8754e564c76c17da8f566fbb 。
私钥库是CA最核心最敏感的数据,各大证书签发公司都采取极端严密的措施来确保私钥库的绝对安全。因此绝大部分针对CA的攻击其实并非为了获取私钥,而是利用注册审核机构RA的权限实现证书滥发。历史上曾发生过一次黑客入侵CA公司导致证书滥发的案件,并导致被入侵的CA公司直接破产。
2011年,总部位于荷兰的CA公司DigiNotar遭到黑客入侵,导致入侵者使用权限为Google、Molizza、WordPress和Tor Project等实体签发了531张虚假证书,这一入侵事件在十天后才被发现。事发之后DigiNotar注销了这些证书,但这已经无济于事,谷歌、微软和Opera等公司都撤销了对DigiNotar根证书的信任。由于失去了CA公司最为核心的资产——信任,在事发之后三个月,DigiNotar宣布破产。
CA收到攻击导致私钥泄露或证书滥发会导致严重的后果,用户私钥泄露其实也同样危险。近年来由于用户私钥泄露导致了许多安全事件,这里就不一一展开:
2022年英伟达驱动签名私钥泄露导致攻击者可以使用其为恶意代码和工具签名,以绕过操作系统防病毒软件以及驱动签名验证。该签名已被用于勒索工具绕过终端防护;
2023年微星UEFI固件签名私钥泄露;
2017年大疆员工将包含私钥的代码上传至github公共代码仓库导致私钥泄露;
2020年SolarWinds后门事件。虽然该事件中私钥并未泄露,但后门模块能够成功植入并随软件分发,很大程度上是因为后门模块获得了官方的可信任签名;
2023-2024年之间,由于虚拟币钱包私钥泄露,在全球范围造成了多起虚拟币被盗案件,单起案件涉案金额可高达上千万至上亿美元。由私钥泄露造成的经济损失占了2024年第一季度全部网络安全事件案值的一半。
OpenSSL是目前应用最广泛的开源PKI工具,可完成当前PKI体系下几乎全部的计算功能,包括生成公私钥对、制作X.509证书、对称加解密和计算哈希等。在系列文章的最后,我想教大家一些实用的东西:使用OpenSSL工具使用RSA加密算法对自己的文件实现加密和解密。
大部分Linux发行版会预装OpenSSL工具,可以直接使用 openssl 命令调用。Windows系统可以用以下两种方式获得:
就像上文SSL部分说的那样,由于非对称加密算法的计算量很大,因此通常并不是直接使用它加密文件,而是先随机生成一个对称加密算法的密钥(比如常用的AES、DES或者3DES,或者国密SM4),使用它对数据进行加密,再使用非对称加密算法加密这个密钥。因此这个流程最后会得到三个密钥:公钥、私钥以及被加密的对称加密算法密钥。具体流程如下(以Shining Light版本openssl为例):
第一步:使用genrsa功能生成私钥: openssl genrsa -aes128 -out private_key.pem 1024 这一步会要求输入不少于四个字符的密码,在本例中使用” testpass “,这个密码在后续步骤中还会使用。这样我们就得到了文件名为 private_key.pem 的1024位RSA私钥。
第二步:根据刚刚生成的私钥生成公钥: openssl rsa -in private_key.pem --out public_key.pem 。这一步会用到刚刚生成私钥时输入的密码。这样我们就得到了名为 public_key.pem 的1024位RSA公钥。
第三步:生成用于实际加密文件的随机的对称加密算法密钥: openssl rand 96 > symmetric_keyfile.key 。在本例里随机密钥选为96个字节长度,算法只会用到前32个字节(256位),剩余部分是填充。
第四步:使用刚刚生成的密钥对文件进行加密,在本例中使用256位AES加密算法对文件进行加密: openssl enc -in plain_text.txt -out encrypted_data.enc -e -aes256 -pbkdf2 -kfile symmetric_keyfile.key 。于是我们就得到了名为 encrypted_data.enc 的加密后的文件。
第五步:使用RSA公钥对加密密钥进行加密: openssl pkeyutl -encrypt -inkey public_key.pem -pubin -in symmetric_keyfile.key -out encrypted_key.key.enc 。这样我们就得到了使用RSA加密的密钥文件 encrypted_key.key.enc 。
现在我们需要妥善保存 private_key.pem 和 encrypted_key.key.enc 两个文件,删掉未经过加密的密钥 symmetric_keyfile.key 和明文 plain_text.txt 。
然后是解密步骤。第一步是解密密钥: openssl pkeyutl -decrypt -inkey private_key.pem -in symmetric_keyfile.key.enc -out symmetric_keyfile.key
然后使用解密后的密钥解密密文,我们就得到了最初的明文: openssl enc -in encrypted_data.enc -out decrypted_text.txt -pbkdf2 -d -aes256 -kfile symmetric_keyfile.key
以上就是系列连载的全部内容,希望大家喜欢。本文内容涉及面较广,但限于个人水平,无法全部顾及(比如完全没有讲到早年专门用于加密电子邮件的PGP——Pretty Good Privacy加密体系),文章内容如有错误或有不明白的地方,欢迎在评论区指正交流。
评论区
共 条评论热门最新