文章整理自范学雷《实用密码学》, 同时删减了部分比如分组算法怎么运算,初始化向量怎么选择、对称密钥的生成管理等章节内容。

信息安全的六个需求

信息安全的六个基本需求:

  • 身份识别、身份认证和授权。这三个需求解决了权限管理的基本问题: 该怎么标识身份?该怎么验证身份?以及一个身份拥有什么样的权利?
  • 信息的机密性、完整性、可用性。这三个需求解决了信息安全的基本问题: 信息怎么能够完整地、秘密地传递出去、接收进来?

image-20231031140005575

密码学最基础的分支有三个,第一个是单向散列函数,第二个是对称密码技术,第三个是非对称密码技术。这三项基础技术的组合运用,诞生出了丰富的安全协议和体系,比如说数字证书、安全传输、区块链、数字货币等。

如果粗陋地来看,对称密码技术可以通过加密、解密,解决“机密性”的问题,单向散列函数,可以解决“完整性”问题,非对称密码技术可以解决授权和认证的问题,通过我们对这三项基础技术的综合运用,就可以提高系统的“可用性”。

单向散列函数:如何保证信息完整性

单向函数指的是正向计算容易,逆向运算困难的函数

散列函数是一个可以把任意大小的数据,转行成固定长度的数据的函数。比如说,无论输入数据是一个字节,或者一万个字节,输出数据都是 16 个字节。

我们把转换后的数据,叫做散列值。因为散列函数经常被人们直译为哈希函数,所以我们也可以称散列值为哈希值。通常的,对于给定的输入数据和散列函数,散列值是确定不变的。

既然输入数据的大小没有限制,而输出结果的数据长度固定,那么你觉得,会不会存在散列值相同的两个或者多个数据呢?一一是确定存在的。

通常,我们把这种情况称为散列值碰撞。对于散列函数,散列值碰撞可不是一件好事情。

如何降低散列值碰撞的可能性?

  1. 最直观的办法,就是在输出数据的长度上想办法。虽然散列值长度固定,但是,我们可以让数据变得更长,散列值越长,存在相同散列值的概率就越小,发生碰撞的可能性就越小。但从另一个角度来说,散列值越长,通常也就意味着计算越困难,计算性能越差。
  2. 除了散列值长度之外,想要降低散列值碰撞的可能性,我们还要考虑散列值的质量。一个好的散列函数,它的散列值应该是均匀分布的。也就是说,每一个散列值出现的概率都是一样的。

单向散列函数既是一个单向函数,也是一个散列函数。它不仅要满足单向函数的要求,还要满足散列函数的要求。你还记得这两种函数的要求吗?其中,最要紧的就是:

  1. 逆向运算困难
  2. 构造碰撞困难

单向散列函数是一定要逆向运算困难的。

雪崩效应(Avalanche Effect) 是密码学算法一个常见的特点,指的是输入数据的微小变换,就会导致输出数据的巨大变化。严格雪崩效应是雪崩效应的一个形式化指标,我们也常用来衡量均匀分布。严格雪崩效应指的是,如果输入数据的一位反转,输出数据的每-位都有50%的概率会发生变化

一个适用于密码学的单向散列函数,就要具有雪崩效应的特点,也就是说,如果一个单向散列函数具有雪崩效应,那么对于给定的数据,构造出一个新的、具有相同散列值的数据是困难的。

怎么解决完整性问题?

想要解决完整性问题,我们就要知道完整性问题的背后逻辑是什么。

完整性意味着什么?完整性的核心是数据未经授权,不得更改。对于“不得更改”这四个字,你最直观的感受是什么?是不是无论如何,数据都没有办法改动?这是一个很强的解读。一般情况下,也很难有满足的场景。

还有一种站在反面看的、曲线的解读,就是如果数据有变动,能够被检测出来,我们就不采纳被算改的数据。使用单向散列函数,就可以通过检查数据是否有变动,来解决数据完整性问题。

判断一个现存的算法,还能不能继续使用是我们选择算法的第一步。根据这个标准,我把常见的算法分为了以下三类:

  1. 退役的算法;
  2. 遗留的算法;
  3. 现行的算法。

image-20231031140214840

image-20231031140224919

image-20231031140231612

一个典型的单向散列函数,由四个部分组成: 数据分组、链接模式、单向压缩函数和终结函数

单向散列函数处理过程

对于单向散列函数,目前现行的、流行的算法有

SHA-256

SHA-384

SHA-512

对称密钥

什么是加密?

在讨论对称加密技术之前,我们要先了解加密、解密和密钥这几个概念

其实这几个概念还是很容易理解的。把信息或者数据伪装、隐藏起来,转换成难以解释的信息或者数据,这个过程叫做加密。和加密这个过程相反的过程,就叫做解密。

