本人录制技术视频地址:https://edu.csdn.net/lecturer/1899 欢迎观看。
我们在平时的工作中或多或少的都接触过加密算法,比如:对称加密,RSA加密算法。我们一般的操作就是上网查找相关的算法,分配自己项目相关的密钥(实际处理中有可能叫Secret Key, APP key等),而对于加密算法本身没有做过多的了解,本篇博客就向大家详细的介绍加密知识,使得我们有一个更好的理解。
不管是在PC或者是APP的登录界面,我们都需要输入“密码”,严格来说,这里的“密码”与密码技术没有任何关系。因为这里的“密码”只是用户记忆或者将其保存在一个地方,没有涉及到任何的加密算法和密钥,因此我们可以将登陆时候输入的“密码”称作为口令(Password)。
编码(Encoding)是信息从一种形式或格式转换为另一种形式的过程。解码(Decoding)是编码的逆过程。从概念上可以看出,编码只是表现形式的转换,没有保密的作用,因为编码和解码的算法是公开的,只要知道是什么编码的内容,任何人都可以轻松地解码。下面给大家介绍三种常用的编码。
ASCII编码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII编码也叫基础ASCII编码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符,点击这里 可以查看具体的对照表。
char ch1 = 'a'; // ASCII 编码对应 97
char ch2 = 'b'; // ASCII 编码对应 98
int diff = ch2 - ch1; // diff 的值是1
int result = (int)ch1; // 转化为int的值就是97
Base64编码是从二进制到字符的过程,可用于在HTTP环境下传递较长的标识信息。标准的Base64并不适合直接放在URL里传输,因为URL编码器会把标准Base64中的“/”和“+”字符变为形如“%XX”的形式。Base64编码表如下表:
索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 | 索引 | 字符 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | A | 8 | I | 16 | Q | 24 | Y | 32 | g | 40 | o | 48 | w | 56 | 4 |
1 | B | 9 | J | 17 | R | 25 | Z | 33 | h | 41 | p | 49 | x | 57 | 5 |
2 | C | 10 | K | 18 | S | 26 | a | 34 | i | 42 | q | 50 | y | 58 | 6 |
3 | D | 11 | L | 19 | T | 27 | b | 35 | j | 43 | r | 51 | z | 59 | 7 |
4 | E | 12 | M | 20 | U | 28 | c | 36 | k | 44 | s | 52 | 0 | 60 | 8 |
5 | F | 13 | N | 21 | V | 29 | d | 37 | l | 45 | t | 53 | 1 | 61 | 9 |
6 | G | 14 | O | 22 | W | 30 | e | 38 | m | 46 | u | 54 | 2 | 62 | + |
7 | H | 15 | P | 23 | X | 31 | f | 39 | n | 47 | v | 55 | 3 | 63 | / |
从上图我们可以看出:Base64的索引范围是0~63,总计64个,而对应的字符分别是 [A-Z,a-z,0-9,+,/ ],总计64个字符,这也是Base64命名的由来。它的目的是任意的字符串,通过Base64编码之后,只会出现这64个字符(Base64编码之后有可能出现’=’, 这个字符后面再说明)组合而成的新的字符串。
情形一:字节个数正好是3的倍数
情形二:字节个数不是3的倍数
由“情形一”描述的Base64分割规则,我们可以总结出任意情况的Base64分割规则(当然包括字节个数不是3的倍数的情形)。
int clen = plen % 3 == 0 ? (plen / 3) : (plen / 3 + 1); // plen: 字符串原始字节数
int old_len = 3 * clen; // old_len: 字符串补0之后的字节数
int new_len = 4 * clen; // new_len: Base64处理之后的分组数
Note: 上述的两个例子说明了不需要补充及补充一个字节的情况,还有一种情况是补充两个字节,大家可以尝试分析一下,比如字符串:“a@aa”。
从上面的分析,我们可以看出,Base64编码处理之后,传输的字节数反而更多了,但是我们在实际的开发过程中随处使用它,比如一般的HTTP[S] 发送请求之前,会进行一次Base64编码操作,这是因为在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据做一个Base64编码,统统变成可见字符,这样出错的可能性就大大降低了。
霍夫曼编码是一种十分有效的编码方法,广泛用于数据压缩中,其压缩率通常在 20%~90% 之间。
假设有一个包含 1000 个字符的文件,每个字符占 1 个 Byte(1Byte=8bits),存储这 1000 个字符就一共需要 8000bits,那有没有更加节省空间的存储方式呢?
思路一:
假设我们通过统计分析发现,这 1000 个字符中只包含 6 种不同字符,假设它们分别是 a、b、c、d、e、f。而 3 个二进制位(bit)就可以表示 8 个不同的字符,所以,为了尽量减少存储空间,每个字符我们用 3 个二进制位来表示。那存储这 1000 个字符只需要 3000bits 就可以了,比原来的存储方式节省了很多空间。a(000)、b(001)、c(010)、d(011)、e(100)、f(101)
思路二:
我们不仅考察文本中有多少个不同字符,还会考察每个字符出现的频率,根据频率的不同,选择不同长度的编码,假设这1000 个字符中只有a、b、c、d、e、f,并且各自出现的频率如下图:
图中,第一列表示字符、第二列表示出现的次数、第三列表示使用的编码、第四列表示占用的二进制位数。上述中使用的编码其实就是霍夫曼编码。a(1)、b(01)、c(001)、d(0001)、e(00001)、f(00000)。通过统计,存储这 1000 个字符只需要 1910bits 就可以了,可以看出压缩比例是76%左右。
霍夫曼编码的中心思想就是:统计字符出现的频率,频率越高,编码比特数越少;频率越低,编码比特数越多。而且要求任意一个编码不能是另一个编码的前缀(这样要求的目的就是防止编码结果产生歧义,导致无法解码)
任意一个霍夫曼编码的结果 都可以使用一个霍夫曼树(二叉树)来表示。对于上述例子的霍夫曼树如下图:
图中橙色的节点就是需要编码的字符(他们在霍夫曼树中全都是叶子节点),而黄色的节点全部是辅助节点,是为了构建霍夫曼树临时生成的。我们发现,编码的时候,将指向左子树的权重全部编码为0,而指向右子树的权重全部编码为1。以字符’d’为例,从根节点出发,走过的节点分别是p、k、z、y、d,而对应的权重编码是0、0、0、1,所以字符’d’的编码是 0001。全部编码结果就是a(1)、b(01)、c(001)、d(0001)、e(00001)、f(00000)。当然,左右子树上面的权重可以自定义,假设将指向左子树的权重全部编码为1,而指向右子树的权重全部编码为0,则全部编码结果就是a(0)、b(10)、c(110)、d(1110)、e(11110)、f(11111),也是正确的,因为他们并不影响最终编码的比特数。
上面就是霍夫曼编码的整体逻辑,由于使用霍夫曼树能够大大减少传输量,所以霍夫曼树又称为最优二叉树。如果要代码实现,有两种思路:
一、 队列构建霍夫曼树
首先根据字符出现的频率,有低到高放入队列中,然后先将队列前面两个元素(f、e)出队,将他们的结果相加构成新的节点(x),而x节点的值就是f、e节点值的和 (50),这一轮计算完毕后,删除刚才用于计算的节点f、e,并且将节点x放入队列中,升序排列,循环前面的操作,直到队列为空位置。 实现思路如下图:
二、数组构建霍夫曼树,这里就不展开叙述了。
Note: 如果对Base64编码及霍夫曼编码的具体实现感兴趣的话,欢迎查看我录制的《算法攻略》视频,里面有详细的C语言代码实现。
加密(Encrypt)相关的内容:对称加密、公钥加密、混合加密、单向散列函数、消息认证码、数字签名、证书及 Diffie-Hellman。其中加密之前的消息称为明文(plaintext),加密之后的消息称为密文(ciphertext),与明文一起参与加密算法的称之为密钥(key),加密的反向过程称之为解密(Decrypt)。
加密及解密的大致流程如下入:
在详细说明这些密码知识之前,我们先了解一下密码常识:
一、不要使用保密的密码算法
这一点有悖于大家的认知。人们一直认为,公司自己设计一套加密算法,私钥公司自己保存,那么别人就无法破解。其实这是一个很严重的错误。如果某个员工泄露或者公司核心员工离职而导致加密算法及密钥泄露,那么可想而知,公司设计的加密算法等于形同虚设。反而那些公开加密算法并且给出详细设计清单的公认加密算法(AES,RSA)才是可靠的,安全的。因为这些加密算法,是经过众多密码学家尝试破译(最终失败)及众多数学家证明很难逆向反推。
二、 使用低强度的密码比不进行任何加密更危险
对于客户而言,加密意味着安全。如果内容没有一点机密性可言,客户直接可以选择不加密。反之,对于重要的内容,会进行加密,但是如果密码强度很低的话,很容易被破解,这样不利于数据信息的安全性。所以要么选择密码强度高的加密算法,要么不加密。
三、 任何密码总有一天都会被破解
鸽巢理论:10只鸽子放进9个鸽笼,那么一定有一个鸽笼放进了至少两只鸽子。
假设RSA密码算法的密钥长度是512bits,那就有2512 种可能。也许大家对这个数字没有具体的概念,我们举例说明:假设我们想暴力破解这个密钥,需要的条件大致如下:
Note: 宇宙的年龄是1011年左右。
根据上述条件,计算的结果是3.16224 * 10154, 而2512 大致结果是 1.340780 * 10154。 所以当达到上述条件的时候,能够遍历一遍512位的RSA密钥空间,可想而知破解难度,但随着科技的发展,后续也许会有重大突破,破解的速度可能更快。但不管加密算法多么的复杂,任何密码总有一天都会被破解。
四、密码只是信息安全的一部分
我们所说的加密操作都是保证数据在传输过程中的安全性,而信息安全还包括很多的其他的层面。比如:密钥被恶意破坏的人发现并且泄露、密钥被其他人骗取等。所以除了传输过程中的信息安全交给加密算法处理,其他的还需要人为约束,制度规范等。
对称加密,采用这种加密方法的双方使用同样的密钥进行加密和解密(加密方和解密方是共享密钥的)。
对称加密的方式有很多,比较流行的有DES(Data Encryption Standard)、三重DES(Triple Data Encryption Standard),但由于实践证明,DES和三重DES已经不安全,可以在可观的时间内被破解。所以现在主流的对称加密是 AES(Advanced Encryption Standard)。AES是公开竞选的,参加竞选的条件就是:被选为AES的密码算法必须无条件地免费供全世界使用。参加者所提交的密码算法,必须在详细设计和程序代码完全公开的情况下,依然保证较高的强度。密码算法的评审不是由某个组织或者评委来完成的,而是由全世界的企业和密码学家共同完成的,其中包括AES竞选的参赛者。一旦被找到弱点,意味着该密码算法落选。在众多的参加者中,最终选择了Rijndael(是由比利时密码学家Joan Daemen 和 Vincent Rijmen 设计的分组密码算法)作为AES。如果大家对AES的具体算法感兴趣的话,请点击这里。
下面就Rijndael 的加密和解密进行说明。
Step1: SubBytes
Rijndael的输入分组为128bits,是16Bytes。首先进行的就是SubBytes处理,也就是以每个字节的值为索引,从一张拥有256个值的替换表找出对应的值,也就是说,要将一个1字节的值替换为另一个1字节的值。
Step2: ShiftRows
这一步是将以4字节为单位的行按照一定的规则向左平移,且每一行平移的字节数是不同的。
Step3: MixColumn
这一步是对一个4字节的值进行比特运算,本质就是进行矩阵运算,得到另一个4字节的值。
Step4: AddRoundKey
上图中的XOR表示异或运算,以"k" 表示的数据是每一轮的密钥。因此这一步的操作就是将MixColumns的输出与轮密钥进行异或运算。
上面的四步操作就是Rijndael的一轮操作,实际上,Rijndael中需要重复进行10 ~ 14 轮计算。
虽然说AES加密算法本身很难破解,但是存在一个致命的问题,就是密钥怎么配送。
1. 事先共享密钥
举个例子,一个公司有100名员工,每个员工都有自己的密钥。员工两两之间需要使用对称加密消息进行通信,比如A需要与B通信,则A将自己的私钥告诉B,B将自己的私钥告诉A;则每个员工需要999个通信密钥,整个公司的密钥数量就是1000 * 999 / 2 = 499500个,这个显然不现实。实现共享密钥虽然有效,但局限性依旧很明显。
2. 通过密钥配送中心来解决
事先准备一台充当密钥配送中心的计算机。这台计算机中保存了所有员工的密钥,对上述问题来说,需要保存的密钥有1000个。这种方式的通信大致如下:
以上就是通过密钥分配中心完成的通信过程。显然能够完成通信,但存在很大的局限性。1. 所有密钥的保存及会话密钥的生成全部由密钥分配中心完成;2. 如果密钥分配中心被入侵,则所有信息都会被泄露。
3. 通过Diffie-Hellman 密钥交换来解决密钥配送问题
4. 通过公钥密码来解决密钥配送问题
其中,第3、4点会在后面的内容中进行详细的说明。
在对称密码中,加密和解密使用的是同样的密钥,即加密和解密只是同一密钥进行相反的运算而已。而公钥加密,加密和解密使用的是不同的密钥。并不是相互对称的,因此公钥加密也称为非对称加密。在公钥加密中的密钥是由接受者自己生成的密钥对 [公钥(Public Key)和 私钥(Private Key) ],其中,接受者将公钥可以通过任意方式发送给发送者,相对的,私钥只能由接受者自己保管。公钥和私钥是一一对应的,由公钥加密的密文,必须使用与该公钥配对的私钥才能够解密。
网络传输过程中,假设有攻击者劫持相关信息。能够截获到的信息:1. B的公钥;2. A发送的密文。但是劫持者没有B的私钥,所以没有办法获取到A发送的明文信息,所以公钥加密解决了对称加密需要配送密钥的问题。但是上述传输还是会出现问题,这个问题就是中间人攻击。
中间人攻击:我们假设中间人是C。在B将自己的公钥发送给A的过程中,C将这条请求拦截,C将B的公钥保存下来,同时C将自己的公钥发送给A;然后A用得到的公钥进行加密(A以为接受到的是B的公钥,其实是C的公钥),将密文发送给B(假设A发送的明文是"I love you"),此时又被C拦截下来,C用自己的私钥进行解密,就可以得知A发送的明文内容是"I love you"。然后C用截获到的B的公钥加密内容(假设C发送的明文是"I hate you")发送,B接受到密文之后,用自己的私钥进行解密。发现接受到的内容是"I hate you"。至此A与B就误会下去了。
通过上述的说明,我们发现:公钥加密本身是可以保证机密性的,而且公钥加密解决了密钥配送的问题,但是它不能确认收到的公钥是否真的是想要的通信对方发送过来的,这种情况,我们就需要对公钥进行认证,关于认证,后续会详细说明。
RSA是一种公钥加密算法(现在市面上几乎都是用这种公钥加密算法),它的名字是有它的三位开发者的姓氏的首字母组成的(Rivest-Shamir-Adleman), RSA可以被用于公钥加密和数字签名(后面会讲解)。下面我们看看公钥加密的代表 - RSA加密解密过程(RSA加密解密表达式很简单)。
RSA加密可以直接用公式表达:
密文 = 明文E mod N
RSA的密文是明文的E次方除以N求余数。其中E和N的组合就是公钥。
RSA解密可以直接用公式表达:
明文 = 密文D mod N
RSA的明文是密文的D次方除以N求余数。其中D和N的组合就是私钥。
那上述加密解密中提到的E,D及N到底是怎么得来的,我们画图进行说明。
下面我们来分析一下为什么RSA算法不容易被攻击。
RSA加密公式是:
密文 = 明文E mod N
而且(E, N) 就是公钥,并且他们是已知的,加密之后的密文也是已知的。破解的问题其实就是已知密文、E、N求解明文。但是这种计算实质是求解离散对数的问题,这是非常困难的,因为人类还没有发现求离散对数的高效算法。
通过使用对称密码,我们能够在通信中确保机密性。然而要在实际中运用对称密码,就必须解决密钥配送的问题。而公钥密码就可以解决密钥配送的问题。但是公钥密码的处理速度远远低于对称密码。所以可以考虑将这两种加密方式结合起来。
混合密码系统中会先使用快速的对称密码来对消息进行加密,这样消息就被转化为密文,从而保证了消息的机密性。然后使用公钥密码对加密消息时使用的对称密码的密钥进行加密。由于对称密码的密钥一般比消息本身要短,因此公钥密码速度慢的问题就可以忽略了。我们日常所使用的HTTPS,就是使用的混合密码系统。混合密码系统的加密流程图如下:
混合密码系统的解密就是一个逆过程:首先使用私钥解密,得到会话秘钥;再用会话密钥解密,得到明文。
我们知道,对称加密和公钥加密能保证消息的机密性,但是不能保证消息是否被篡改或者被伪造,也就是不能检查消息的完整性。而单向散列函数可以根据消息的内容计算出散列值,而散列值就可以被用来检查消息的完整性。单向散列函数(one-way function)有一个输入和一个输出,其中输入称为消息(message),输出称为散列值(hash value)。
消息可以是文本、图像、声音等文件,单向散列函数不需要知道消息实际代表的含义。无论任何消息,单向散列函数都会将它作为单纯的比特序列来处理。散列值的长度和消息的长度无关。无论是1bit,还是100M,或者是10G,单向散列函数都会计算出固定长度的散列值。
单向散列函数必须能够输入任意长度的消息。无论输入多长的消息,都能够产生长度很短的而且长度固定的散列值,如果消息越长生成的散列值也越长的话,就不好用了。
计算散列值所花费的时间必须要短。尽管消息越长,计算散列值的时间也会越长,但如果不能再现实的时间内完成计算就没有意义了。
为了能够确认完整性,消息中哪怕只有1bit 的改变,也必须有很高的概率产生不同的散列值。两个不同的消息产生同一个散列值的情况称为碰撞(有的地方称为哈希冲突)。如果要将单向散列函数用于完整性的检查,则需要确保在事实上不可能被人为地发现碰撞。事实证明,碰撞的概率可以忽略不计。
弱抗碰撞性:要找到和该消息具有相同散列值的另外一条信息是非常困难的。
强抗碰撞性:要找到散列值相同的两条不同的消息是非常困难的。
我们应该都有过下载软件下载完毕之后,发现不是自己想要的内容的经历;或者下载电影,下载到99%而下载不了的问题。这里面有很大的因素是想要下载的内容,被人篡改或者替换了。而正规的软件下载,软件提供商会提供被下载软件的散列值,用户在下载之前,可以先用单向散列函数计算软件的散列值,然后使用这个散列值与官方提供的散列值进行比较,就能知道是否是真正的需要下载的软件了。
关于 消息认证码 和 数字签名 会在后续内容进行讲解。
MD5 (MD: Message Digest,即消息摘要)是由 Rivest 在1991 年设计的单向散列函数,能够产生128bits 的散列值。MD5的抗碰撞性已经被攻破,也就是说,现在已经能够产生具备相同散列值的两条不同的消息,因此它也已经不安全了。后续又出现了很多的单向散列函数,比如: SHA-1、SHA-256、SHA-384、SHA-512(SHA: Secure Hash Algorithm)。后来最出名的是SHA-3。SHA-3 和 AES 一样,都是通过公开竞选产生的,最终认定 Keccak 为 SHA-3 标准的单向散列函数。
确认消息的机密性,我们可以使用对称加密、公钥加密;确认消息的完整性,我们可以使用单向散列函数。
但我们前面也提到了中间人攻击,之所以会出现中间人攻击,就是攻击者可以伪装成消息的发送者,而我们不能进行认证。消息认证码(Message Authentivation Code)是一种确认完整性并进行认证的技术。
消息认证码的输入包括任意长度的消息和一个发送者与接受者之间共享的密钥,它可以输出固定长度的数据,这个数据称为MAC值。单向散列函数中计算散列值时不需要密钥,相对的,消息认证码中则需要使用发送者和接受者之间共享的密钥。要计算MAC值必须持有共享密钥,没有共享密钥就无法计算MAC值,消息认证码正是利用这一性质来完成认证的。因此我们可以说:消息认证码是一种与密钥相关联的单向散列函数。
我们还是以画图的形式来分析一下消息认证码的整体流程。
消息认证码之所以工作,是因为通信双方有共享的密钥。因为只有根据共享的密钥才能够计算出正确的MAC值。从上图可以看出,消息认证码并没有保证机密性,消息是明文传递的,但是它可以保证完整性及完成认证工作。为了确保共享密钥的安全性,我们可以对共享密钥进行公钥加密或者进行 Diffie-Hellman运算(后续会讲解到)。
消息认证码虽然可以确认消息的完整性及可以认证通信的对方,但如果处理不当,有可能出现重放攻击。
重放攻击:我们举一个例子进行说明,假设有A银行及B银行,而攻击者C在A、B银行都有自己的账号。
整个过程中,C并没有破解消息认证码,而只是将A银行的正确MAC值保存下来重复利用而已。这种攻击方式称为 重放攻击(Replay Attack)。
防止重放攻击的方式有很多。
1. 对第三方证明
假设通信的双方是A和B,现在要对C进行证明。那么C必须校验MAC值,由于计算MAC值的密钥在A和B那里,所以C也就无法计算出正确的MAC值了。假设A非常信任C,将密钥值告诉C,此时C是可以计算出正确的MAC值了,但是能够提供MAC值的有A和B两者,依旧不能说明消息是A提供的还是B提供的。
2. 防止否认
由于A和B都能对消息进行消息验证码处理,所以都可以得出MAC值,如果此时C想问消息到底是谁发出的从而去追究责任,A和B完全都可以矢口否认,认定是对方发出的。因为能够计算出正确MAC值的有A和B两者,无法进行确认。
为了解决上述问题,我们可以使用数字签名来解决。
通过消息认证码,我们可以识别消息是否被篡改或者发送者身份是否被伪装,也就是可以校验消息的完整性,还可以对消息进行认证。然而,消息认证码无法防止否认。之所以无法防止否认,是因为消息认证码需要在发送者和接受者之间共享一个密钥。现在我们假设发送者和接受者之间不共享密钥,那就可以防止否认了。发送者发送消息时,使用自己的私钥生成一个签名,相对的,接受者则使用和发送者配对的公钥对签名进行认证。这就是数字签名。
生成数字签名这一行为是由消息的发送者来完成的,也叫“对消息签名”,验证数字签名这一行为一般由消息的接受者来完成,当然验证可以由任何人完成,只要认证的人持有用于认证的钥匙。这样看来,数字签名和公钥加密很像! 公钥加密中,密钥分为加密密钥和解密密钥,用加密密钥无法进行解密,解密密钥只能由需要解密的人持有。而加密密钥则是任何需要加密的人都可以持有。而数字签名就是通过将公钥密码“反过来用”而实现的。下面的表格说明了公钥密码和数字签名的使用。
私钥 | 公钥 | |
---|---|---|
公钥密码 | 接受者解密时使用 | 发送者加密时使用 |
数字签名 | 签名者生成签名时使用 | 验证者验证签名时使用 |
谁持有密钥? | 个人持有 | 只要需要,任何人都可以持有 |
我们了解了公钥加密和数字签名,公钥加密是使用公钥进行加密,私钥进行解密;而数字签名是使用私钥进行签名,公钥进行验证。假设明文是content, 加密函数是 E, 解密函数是 D, 则根据公钥加密的定义可得出 D(E(content)) = content. 而数字签名是逆运算,则有E(D(content)) = content. 正是得益于这个可逆解的数学公式,我们可以实现数字签名。下面我们看看数字签名的流程图。
数字签名过程中,很多人可以进行认证,而数字签名的私钥只有发送者自己才有。所以可以有效的防止否认。
1. 为什么可以直接对明文进行数字签名?
在【数字签名的流程图】中,我们看出消息是直接发送出去的,可以不需要加密,因为数字签名的职责是进行认证,而不是保证消息的机密性。
2. 数字签名可以被任意复制吗?
签名可以被复制。并不意味着签名就没有意义,因为签名所表达的意义是特定的签名者对特定的消息进行了签名,即便签名被复制,也并不会改变签名者和消息的内容。
3. 签名会不会被重复使用?
在获得签名之后,将相关信息提取出来。然而在数字签名中,签名和消息之间具有对应关系,消息不同,签名的内容也会不同,因此事实上是无法做到将签名提取出来重复使用的。
4. 删除签名也无法作废吗?
的确,带有数字签名的内容即便删除掉也无法作废,要作废带有数字签名的内容:1. 根据新的内容,重新创建数字签名; 2. 在消息中申明该消息的有效期(证书就是这么处理的)。
证书就是将某人或者某个组织的相关信息(包括公钥), 由认证机构(Certification Authority, CA)施加数字签名。只要看到证书,我们就可以知道认证机构认定该公钥的确属于某人或者某个组织。从基本概念可以看出,证书其实就是对公钥的认证,因此又称为公钥证书,简称证书。
在公钥加密那一节提到中间人攻击(攻击者针对发送者冒充接收者,针对接收者冒充发送者,用自己的公钥进行欺骗,最根本的原因就是发送者和接收者不能确定接收到的公钥是否真的是想要通信的对方发送过来的)。而证书就是为公钥加上数字签名,成功的解决了中间人攻击的问题。
证书的大致处理流程如下图:
证书是由认证机构颁发的,使用者需要对证书进行验证,如果证书的规范不统一,那使用起来就会非常的不方便,所以人们指定了X.509规范,现在很多应用程序都支持X.509并将其作为证书生成和交换的标准规范。X.509证书所包含的构成要素大致如下:
证书要素 | 值 |
---|---|
证书序列 | S/N: 4233232BC543333A563993B09234222A |
证书颁发者 | Issuer: CN=China History Department CA,… |
公钥所有者 | Subject: … aka: [email protected] |
SHA-3 指纹 | sha3_fpr: 32:45:3C:56:B1:09:21:D5:4C… |
MD5指纹 | md5_fpr: 34:12:B9:0A:4C:37:D9… |
证书ID | certid: 123124325454325BA4334556c566d2222a344 |
有效期(起始时间) | notBefore: 2019-05-01 00:00:00 |
有效期(结束时间) | notAfter: 2022-05-01 00:00:00 |
散列算法 | hashAlgo: 1.2.840.113549.1.1.5(shalWithRSAEncryption) |
密钥类型 | keyType: 2048 bit RSA |
密钥ID | subjectKeyId: 5234234234A5545BCCC2234D54330 |
密钥用途 | keyUsage: digitalSignature keyEncipherment |
X.509有多种常用的扩展名,常用的如下:
证书后缀名 | 说明 |
---|---|
.cer, .crt, .der | 通常是DER二进制格式的,但Base64编码后也很常见 |
.pem | (隐私增强型电子邮件)DER编码的证书再进行Base64编码的数据存放在"-----BEGIN CERTIFICATE-----“和”-----END CERTIFICATE-----"之中 |
.p7b, .p7c | –PKCS#7 SignedData structure without data, just certificate(s) orCRL(s) |
.p12 | –PKCS#12格式,包含证书的同时可能还有带密码保护的私钥 |
.pfx– PFX | PKCS#12之前的格式(通常用PKCS#12格式,比如那些由IIS产生的PFX文件 |
认证机构是对证书进行管理的人,它的主要职责:
前面两步我们已经在【基本概念】中进行了说明,现在我们看看“作废证书”。当用户的私钥丢失、被盗时,认证机构需要对证书进行作废。此外,即便私钥安然无恙,有时候也需要作废证书,例如用户从公司离职导致其失去私钥的使用权限,或者是名称变更导致和证书中记载的内容不一致等情况。
纸质证书只要撕毁就可以作废了,但是这里的证书是数字信息,即便从仓库中删除也无法作废,因为用户会保存证书的副本,但认证机构又不能入侵用户的电脑将副本作废。要作废证书,认证机构需要制作一张证书作废清单(Certificate Revocation List),简称 CRL。
假设我们手中有某个证书,该证书有合法的认证机构的签名,而且也在有效期内,但仅凭这些还不能说明该证书一定是有效的,还需要查询认证机构最新的CRL,并确定证书是否有效。一般来说,这个检查不是由用户自身来完成的,而是应该由处理证书的软件来完成。
在进行对称加密的时候,我们需要进行密钥配送,主要有两种方案:1. 密钥配送中心; 2. 公钥加密。
使用密钥配送中进行密钥配送,当时进行了详细的流程说明,它不仅流程复杂,还需要经过配送中心进行中转。使用公钥加密进行配送是一种不错的选择,发送者使用接收者的公钥加密对称加密的密钥发送给接收者,接收者再使用自己的私钥解密,便可以得到对称加密的密钥。这一节,介绍另一种可以解决对称加密密钥配送的方案:Diffie-Hellman。
Diffie-Hellman密钥交换是一种算法,通信双方仅通过交换一些可以公开的信息就能够生成出共享的秘密数字,而这一秘密数字就可以被用作对称密码的密钥(IPsec中就使用了经过改良的Diffie-Hellman密钥交换)。虽然这种方法的名字叫做“密钥交换”,但实际上双方并没有真正交换密钥,而是通过计算生成出了一个相同的共享密钥。因此,这种方法也称为Diffie-Hellman密钥协商(Diffie-Hellman key agreement)。交换的示意图大致如下:
通过上述的操作,通信双方得到的对称加密的密钥就是 GAB mod P,然后就可以使用这个密钥加密传输通信内容了。
上述操作步骤中,能够截获到的信息是P、G、GA mod P、GB mod P;但是根据这四个值,计算出GAB mod P就非常困难了,其实这里求解的难度就是已知P、G、GA mod P 求解A的值,因为这又是离散对数求解的问题,而我们在前面讲解过的RSA加密算法也是使用的这个数学理论知识,由于有效的计算离散对数的方案至今没有被发现,所以他们是安全的。
回顾一下,我们讲解了:编码知识、对称加密、公钥加密、单向散列函数、消息认证码、数字签名、证书、Diffie-Hellman。而实际的软件开发过程中,使用这些知识点的地方太多了,这里我们选取 “git”、“bitcoin”、“HTTPS” 进行说明。
1. SSH key
我们在实际的开发过程中,几乎都在使用git进行版本管理控制。刚开始使用git的时候,每次的操作步骤都需要输入账号及密码,那是因为需要进行身份的确认。但是每次操作如果都输入账号及密码,那就太麻烦了,我们有必要使用SSH key进行身份的认证。处理的大致步骤如下:
ssh-keygen -t rsa -b 4096 -C "[email protected]"
> Generating public/private rsa key pair.
> Enter a file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]
> Enter passphrase (empty for no passphrase): [Type a passphrase]
> Enter same passphrase again: [Type passphrase again]
通过以上步骤,其实就已经根据用户名及密码,产生了对应的 “密钥对”,然后可以查看具体产生的 “密钥对”,结果如下图:
其中 “id_rsa” 是私钥,“id_rsa.pub” 是公钥,私钥是自己保存在电脑里面的,不需要也不能透露给任何人,而公钥是要发送给git服务器的。产生的公钥的大致内容如下:
ssh-rsa AAAAB3NzaC1yc2EBBBBBAQABAAABAQDIGOrS2jY4VpBFcnyPDu6RKG56j2Rdc0AnA91v85PAnLqZnYNXdffcNclvSd9s7YXOdj3IGwFXdQM2hLOZGCSuJ35x3mXzDoslQybM9LLAoJYCyPan5mwvS+q9qLa+PouAe4AZbTMAByYb3DtoDkjMW//nkCNgtMgNb9IP2nT23ifk1l0qMR2aQZtqrWO/Q0UeKNnZUNz8hV65OMa7N55n+UhvvMs705fTRQK7Y8cTcNcfVqc7CJp3tTQpJ8g+VA7mpVaKMQKzuib/ILWO9KZaJbfJtRR3clcao2NIwObGT5/Ad0Cj9QE3cBy9sbCtSKPgXPCDqR5hK3dr254JZA/X [email protected]
2. Bitcoin
Bitcoin 中文称为比特币,是一种虚拟货币。比特币可以脱离物理介质,仅通过互联网就可以流通。比特币的实现基于一个自称 中本哲史 的不明身份的人所发表的一篇论文。
比特币交易是在比特币地址之间完成的。假设A要从B商店购买商品,并通过比特币来支付,流程如下:
区块链:区块链就是保存比特币全部交易记录的公共账簿。
全世界使用比特币进行的所有交易都被记录在这一本公共账簿中。顾名思义,区块链就是将交易以区块为单位组织起来的。
一个区块是由若干条交易以及一个区块头所组成的,区块头中保存了“上一个区块头的散列值”(其中区块头2中保存的散列值H2就是根据它前面的区块1的区块头1计算出来的);区块头中还保存着 “本区块所有交易的整体散列值”(其中区块头2的散列值T2就是根据区块2中记录的所有交易数据计算出来的散列值)。
3. HTTPS
SSL/TLS 中综合运用了对称密码、消息认证码、公钥密码、数字签名、伪随机数生成器等密码技术,严格来说,SSL(Secure Socket Layer) 与 TLS(Transport Layer Security) 是不同的,TLS相当于是SSL的后续版本,TLS是IETF在SSL 3.0的基础上设计的协议,相当于SSL 3.1。
SSL/TLS 可以承载 HTTP 通信,发送邮件使用的SMTP和接收邮件使用的POP3都可以使用SSL/TLS进行承载。在这样的情况下,SSL/TLS就可以对收发的邮件进行保护。
SSL/TLS 提供了一种密码通信的框架,这意味着 SSL/TLS 中使用的对称密码、公钥密码、数字签名、单向散列函数等技术,都是可以像零件一样进行替换的。也就是说,如果发现现在使用的某个密码技术存在弱点,那么只要将这一部分进行替换就可以了。尽管如此,也并不是说所有的组件都可以自由选择。由于实际进行对话的客户端和服务端必须使用相同的密码技术才能进行通信,因此如果选择过于自由,就难以确保整体的兼容性。为此,SSL/TLS 就像事先搭配好的盒饭一样,规定了一些密码技术的 “推荐套餐”,称之为 密码套件。
下面我们看看最为复杂的握手协议 ,它的大致流程图如下:
从上述可以看出,握手协议中使用了密码技术中的对称密码、消息认证码、公钥密码、数字签名、伪随机数生成器等密码技术,充分的保证了数据传出的安全性及完整性。
至此,关于密码技术的这篇博客就讲解完毕了,如果大家感兴趣的话,欢迎点赞及转发~~~