计算机网络——网络安全②
报文完整性和数字签名
我们已经考察了密码学如何为通信提供机密性,现在让我们来关系其如何保证报文完整性(Message integrity)。
具体而言,如果Bob接到自称来自Alice的报文,那么他需要验证:
- 该报文的确来自Alice;
- 该报文没有被其他人(如Trudy)篡改。
对称加密在一定程度上可以做到这一点,对称加密手段通常足以对抗无意的报文错误,但是往往不能避免恶意的攻击。 密码学提供了更加强力的工具:密码散列函数。
密码散列函数
散列函数(Hash function,也叫哈希函数或杂凑函数)以报文$m$为输入,计算并得到一个称为散列(或称哈希、杂凑)的长度固定为$n$比特的字符串。 此前讲过的互联网检验和和CRC都满足这个定义。 密码散列函数(Cryptographic hash function)还要满足以下性质:
- 对任何随机输入报文$m$,其散列的任何一位为指定值(零或一)的概率为$2^{-n}$;
- 难以(计算上不可行)从给定的散列值计算原报文(即原像);
- 给定一个报文,难以(计算上不可行)找到另一串不同的报文,使两个报文的散列值相同。
前两条要求和“性质好”的散列函数的要求类似,即“好”的散列函数通常也满足这两条。 第三条要求则是密码散列函数独有的,如果出现两个不同的报文产生相同的散列值,则称发生了碰撞(Collision)。 一般的散列函数通常也需要避免碰撞,但通常只需要避免无恶意的碰撞,比如电信号的错误; 而密码散列函数则需要抵抗恶意构造的碰撞攻击,如果能够很容易地构造一个碰撞,那么这个散列函数就不是密码学安全的。 安全散列算法的散列值也叫做摘要(Digest)。
互联网校验和显然不满足这个定义,因为其仅执行加法,从而对报文的顺序不敏感。
比如,我们按16比特进行分块,假设一个字符占8比特并用ASCII编码,然后计算反码加法,那么aabb
和bbaa
的检验和就是相同的。
现在常用的密码散列函数包含RFC 1321中标准化的MD5算法。 其由Ron Rivest在1991年发明,用来替代更早的MD4标准。 这种算法将数据分为512比特的分块,并给出一个128比特的摘要。 尽管这种算法早在1993就被发现了碰撞(即两个不同的IV产生相同的散列值),并被发现了多种安全漏洞,其仍然被广泛使用。 这种算法虽然不足以对抗恶意的攻击,但仍可以保证数据不受无意的破坏。
此外最流行的密码散列函数为安全散列算法(Security Hash Algorithm,SHA),这族算法包括多个不同的算法,从SHA-0到SHA-3。 目前常用的为SHA-1,版本管理工具Git就使用它区分不同的提交。 SHA-1生成一个160比特的摘要。 这种算法在2017年也被发现了碰撞。
报文鉴别码
保证报文完整性的过程称为报文鉴别(Message Authentication),区别于端点鉴别。 直接为报文计算其散列值并附在报文后虽然可以避免报文本身被修改,但是完全不能避免攻击者替换报文并重新计算散列值。 为了保证报文的完整性,除了使用密码散列函数之外,还需要一个鉴别密钥(Authentication key)。 Alice和Bob共享这个鉴别密钥,就像对称加密里那样。
在发送报文时,Alice并不简单地计算报文的密码散列函数,而是计算把报文和鉴别密钥连接在一起,然后计算整体的散列值,再把这个散列值附在报文后面。 这个散列值称为报文鉴别码(Message Authenticated Code,MAC)。 Bob收到报文和散列值后,把重复以上步骤,然后检验自己计算的散列值与附在报文后的散列值是否相同即可。 此前提到的OSPF就使用报文鉴别码保证报文的完整性。
报文鉴别码虽然也要使用一个密钥,但是其并不需要使用加密算法对报文进行加密。 如果通信端不关系机密性,只需要保证完整性,那么就可以使用这种方法。
目前报文鉴别码最常用的标准为基于散列的报文鉴别码(Hash-based Message Authentication Code,HMAC)。 这种标准可以和MD5或SHA-1等散列算法一同使用。
现在我们来考虑如何分发鉴别密钥的问题。 考虑为路由器分发用于OSPF报文鉴别的鉴别密钥。 这个密钥可以使用硬件手动分发,也可以为每个路由器分配一个公钥,然后为使用这个公钥加密鉴别密钥,然后进行分发。
数字签名
如同在现实中进行签名一样,在数字领域,我们也需要一些手段证明某个文件的所有者或创作者是某人,或是表明他认可文件的内容,这可通过数字签名(Digital signature)实现。 数字签名应当和现实的签名一样可以鉴别且不可伪造,即可以证明有且仅有指定的人可以签署这个文件。
此前介绍的MAC并不足以实现数字签名,因为通信的两方都具有鉴别密钥,从而都可以计算MAC,因此不具有唯一性。 相对地,公钥密码是实现数字签名的绝佳手段,这是因为公钥密码系统的特性,即$K_B^-(K_B^+(m)) = K_B^+(K_B^-(m)) = m$。 回忆一下,在RSA中,公钥和私钥实际上是指数,而由于同余环的乘法分配性,交换它们不会导致结果的改变。 交换公钥和私钥可以得出相同的结果,这意味着发送方Bob可以使用自己的私钥计算签名的报文,然后任何人都可以使用公钥解密得到正常的报文。
这种方法能够保证唯一性和不可伪造性,因为能够解密出正确的报文说明加密过程使用了私钥,而原则上只有Bob本人具有私钥。 如果篡改了源文档,那么签名当然是无效的,因为解密出的报文和原报文不能对应。
为整个报文进行加密需要花费大量的时间,因此我们可以加密报文的散列值。 散列函数能把任何场地的报文转换成相同长度的散列值,且散列值通常远远小于报文,因此可以降低加密解密算法的时间。 因此,我们在计算签名时,不需要计算$K_B^-(m)$,而是计算$K_B^-(H(m))$即可,其中$H$是一个散列函数。 这个被加密的报文摘要就可以称为数字签名。 Bob只需要把数字签名附在报文后面即可。
验证这个数字签名时,Alice首先利用公钥解密数字签名,得到报文的摘要。 然后,她使用相同的散列函数计算报文的摘要,然后比较两个摘要,只要相同就可以证明这个数字签名是有效的。
数字签名提供比报文鉴别码更强大的保证。 如果能保证私钥仅有发送方所有,那么数字签名提供了抗抵赖性(Non-repudiation),只要一个报文具有数字签名,那么发送方就不能否认这个报文来自自己。 相对地,MAC在生成过程中不使用任何加密或解密过程,因此速度更快,但是其只能确认报文未受修改,不具有抗抵赖性,因此是可否认的鉴别。 如果需要广泛地使用数字签名,那么就需要广泛地启用公钥加密,为此需要专门的公钥基础设施(Public Key Infrastructure,PKI)。
公钥认证
数字签名的重要用途之一就是认证公钥确实属于某个特定的实体,这称为公钥认证(Public key authentication)。
数字签名确实可以确保报文来自其自称的发送者,且报文未受篡改,但是只凭借发送方和接收方两方,不能保证其自称的发送者就是真正的发送者。 假设Trudy使用自己的私钥对一个报文进行签名,然后把报文和公钥发送给Alice,并自称是Bob发送的。 Alice只能凭借报文做出决定,她用公钥成功验证了这个报文未受篡改,但是没有办法验证这个报文确实是Bob发送的,除非她事先能够知道Bob的公钥(如同向Github添加GPG公钥,或是为SSH添加密钥那样),或是有其他办法确定一个公钥是否真的属于其自称那一方(如同我们将要介绍的那样)。
将公钥与实体绑定的工作通常是由认证中心(Certification Authority,CA)进行的。 CA具有以下功能:
- 证实一个实体的真实身份。CA只负责证实网络实体与网络真实身份的对应关系,而如何证实现实实体与网络实体的对应关系则不是一个密码学问题。
- CA证实身份后,便会生成一个把实体和公钥绑定起来的证书。这个证书包括公钥和标识实体的一些的标识信息(如域名、IP地址、姓名等)。CA负责用自己的私钥对这个证书进行数字签名。
如何利用公钥认证确定公钥确实属于某个实体呢? Bob使用完全相同的方式进行数字签名,唯一不同的是,Bob不发送其公钥,而是发送其证书。 这个证书,正如前文所述,既含有公钥,又含有CA的数字签名。 Alice收到报文和证书后,使用CA的公钥验证其公钥确实和Bob对应,然后再使用Bob的公钥验证报文的完整性。
CA的公钥(和证书一起)通常是提前安装到计算机之中的,在Windows中可以使用证书管理器(certlm.msc
)查看。
国际电信联盟(International Telecommunication Union,ITU)和互联网工程任务组(Internet Engineering Task Force,IETF,负责维护RFC)分别提出了ITU X.509和RFC 1422作为基于CA的证书管理规范。
端点鉴别
端点鉴别(End-point authentication)就是实体通过计算机网络向另一个实体证明其真实身份的过程。 我们重点关注通信实际发生时如何鉴别活跃的实体,比如用户如何向电子邮件服务器证明自己的身份。 鉴别应当在报文和数据交换的基础上,作为某种鉴别协议(Authentication protocol)的一部分独立完成。
最简单的鉴别协议就是直接让Alice向Bob发送一个自称为Alice的报文,但是这种鉴别协议显然不能抵抗任何攻击。
我们知道互联网上每个实体都有独立的IP地址,那么能否用IP地址作为鉴别的凭据呢? 答案是可以,但不够好。 首先,IP地址可以而且确实会发生变动; 其次,IP地址位于网络层,而通过编写驱动甚至自己的操作系统,任何人都可以伪造IP数据报。 这种伪造IP源地址进行的攻击称为IP哄骗。
接下来我们使用口令(Password)进行端点鉴别。 这种鉴别方法实在是过于常见,但这并不意味着其非常安全。 首先,如果口令没有加密,那么任何入侵者都可以通过窃听或嗅探(Sniff)来获取Alice的口令,从而伪装Alice。 其次,即使使用加密方法对含有口令的报文进行加密,这种鉴别协议也不能防止重放攻击(Replay attack)。 只要用于加密的密钥没有改变,那么Alice发送的正确的口令加密(或者散列)后的报文总是一致的。 Trudy仅仅需要记录这个报文然后重新发送,就可以冒充Alice,正如阿里巴巴对抗四十大盗那样。
上一个鉴别协议出现问题的最大原因在于无法区别正常的和重放的报文。 为此,我们引入不重数(Nonce)来进行区别。
使用不重数的鉴别协议如下:
- Alice向Bob发出鉴别请求;
- Bob选择一个不重数,并发送给Alice;
- Alice用一个对称密钥对不重数进行加密,然后发送至Bob;
- Bob使用密钥解密并和选择的不重数比较。
也可以使用公钥加密系统,此时Alice用自己的私钥加密,而Bob使用公钥进行解密,但这个公钥必须被认证(即包含在证书中),否则Trudy仍可以冒充Alice。 Bob选择的不重数要么是从很大范围内随机的,要么需要附有报文鉴别码。 否则Trudy可以冒充Bob,发送不重数,记录Alice的响应,然后使用重放攻击。 Alice可以使用自己的口令加以处理作为对称密钥,如同基于口令的加密那样,这样这种鉴别协议就和口令结合起来了。
实际上,服务器通常不会储存客户的口令,而是储存口令的散列值(摘要),客户在本地计算口令的散列值,然后和服务器的散列值匹配。 这种加密可以防止服务器数据库的泄露导致口令的泄露。 为了避免暴力破解散列值的原像,在进行散列之前,往往还要在口令后附上一串随机字符串(称为“盐”)。
使用会话ID(Session ID)或会话权标(Session token)1的鉴别和不重数的核心思想相同。
应用层加密:电子邮件与PGP
正如网络协议栈分为几层一样,网络上的安全也可以分为几层。 我们将分别考考察应用层的PGP、连接层的SSL、网络层的IPsec和链路层的一些安全协议,它们分别为所在的层提供安全服务。
如果我们能够在更底层提供安全,那为什么还需要更高层的安全协议呢? 首先,更底层的安全协议不能提供更高层所需的安全性。 在网络层的安全协议能够保证某个数据报确实来自指定的客户端,从而提供比应用层加密更“深入”的安全性,但是不能保证希望使用某个服务的用户确实是真实的用户,因为同一用户可以使用不同的IP地址。 其次,在更高层部署安全协议通常更加简单,开发一个应用程序比开发一个系统或驱动更简单,而开发软件比设计硬件更简单。 正是因此,PGP作为应用层协议才能够成为互联网上第一个广泛应用的安全协议。
电子邮件加密
我们现在来关心电子邮件加密的问题,首先来分析电子邮件加密的需求。 对电子邮件应用,我们需要它提供以下安全服务:
- 机密性:没有人希望其他人可以随意读取自己的电子邮件;
- 端点鉴别:接收方和发送方都希望确认对方的身份,接收方鉴别可以通过加密一起实现,但发送方鉴别就更加困难;
- 报文完整性:确保电子邮件不被其他人修改。
对称加密可以较好的完成前两个任务,但是分发密钥相当困难,因此我们需要使用公钥密码系统。
首先我们考虑如何保证机密性。 结合我们此前的讨论,这一点是比较容易完成的。 出于效率考虑,我们选择使用会话密钥,而非完全使用非对称加密。 具体而言,使用以下几步:
- Alice选择一个会话密钥$K_S$;
- Alice使用会话密钥加密报文,使用Bob的公钥$K_B^+$加密会话密钥$K_S$;
- Alice将加密的会话密钥$K_B^+(K_S)$附加在报文后面(称为“级联”),然后发送整个包;
- Bob接收到这个包,从中提取加密的会话密钥$K_B^+(K_S)$,然后解密得到会话密钥;
- Bob使用会话密钥解密报文。
显然,这些步骤并不提供报文完整性,也不提供端点鉴别服务。
接下来我们抛弃保密性,考虑如何设计一个提供发送方鉴别和报文完整性的系统。 显然,这就是数字签名所提供的服务,我们因此可以设计一个雷同的系统:
- Alice使用一个周知的散列函数$H$,计算其报文的摘要;
- Alice使用她的私钥对摘要进行签名,得到$K_A^-(H(m))$;
- Alice将报文与签名级联起来,然后发送整个包;
- Bob接收到这个包,提取签名,使用Alice的公钥解密、计算散列并进行比较。
现在,我们把两者组合起来,就可以得到完整的系统:
- Alice选择一个会话密钥;
- Alice使用一个周知的散列函数计算摘要,并使用其私钥进行签名;
- Alice将两者级联起来,组成一个包,并用会话密钥加密;
- Alice使用Bob的公钥加密会话密钥,然后把加密的会话密钥和加密的包级联起来,组成最后的邮件。
这个加密过程总共使用了一次Alice的私钥,一次Bob的公钥和一次对称密钥。 这个加密方式没有对加密的会话密钥进行报文完整性的保证,但是即使损害了其完整性,也只会导致邮件无法解密,不会导致其泄露。
最后,我们仍需要解决在数字签名中出现的公钥认证问题,即虽然发送方鉴别保证了报文确实来自自称的发送方,但是如何确保自称的发送方就是实际发送方呢? 这实际上是公钥分发的问题,即公钥如何正确地分发到接收方去。 此前介绍的CA能够解决这个问题,而即将介绍的PGP则以非中心化的信任网络进行公钥的分发。
Pretty Good Privacy
自从1991年Philip Zimmermann免费发布Pretty Good Privacy(PGP)后,其已经有了长足的进展,而彼时美国政府仍将加密算法视作军火而禁止其传播。 PGP可以被用来进行电子邮件加密,其加密方式和上文所述的最后一种方式几乎完全一致。 值得注意的是,PGP现在已经不止是一个应用程序,而已经演化出了OpenPGP这一数据加密标准,规定在(RFC 4880)[https://tools.ietf.org/html/rfc4880]中。
根据版本不同,PGP使用MD5或SHA等密码散列函数计算摘要,使用CAST、三重DES或IDEA进行对称加密,使用RSA进行公钥加密。
PGP直到近几年才支持基于证书和CA的PKI,在此之前,其使用称为信任网络(Web of trust)的机制进行公钥分发。 在信任网络中,每个人的证书并不由CA签署,而是由网络中的其他人签署,同时每个人自己也可以为其他人签署证书。 每个人自己的公钥被签名的次数越多,那么他签名的效力就越大。 除了在因特网上进行这种担保之外,一些PGP用户在线下举行密钥签署聚会(Key-signing party)来用自己的私钥签署对方的公钥。 在创建自己的证书之前,使用者通常还需要指定一个第三方,当自己的私钥丢失时,可以通过此第三方注销对应的公钥。
信任网络作为去中心化的公钥分发方法,其不容易受到单个主机故障的影响。 但是,没有中央控制者(CA)也导致新加入的用户或是位于信任网络不发达的偏远地区的用户难以受到其他用户的信任。 为了改善这一情况,有人搭建了公钥分发服务器来上传或分发被信任的公钥,但是这种第三方中间人又容易受到攻击。
-
Token一般称为“令牌”,权标是国家标准推荐的翻译。权标也有“法西斯”的意思。 ↩