一般来说,加密产生的那个难以解释的信息或者数据,我们把它叫做密文(Ciphertext)。对应的,加密前的数据,我们通常把它叫做明文 (Plaintext)

密文信息通常看起来都是晦涩难懂、毫无逻辑的,所以我们一般会通过传输或者存储密文信息,来保护私密数据。当然,这建立在一个假设基础上:没有经过授权的人或者机器很难通过密文计算出明文;经过授权的人或者机器,才能够通过密文计算出明文.

那经过授权的人或者机器,是怎样通过密文计算出明文的?对,就是使用密钥

在现代密码学里,密钥是在加密和解密运算里,决定运算结果的一段信息。因为,加密要使用密钥把明文信息转换为密文;解密要使用密钥把密文复原为明文

也就是说,加密运算需要两个输入: 密钥和明文。解密运算也需要两个输入: 密钥和密文

到了现代密码学,加密数据的安全性就依赖于加密算法的质量和密钥的保密性这两个因素。密钥部分,是私有的部分,需要严格保密;算法部分,变成了公开的部分,要接受公开讨论、评测,接受各种分析和攻击。一个算法,如果在接受了公开的分析、评测和各种各样的攻击之后,还依然被认为是安全的,我们才能说,这个算法的安全性是真的经得起考验的。

一个现代密码学算法的安全性,都是基于密钥的保密,而不是算法保密要求

什么是对称密钥?

对称密钥,顾名思义,就是每一个参与者都持有相同的密钥,使用相同的密钥。

非对称密钥,就是指每一个参与者都持有不同的密钥,使用不同的密钥。

怎么选择对称密钥算法

像单向散列函数一样,我们可以把对称密钥算法也按照退役的、遗留的以及现行的算法来分类。

image-20231031140808917

image-20231031140819465

image-20231031140826642

序列算法和分组算法

不知道你注意到没有,在上面的表格里,RC4和 ChaCha20 没有数据分块数据,而3DES和AES有数据分块数据。这是因为 RC4和ChaCha20是序列算法,3DES和AES是分算法。那么,问题来了,序列算法和分组算法有什么不一样的呢?

我们前面讲过,为了能够处理任意大小的数据,并且输出结果长度固定,单向散列函数需要对数据进行分组,然后按数据组进行运算。在对称密钥算法里,因为输出结果的长度没有限制,对数据的处理方式,也就有了更多的想象空间。

如果我们从数据是否分组这个角度考虑,就有两种处理方式

  1. 进行数据分组,然后按数据组运算,这就是分组算法
  2. 不进行数据分组,按照原始数据的大小进行运算,这就是序列算法。

那么,怎么计算序列算法呢?

序列算法的基本思路,就是从对称密钥里推导出一段和明文数据相同长度的密钥序列,然后密钥序列和明文进行亦或运算得到密文,和密文进行异或运算得到明文

image-20231031140931688

序列算法的关键,是怎么从固定长度的对称密钥推导出参与运算的任意长度的密钥序列。一般来说,密钥序列的推导和异或运算都是快速的运算。所以,序列算法通常被认为是更高效的算法

比如说,在类似的条件下,相同安全强度的对称密钥,ChaCha20 算法要比 AES 算法快到六倍,比 Camellia 算法快近十倍。如果你关注过浏览器底层技术细节,你可能会注意到,ChaCha20 是现代主流浏览器优先选择的加密算法。

由于不需要数据分组,序列算法的安全性主要取决于密钥序列的推导算法,而不用考虑数据分组带来的种种陷阱。对于应用程序而言,这是一个便于使用,不易出错的选择。

分组算法的安全性,除了算法本身之外,还取决于数据分组的策略。应用程序需要同时指定分组算法和分组策略。对于不太了解密码技术的细节的程序员来说,这实在是一个不小的挑战。

良好的性能,以及皮实的用法,这是我倾向于优先使用序列算法的两个基本原因。同样按照这两个标准,同时考虑安全强度,我建议你使用下面的对称密钥算法,按照优先级从高到低排列:

ChaCha20

AES-256

AES-128

如何防止数据被掉包

简单来说,就是要能够验证消息。

怎样有效地验证一段信息?

首先,我们来分析下,要想有效地验证一段信息,需要满足什么条件呢?
第一个条件就是,我们要有额外的信息。只有要验证的信息本身,是没有办法验证这个信息的。也就是说,信息本身不能自己验证自己。这个额外的信息,我们暂且称之为验证信息。
第二个条件就是,验证信息和待验证的消息之间要有关联。如果没有关联,也就意味着如果我们替换掉待验证的信息,验证信息并不受影响,这显然起不到验证的作用。
如果待验证的信息有变动,验证信息也要变动,而且验证信息的变动结果要不可预测。如果可以人为构造一段信息,它的验证信息和待验证的信息是一样的,也起不到验证的效果。
第三个条件,就是验证信息的计算要快,数据要小
那么,有同时满足上述三个条件的一个方案吗? 消息验证码(Message AuthenticationCode,MAC)就是最常用的满足上述三个条件的一个方案。

