密码学知识和应用框架(图/码)
先上一张图(原创):
图中描述一个一个最典型场景的密码学应用的场景。客户端有一个信息,需要发送给服务端,为了保证这个过程的安全,需要涉及的相关的数据操作和转换,基本上就覆盖了密码学相关的专业领域。
目的和基本概念
密码学相关计算和处理的目的,主要是为了使用技术手段如程序和算法,保障信息在传递过程中的安全和可靠。主要包括:
- 完整性
能够确认,接收到的信息就是所发送的原始信息,是完整的,未经篡改的。
- 机密性
使用某种方式确定,只有通信的双方,才能知晓通信的实际内容。在现有的技术条件下,信息的机密性通常使用信息加密和解密的操作方式达成。
- 不可否认性
如果约定好信息应该是从A发给B的,就必须有某种方式可以确认这个信息确实是从A发出来的,并且只能由A发出。本质上是确认信息和实体之间的关联。
- 抵抗攻击和破解
安全性并不是简单的0和1,大多数情况下是一个程度和平衡的问题。我们只能尽力在约束的限制下,提供尽可能高的安全性,其实并没有绝对安全和完美的系统。所以,遵循一些基本原理、原则和最佳实践,也可以明显改善安全性。比如最基础的信息加密,在内容、密钥和算法一定的情况下,可以通过引入随机信息和处理机制,可以做到“一次一密”,“动态结果”,就可以用很小代价大幅度提高破解的难度。
另外,之所以将密码学相关技术和算法,称为套件(Crypto Suit)。是因为要综合解决信息安全的问题,可能会用到几种技术的组合,它们分别解决一部分信息安全问题,综合起来就可以提供比较完善的安全保障。
编解码(Encode & Decode)
我们现在使用的大多数互联网应用系统,都是人机系统,所以并不是完全的程序之间的交互,还涉及到人与机之间的交互。比如我们在输入输出数据的时候,都必须使用某种形式的人类语言或者符号系统,但其实对于计算机信息处理或者信息传输,它们都使用信息的编码方式(底层都是二进制数据bit)。所以,需要规划和设计一种能够在人类符号系统和计算机数据之间进行适配和转换的系统,就称为编解码系统。
信息技术最早使用的编码系统是ACSII(American Standard Code for Information Interchange,美国信息交互标准编码),显然它是为英语这种字母型语言设计的编码方式。显然不适合于中文这种符号型语言的使用需求,于是就创建了GB2312、GBK、JIX、KSX等编码方式。在相当长的一段时间里,这些编码虽然解决了特定语言的问题,但还是无法解释所有语言在同一场景中的需求,还造成了很多兼容性的问题。于是就产生了Unicode,它具备非常大的编码空间,从而兼容世界上所有的语言和文字。
经过多年的发展,得益于技术和处理能力的进步,复杂编码带来的性能影响已经完全可以接受。现在的互联网应用已经基本统一了编码系统。一个统计资料表明,截止到2019年11月,在所有网页中,UTF-8编码应用率高达94.3%,已经成为事实上的行业标准。所以,现在我们在Web应用中,最常见的编解码场景,就是将UTF8转为字节流,或者反向进行转换。这些现在在技术上实现非常简单,一般使用TextEncoder和TextDecoder就可以操作。
我们在密码学应用中,还经常接触到所谓base64编码和hex编码。本质上而言,它们应该是二进制编码信息,只不过使用了特定的方式进行呈现而已。所以,它们的转换方式和UTF8不太一样,由其算法和规则决定。
密码学应用开发者还应当熟悉字节数组的处理规则。虽然所有信息,都可以编码成二进制的数字序列,但这样在开发和调试时,非常不方便(太长了)。所以在实际情况下,通常使用一个小整数(Uint8,8位无符号整数)来代表一个8位的二进制字符串,成为一个字节(byte)。二进制数组,就可以方便的转为字节数组,来进行处理了。所以我们就经常在密码学的程序中,看到这个字节数组的操作,比如SHA256的结果,就是32字节(256位),每一个字节,可以使用两个hex字符来表示,所以SHA256的结果,可以用64个hex字符来表示。
相同的逻辑,四个字节,可以组合成一个Word(字,其实就是一个32位的整数),可以更方便的表示和计算,比如在AES里面的密钥扩展,通常都是使用字来表示的。由于程序中一般都有整数的位移操作,所以在这些编码方式之间进行转换,可以是非常简单和高效的,算法中也通常进行这样的操作。
随机数(Random)
随机数是密码学中,提供信息的动态可变和不可预测性的重要基础。几乎所有编程语言和密码学组件都提供随机数的生成方式。但遗憾的是,由于随机算法本身的确定性,理论上而言,在没有外部系统干扰的情况下,所有生成的随机数,都是“伪随机数”。笔者理解它可能有两个含义和问题:
1 随机算法的开始,基于一个初始值(种子),那么这个种子又是怎么来的呢? 如果它也来自某种规则,那如何保障其随机性呢
2 即使种子是随机的,但后面随机数的生成,又取决于算法,这样就有了某种可预测性,那怎么谈得上随机呢
所以,真正的随机信息,可能并不存在。但笔者觉得,其实人们真正想要的,可能并不是所谓随机性,而是“不可预测性”。所以一般对安全性要求非常高的系统,它可能通过引入一些外部干扰,来达成这一目的。技术上可行的一些方案包括浏览器界面上用户鼠标的移动,网络访问的响应时间、繁忙街头监控摄像头的画面等等。
幸运的是,我们一般也不会有这么高的要求,就简单的使用系统提供的功能就够了。比如JS语言提供的Math.random函数,crypto模块的randomBytes等等。
摘要(Hash)
Hash算法,在中文中有很多名词,比如哈希、杂凑、散列、混淆、摘要等等,笔者觉得,“摘要”这个词是最贴切准确的。因为这个算法的基本目的,就是可以从任意一段信息,可以通过计算,获得一个固定长度的信息。一个优秀的摘要算法应当具备下列特性:
- 正向计算性能好,逆向计算,或者找到相同摘要信息的原始信息(称为碰撞)很困难
- 离散性好,表现就是两个字符串即使只相差一个字符,摘要结果却完全不同,无法从结果找到相关性或者规律
- 由于离散的特性,我们在逻辑上可以认为,这个摘要信息,可以代表原始信息(网络上软件包的校验,就是基于这个设定),再某种程度上是等价的。
理论而言,不管摘要算法如何设计,其编码空间总是有限的。所以原始信息和摘要信息之间是一个多对一的关系,一个摘要信息,除了其编码时使用的原文之外,必然可以找到某些信息也具有相同的摘要值,而且这些信息肯定有很多组,所以,逻辑上不可能通过某种计算,由一个值可以得到多个信息含量更大的值。因为,我们经常听说的“摘要加密破解”这个说法也是不准确的,首先摘要并不是一个加密的操作,因为它并没有简单可逆的解密操作;其次破解的过程,更多的是一个查找一个具备相同摘要值得信息,只要这个查找的效率超过简单遍历,就可以认为它是某种程度的“破解”,于是其实业界会使用一个更恰当的名词:"碰撞collision"。
很容易可以理解,摘要安全性的决定性因素在于摘要的长度,另外就是变换机制了。现在使用最广泛的标准化的摘要算法是SHA256(Secure Hash Algorithms,安全摘要算法,256表示其摘要长度是256比特)。而且,它实际上是新设计的SHA2家族的一个成员,在变换计算的实现方式相比以前也有很大改进。在现有的技术条件下,它在安全性和性能方面有一个比较好的平衡,Web技术和比特币都大量使用此算法。比较早期的算法,如MD5和SHA1已经不推荐使用了。
消息验证码(MAC)
这里的MAC,不是苹果电脑,也不是网卡的硬件地址,而是Message Authentication Code,消息认证码。它可以对一个信息(消息)进行一些技术操作,得到一小段信息,可以用于代表原始信息,并可以对原始信息的完整性进行验证。
这,不就是摘要吗? 确实,摘要确实也可以作为消息验证。但一般为了提升这个过程的安全性,我们一般会使用一个密钥来辅助信息摘要的过程,生成的摘要信息,只能再通过密钥来进行验证,也只有拥有密钥的,才能进行正确的验证。验证的过程也很简单,就是对需要验证的信息,再进行一次计算,来和MAC进行比较是否匹配就可以了。如果原始信息被修改,或者密钥不正确,得到的验证码显然是不能匹配的,这样就保证的信息是原始完整的。
从技术和实现而言,现在的常用的消息验证码,确实也是基于某种摘要技术,所以通常也称为HMAC(Hashed Message Authentication Code)。比如HMAC-SHA256,就是基于SHA256实现的。其实也有基于加密技术的,称为CMAC(Crypted)。
读者可能会想到,我们可以简单的把密钥加到原文前面或者后面,然后做摘要计算,不也能达到相同的效果吗? 简单的答案是这样不够安全。上一代的著名摘要算法MD5就有一个Concatenated combiners(串接组合器)的缺陷让业界认识到简单的连接方法是有一定的安全隐患的。还是应该使用具有数理理论支持,并能够经过安全和实用检测的标准算法。
对称加密(Symmetric-Key)
加密,是为了保障信息在公开环境下的机密性。我们将原始信息称为明文,然后使用某种密码学模型对明文进行再次编码或者转换,生成一组新的信息(密文),这些信息表面上已经完全不能表达明文所携带的信息,这个过程就称为加密。加密必然对应着解密,就是可以使用对应(但不一定)的方式,将密文可以准确的还原成原文。为了提高这一过程的灵活性和安全性,我们通常在加密的时候,使用一些机密信息,甚至包括随机和动态的信息参与加密计算,用于干扰和混淆生成的密文,从而避免由于他人知晓加密规则来进行破解,这些机密信息通常被称为密钥,只有知晓密钥,才能正常的对使用这些密钥加密的信息进行解密。所以密钥除了加密解密双方之外,对于外部应当是机密的信息。
很容易理解,加密和解密应当使用一个相同的密钥,这种加密方式就称为单密钥加密,也称为密钥加密或对称加密。此外,虽然信息的加密解密可以只是逻辑和数学上的概念,我们主要讨论其使用信息技术来实现的场景,就是使用比特表示信息,使用软件和程序执行来进行信息的计算操作。
显然任何人都可以基于基本的数据变换规划自己的对称加密算法,但设计足够安全的算法,并不是一件简单的事情,需要综合考虑加密和解密操作的易实现性、操作效率、资源占用、对硬件架构和软件环境的兼容性、可扩展和可移植性、抵抗破解的能力等很多的因素。
与密码和密码学诞生初期,主要用于军事目的相比,现在的密码学应用已经深入和广泛的应用到如通信、互联网、电子商务、各行业信息化等所有信息技术领域,以称为信息技术最基础的技术设施和构成部分。经过多年的发展、进化和淘汰,现在主流的对称加密算法是以AES和Chacha20两大类算法,它们代表块加密和流加密两大对称加密的技术路线。
虽然都是对称加密技术,但实际上块加密和流加密的构想和操作方式截然不同。块加密也称为分组加密,它先将信息分成固定长度的小组,然后以小组为单位,进行数据的变换和操作;小组操作完成后,再使用一些链接规则,将结果连接起来进行输出,就完成了整个的加密过程;解密方面,基本上就是加密的每个步骤的逆运算,最终由密文和密钥还原成明文。现在分组加密的主流算法是AES-256-GCM。
流加密则完全不同,它基于原始密钥,再增加随机数和计数器,生成一个密钥流,然后用这个流和明文进行简单异或操作,就达到了加密的目的,解密过程巧妙的利用到了异或还原的特性,生成同样的密钥流,再做一遍异或操作,就可以得到明文。显然,流加密的计算更加精巧、高效、灵活,所以在HTTPS协议中的应用也非常广泛。现在流加密的主要算法是Chacha20-poly1305。
除了经典对称加密的核心算法之外,一个重要的技术发展和应用趋势是将加解密和消息验证结合起来,在整个过程中同时保证信息的机密性和完整性。同时,还可以允许在计算过程中增加一些自定义的明文信息,在实际应用实现中更加方便灵活。这种模式称为AEAD(Authenticated Encryption with Associated Data, 带附加信息的认证加密)。
密钥衍生(Key Derivation)
先厘清一下“密码”和“密钥”这两个名词。在密码学场景中,密码通常指由用户记忆的某些机密信息,而密钥是指进行密码学操作时,相关算法的动态参数。密码和密钥都不应该以任何方式公开或者分享。
为了方便记忆和使用,密码通常都是字符数组符号等的组合,而密钥的格式通常由算法要求决定,比如AES-256要求密钥是32个字节,显然需要进行转换,这个就是密钥衍生函数。它可以将任何输出,衍生计算出对应的密钥,在这个过程中,还可以增加一些随机信息和混淆规则(如计算多轮)等等,增加密钥的"随机性"。合理使用密钥衍生函数,可以达到“一次一密”的效果。可以使每次加密使用的实际密钥(衍生后)和加密的内容看起来都是“动态”的,增加了安全性。这种计算和函数常被称为(KDF, Kery Derivation Function 密钥衍生函数)。
常用和标准的KDF包括pbkdf2(Password Based Key Derivation Function version 2)和hkdf(Hashed Key Derivation Function ),它们在nodejs crypto模块中都有实现和支持,可以根据需要灵活选择。
非对称加密
对称加密技术,其实在结构上有一个无解的问题。 其安全性问题,并不在于其算法本身,或者使用密钥的长度,这些问题都可以简单的通过设计和增强算法,或者加长密钥长度来解决。 核心问题在于“对称”,就是加解密双方,必须使用一个密钥。 那么这个密钥,生成后又是怎样告诉对方的呢? 打电话?发邮件?那又如何保证这些机制的安全性呢?
于是,有些天才,基于数学原理,发明了非对称加密的算法。
非对称加密的原理比较复杂,我们这里只简单探讨其基本的使用场景和流程。简单说来,使用密钥加密,需要使用一对密钥(密钥对,KeyPair),公钥和私钥,公钥可以公开,私钥必须保密,公钥和私钥之间有数理逻辑的严格对应关系,并且这个密钥对可以唯一的标识其持有者。比如,如果Charlie准备发送消息给Steven,他需要先获取S的公钥,然后使用这个公钥,对信息进行加密,然后将密文传输给S;S获得密文后,使用自己的私钥,可以对其进行解密。通过数学原理和算法的设计,可以保证公钥加密的信息,只能由其对应的私钥进行解密。这就是非对称加密的基础原理。所以非对称加密也被称为公钥加密。
非对称加密和安全的原理,基于一个比较容易理解的数学特性。这里有一个简单的例子,两个质数相乘,计算非常简单可以得到一个结果,但已知这个结果数据,要找到原来的两个质数,相对就非常困难了,然后利用这个特性和运算律,设计一些等价计算,就可以设计非对称加密算法了。
和对称加密相比,非对称加密通常的实现和计算比较复杂,所以一般不会直接应用到对原始明文的加密中。而是用它来进行密钥的加密和交换(因为密钥通常不会太大,而且大小相对固定),然后再用密钥进行对称加密计算,这样组合非对称加密和对称加密计算,来平衡整体的安全性和执行效率。
虽然看起来非对称加密的实现和操作比较复杂,但在大规模和开放式的使用场景中,它还可以提供一个潜在的优势,就是更高效的密钥管理。 我们做一个简单的评估,如果网络中有N个用户需要交换信息并保障安全,如果使用对称加密,他们直接就需要N(N-1)/2个密钥(不能共享完全密钥);但如果使用非对称加密,总共需要2N个密钥,孰优孰劣,是非常明显的。
现在非对称加密的实现,有两大主流的技术路线。一类叫RSA(三个发明人的名字),基于离散对数的数学原理;另一类称为EC(椭圆曲线),基于椭圆曲线方程的数学原理。 相对而言,椭圆曲线算法使用的密钥长度比较短,可以提供的安全性较高,应用时的参数选项也比较多,应用就越来越广泛,已经成为主流的非对称加密的技术路线了。
签名和验证(Sign & Verify)
除了信息加密外,签名和验证,也是非对称加密技术一个非常重要而广泛的应用方式。签名的目的,首先是为了保障信息的完整性。它可以通过某种计算,从一个原始信息中,生成一个签名信息;并且在原始信息被篡改后,无法通过对应的验证操作。前面探讨过的MAC其实可以满足这个需求,但MAC操作,需要在签名方和验证方共享一个密钥,或者只能“自签自验”,这样使用的场景就受到很大限制。
使用非对称加密进行签名和验证,可以完美的解决这个问题。笔者理解,签名和验证其实是非对称加密的一种有趣的逆操作。就是签名方,可以使用自己的私钥,对信息进行加密,并公开这个密文信息;然后,验证方,可以使用签名方的公钥,对这个密文进行解密,就可以看到原始信息。由于,只能通过签名方私钥对应的公钥,才能正确的解密,所以,能够确认这个信息,确实是签名方加密的,而且只能是签名方使用私钥加密的,从而实现了信息安全的不可否认性。
由于一般而言,非对称加密的计算比较复杂,效率较低,所以实际应用中,通常签名操作也不会直接对信息原文进行,而是先进行摘要计算,然后对这个长度固定的摘要信息来进行签名和验证。这样就组合了摘要、签名和验证这几种操作方式,来保障操作高效顺畅。
密钥协商(Key Exchange, DH)
前面我们提过,在真实的应用场景中,信息还是使用对称加密,但可以使用非对称加密先传输密钥。但其实非对称加密机制还提供了一种更好的密钥分享方式-密钥协商。
密钥协商算法通常又被称为DH,来自其两个发明者Diffie和Hellman。如果直译的话,英文中的Key Exchange 其实是密钥交换的意思,但笔者认为不是特别恰当。其实在DH计算过程中,并没有任何的通信和数据交换发生,它的本质是基于数学的原理,由本方的私钥,和对方的公钥,计算出一个密钥,由数学原理可以保障这一对密钥的一致性,所以笔者觉得“协商”这个词可能更为贴切和准确。
这里有一个简单的公式,帮助大家理解DH的基本原理,实际算法实现可能非常复杂,但不影响对原理和逻辑的理解:
((g^a)^b) % p = ((g^b)^a) % p
计算过程和实例如下:
项目 | 数值 | 说明 |
---|---|---|
原根 G | 3 | 质数常数, 开放底数 |
基数 P | 23 | 质数常数,用于求余 |
Clarlie私钥 va | 5 | 自选 |
Charlie公钥 pa | 243 | = G^va,公开 |
Steven私钥 vb | 8 | 自选 |
Steven公钥 pb | 6561 | = G^vb, 公开 |
Clarlie计算密钥 ka | 15 | = (pb ^ va) % P |
Steven计算密钥 kb | 15 | = (pa ^ vb) % P |
这里也可以帮助解释,为什么相对而言非对称加密计算的效率远不如对称算法。因为对称算法大量使用位计算方式在有限整数空间内进行操作,对于现有CPU指令体系特别友好。而非对称加密大量使用大整数操作,处理和操作要复杂很多。
基于多年开发和应用的经验,笔者慢慢体会到,可能这个DH机制,才是非对称加密的实质和基础。当然,这个结论是没有认真研究其算法基础和源码的前提下得到的,因为从理论上可以推导,使用DH,可以实现所有非对称加密的操作。
- 私钥加密-公钥解密(VEPD)
加密方可以生成一对临时密钥,然后使用密钥协商计算加密密钥(私钥+临时公钥);加密信息后,将密文和临时私钥打包传输给解密方;解密方分离临时私钥,结合加密方公钥计算解密密钥(同加密密钥),然后对密文进行解密。
- 公钥加密-私钥解密(PEVD)
加密方生成临时密钥,然后使用临时私钥和解密方公钥计算加密密钥;加密信息后,将密文和临时公钥发送给解密方;解密方分离临时公钥,结合私钥计算解密密钥(同加密密钥),然后对密文进行解密。
- 私钥签名(VS)
本质上就是VEPD,但使用原始信息的摘要作为加密的明文,加密的结果就是签名信息。只能由签名方的公钥来正确解密。
- 公钥验证(PV)
由于只能用签名方的公钥解密,可以确认此签名信息来自签名方,解密后,和原始信息的摘要信息进行比对完成验证,可以确认内容的完整。
密码学安全理念
作为成熟而广泛使用的信息安全技术基础,当前主流的密码学套件和实现,都是经过严格的安全性分析和认证的,值得信任。没有必要,不需要自己编写和修改算法特别是核心操作和参数,那样会带来一些未知的安全风险。
经过多年的发展和应用,业界也基本上达成了一个认知的共识,就是加密的安全性,应该主要取决于密钥的安全性,而非算法保密的安全性。所以算法应该公开,在业内广泛的进行测试和检查,并在一个相对较长的时间内经过多个方面,在理论和实际上进行考验,最终才能确认算法成熟并且逐渐被予以信任。AES和Chacha等都是这样的一个发展历程,才能被广泛的接受和应用。
示例代码
下列示例代码展示了在nodejs中,进行的相关密码学操作和计算。来帮助读者从应用的角度来感受和理解这个体系。
其他相关内容
本质上这些内容并不是密码学的核心内容,但笔者觉得有必要探讨一些比较重要的相关概念和内容。
一次密码(OTP)
一次性密码(One Time Password),现在经常用于为了提高安全性而使用的多因子认证(Multi Fact Authentication, MFA)。最典型的OTP应用,就是Google Authtiator。它通过独立于应用系统和动态认证信息,大大提高了认证过程的安全性。
OTP并不是独立的技术体系,它可以基于Hash和HMAC。笔者另有一篇文章阐述相关内容,这里就不再赘述。
数字证书(Certificate)
基于前面的密码学相关技术和基础,特别是非对称加密算法,我们可以通过数字证书技术,创建一个相对安全、容易部署和使用的网络通信、身份认证和信息加密传输的体系。
数字证书是一个信息片段,它主要包括以下信息:
1 主体身份:证书标识了一个实体(如网站、个人或组织)的名称、地址等,以及证书用途、时效等其他辅助信息。 2 主体公钥:证书包含了主体所生成的公钥,用于加密和验证数字签名。 3 证书颁发机构签名:数字证书通常由证书颁发机构对证书内容进行数字签名,以确保证书的真实性和完整性,任何人都可以使用证书颁发机构的公钥,对这个信息进行验证。
这里其实还有一个隐藏的信息,就是实体在申请证书,提供自己的公钥之前,其实是生成了一个密钥对的,这个私钥也会被保存下来,在实际的使用场景中,配合其公钥使用。比如使用Nginx搭建网站,就需要同时配置网站的证书和私钥。
在逻辑上,任何人都不能自证真实性,所以一般情况下,数字证书通常由可信的第三方实体,称为证书颁发机构(Certificate Authority, CA)来签发和管理,由于这些工作和内容都是围绕公钥技术来展开的,所以这个体系通常称为公钥基础设施(Public Key Infastruction PKI)。
在实际使用中,证书通常作为一个文件保存在系统中使用。
- timeingSafeEqual(时序安全相等)
Nodejs的crypto模块,提供了一个timingSafeEqual函数,揭示了一个在密码学中,非常重要的安全概念-时序安全。一般情况下,如果比较两个数组或者缓存区内容,比如做签名验证的情况,如果涉及敏感信息,这个比较操作可能会受到时序攻击(Timing Attack)的影响。
时序攻击是一种基于时间差异的攻击方法,攻击者通过观察计算时间的差异来推断比较操作的结果,从而可能猜测出敏感信息。这种攻击利用了计算机处理不同输入所需的时间的微小差异。
timingSafeEqual函数就是为了解决这个问题而设计的。这个函数能够以一种时序安全的方式进行缓冲区的比较。它会以固定的时间执行比较操作,无论比较结果是相等还是不相等。这样可以防止攻击者根据执行时间的差异来推断比较结果。
- SJCL
nodejs虽然提供了功能强大而完善的crypto模块,但起码在前几年,这个库是不能用在浏览器的执行环境中的。在这种情况,SJCL(Stanford Javacript Crypto Library,斯坦福JS加密库)就是一个不错的选择。它是一个纯JS实现,小巧精悍,功能丰富,文档齐全。特别是它的很多实现和处理方式,对于学习和使用密码学相关的知识的开发者,有很大的启示和参考价值。
转载自:https://juejin.cn/post/7240042688313655354