消息验证码是怎么工作的?

首先,我们来看使用消息验证码的前提,就是信息的发送方和接收方要持有相同的密钥,这和我们前面讨论的对称密钥的条件是一样的。能够使用对称密钥的场景,都能够满足这个前提。
另外,信息的发送方和接收方要使用相同的消息验证函数。这个函数的输入数据就是对称密钥和待验证信息。信息的发送方使用消息验证函数,可以生成消息验证码。
接下来,信息发送方把待验证信息和消息验证码都发送给信息接收方。信息接收方使用相司的消息验证函数和对称密钥,以及接收到的待验证信息,生成消息验证码
然后,信息接收方对比接收到的消息验证码和自己生成的消息验证码。如果两个消息验证码是一样的,就表明待验证信息不是伪造的信息。否则,待验证信息就是被篡改过的信息。

怎么选择消息验证函数?

最常见的方案就是基于单向散列函数的消息验证码 (Hash-based Message Authentication CodeHMAC,简称 HMAC)

为什么需要对称密钥?

我们先来看看,如果没有对称密钥的加入,消息验证码还能不能工作。

信息发送方把待验证信息和消息验证码都发送给信息接收方。假设存在一个中间攻击者能够解开待验证信息和消息验证码。由于单向散列函数是公开的算法,中间攻击者就可以算改待验证信息,重新生成消息验证码。
然后,中间攻击者把算改的信息和算改的验证码发给信息接收方。算改的信息和算改的验证码能够通过信息接收方的信息验证。也就是说,这样的话,信息接收方就没有办法识别出这个信息是不是原始的、没有算改的信息。这样,信息验证就失效了。

可是,如果对称密钥参与了消息验证码的运算,由于中间攻击者并不知道对称密钥的数据,攻击者就很难伪造出一个能够通过验证的消息验证码。换一个说法,对称密钥的参与,是为了确保散列值来源于原始数据,而不是篡改的数据

有哪些常见的HMAC算法?

HMAC 算法是由单向散列函数的算法确定的。下面的表格,我列出了一些常见的算法。同样的,我们把HMAC算法也按照退役的、遗留的以及现行的算法来分类

image-20231031153734261

image-20231031153741770

其中,HmacSHA256和HmacSHA384 是目前最流行的两个 HMAC算法。和以前的讨论一样,为了最大限度的互操作性和兼容性,我们应该选择当前最流行的算法。

加密数据能够自我验证吗

先加密还是后加密

想要保持信息的私密性,我们可以在信息传输之前,把明文数据加密成密文数据,然后传输密文数据。如果我们还想要保持信息的完整性,我们就要使用消息验证码。
第一个来到我们面前的问题是:消息验证码和信息加密该怎么结合起来?或者换一种说法就是,怎么构造可认证的加密(Authenticated Encryption (AE)) 呢?

加密和验证组合起来的方式不外乎三种方案

加密并验证

第一种方案,就是加密明文数据,计算明文数据的消息验证码,输出密文数据和验证码这种方案,我们简称为加密并验证。安全外壳协议 (SSH)就是采用加密并验证的方案.

image-20231031154222538

这个方案的消息验证码,保护的是明文信息的完整性,而不是密文信息的完整性。如果明文信息相同,它的消息验证码也是相同的。从攻击者的角度看,如果发现两个相同的消息验证码,就可以猜测明文信息大概率是相同的。

我们前面反复讨论过,为什么要使用初始化向量来避免重复的明文生成重复的密文。这个方案,又一次把这个缺陷暴露了出来,只不过现在,我们是通过消息验证码来判断的。

加密后验证
第二种方案,加密明文数据,计算加密数据的消息验证码,输出密文数据和验证码。这种方案,我们简称为加密后验证。IPSec 协议采用的就是加密后验证的方案。

image-20231031154453576

这个方案的消息验证码保护的是密文信息的完整性,而不是明文信息的完整性。由于密文是从明文演算过来的,也就间接地保护了明文的完整性
另外,只要加密算法不把相同的明文信息加密成相同的密文信息,它的消息验证码也就是不同的。所以,这个方案没有上面的加密并验证方案的安全问题。

验证后加密
第三种方案,则是计算明文数据的消息验证码,加密明文数据和验证码,输出密文数据。
这种方案,我们简称为验证后加密。SSL 协议采用的就是验证后加密的方案。

image-20231031154704808

这个方案的消息验证码,保护的是明文信息的完整性,而不是密文信息的完整性。如果我们把明文信息和消息验证码看作是一个数据,我们前面提到的 CBC 攻击方案是不是似乎又回来了?

该选用哪一种方案

由于新的密码分析技术的进展,尤其是前面我们讨论过的 BEAST 攻击这种新技术的出现加密并验证以及验证后加密这两种方案都受到了很大的挑战和质疑
接下来,在2014 年以后,无论是 SSH 协议还是TLS 协议,都提供了加密后验证的选项用来提高协议的安全性。其中,2018 年新发布的 TLS 协议,甚至完全抛弃了CBC模式也不再使用上述的任何一个方案。

所以,如果我们只能从上述三个方案中间选择,加密后验证这个方案目前来说,是最安全的方案

TLS协议采用的新加密方式(带关联数据的加密

到目前为止,我们都没有讨论,信息是怎么传递给对方的。无论是在互联网里,还是现实生活中,我们要传递给对方的信息,并不单纯只有信息本身。

信封上还要贴邮票、盖邮戳;快递的传递,要有包裹,包生活中信件的传递,需要信封,裹上要贴快递单。信封和包裹,虽然是用来携带具体信息的,但其实也是信息的一部分信封里的信件是要保密的,但是信封上的信息是公开的。信封上的信息虽然是公开的,但是同样不可更改。

网络上的信息也是这样,而且更复杂。比如一段网络数据,除了要携带应用要传递的信息外,它的信封上一般还要有版本号,信息类型以及数据长度等信息。

image-20231031155744774

如果一段信息的版本号或者数据类型被算改,接收方就没有办法正确解读接收到的信息;

如果信息的数据长度被修改,接收方就没有办法判断接收的数据是不是完整的,从而影响系统的读写效率,甚至进而存在拒绝服务攻击的风险。
那有没有办法保护信封上的信息,也就是公开部分信息的完整性?

带关联数据的加密,就是用来解决这个问题的。在解决公开信息的完整性问题的同时般的算法设计也会同时解决掉私密信息的完整性问题。所以,这一类算法,通常也叫做带关联的认证加密(Authenticated Encryption with Associated Data (AEAD))。

image-20231031155850028

不同于我们前面讨论过的加密函数,带关联的认证加密的加密函数需要三个输入数据

  1. 加密密钥
  2. 明文信息
  3. 关联信息

输出结果包含两段信息

  1. 密文信息;
  2. 验证标签。

一般来说,验证标签可以看做是密文信息的一部分,需要和密文信息一起传输给信息接收方。
如果改变明文信息,密文信息和验证标签都会变化,这一点,就解决了明文信息的验证问题。如果改变关联信息,至少验证标签会不一样,这一点,解决了关联信息的验证问题。

对应地,带关联的认证加密的解密函数需要四个输入数据

  1. 解密密钥;
  2. 关联信息;
  3. 密文数据;
  4. 验证标签;

而输出的是明文信息。在解密过程中,如果密文信息或者关联信息验证失败,明文信息不会输出。换句话说,只有明文信息和关联信息的完整性都得到验证,才会有明文信息输出。

image-20231031160431031

这么一看,带关联的认证加密既解决了需要保密信息的私密性和完整性,也解决了关联信息的完整性问题,这使得带关联的认证加密算法成为目前主流的加密模式
带关联的认证加密算法的广泛使用,也使得曾经占据主导地位的 CBC算法可以从容地退出历史舞台。因为,带关联的认证加密算法能够进行自我验证

自我验证,就意味着解密的时候,还能够同时检验数据的完整性。这无疑减轻了应用程序的设计和实现压力。有了带关联的认证加密算法,应用程序再也不需要自行设计、解决数据的完整性问题了。

AEAD带关联的认证加密算法

有哪些常见的算法?

还是老规矩,我们先来看看有哪些常见的算法。现在,常见的 AEAD 模式有三种
GCM;
CCM;
Poly1305。

一般地,我们可以把带关联数据的认证加密看做一个加密模式,就像 CBC 模式一样,我们可以和前面提到的AES 等加密算法进行组合。 ChaCha20和 Poly1305 通常组合在-起; Camellia 与 AES 通常和 GCM 以及CCM 组合在一起。
由于AEAD模式相对较新,而3DES/DES 等遗留或者退役算法又存在明显的安全缺陷,所以,我们一般不会使用遗留或者退役算法的 AEAD 模式。

当前推荐使用的、有广泛支持的、风险最小的算法。它们是
AES/GCM;
ChaCha20/Poly1305;
AES/CCM。

[完]