密码学入门
原创:打码日记(微信公众号ID:codelogs),欢迎分享,转载请保留出处。
简介
在信息安全领域,一般会遇到"窃听"、"篡改"、"伪装"、"否认"这些威胁,而密码学家们提供了相应的密码学算法来解决这些问题,如下:
- 窃听:攻击者可以在网络上安置了一个路由器,侦听所有经过的数据包,这样数据就被泄密了,密码学提供了对称密码与公钥密码算法对数据加密,保证机密性。
- 篡改:攻击者对经过的数据包进行修改,使得接收方获取到错误的信息,密码学提供了单向散列函数生成“数据指纹”,保证数据完整性。
- 伪装:攻击者伪装成发送方来发送数据,使得接收方获取到虚假的信息,密码学提供消息认证码生成"认证码",保证数据来源的正确性。
- 否认:发送方本身是攻击者,发送了恶意请求后,谎称自己没有发此请求,密码学提供了数字签名算法,使其不可否认。
像对称加密、公钥加密、消息散列、消息认证码、数字签名这些算法,也被称为密码学家的工具箱。
加密分类
现如今的加密算法,主要分为两大类,一类是对称加密,而另一类是非对称加密,而对称加密在实现方式上又可以分为两类,一类是分组加密算法,另一类是流加密算法。
分组加密
分组加密(block cipher),每次加密或解密数据中特定长度的一小块,前一块处理完了再处理下一块,直到所有数据都处理完,这里的“一块”就称为分组,而一个分组的bit数就是分组长度。
常见的分组加密算法有DES与AES,比如DES的分组长度是64bit,而AES的分组长度可以是128bit或192bit或256bit,由于DES或AES的算法过程比较复杂,暂不介绍,但我们有必要了解一下它们经常使用的基础运算XOR。
XOR加密
在位运算中,一般有AND(与)、OR(或)、NOT(非)运算,然而还有一种位运算也非常常用,即XOR(异或),它的运算规则如下:
简单来说,就是两边相同结果是0,两边不同结果是1,所以才称为“异或”嘛!
而如果将XOR运算应用在多bit的数据上,我们会发现XOR有非常好的自反特性,如下:
- 数据A与B异或之后,变成了一个新数据
- 而当我们将新数据再与B异或运算后,发现它又变成了A!
- 从而,我们惊奇的发现,如果把B看成是密钥的话,XOR可以用来实现加密、解密算法,而加密、解密过程,都只需要数据与密钥做XOR运算即可。
Linux命令做XOR加密
$ pip install xortool
# 明文文件,内容是hello
$ echo -n hello > plain.txt
$ xxd plain.txt
00000000: 6865 6c6c 6f hello
# 用xor算法加密,密码为pass,生成encrypt.bin
$ cat plain.txt | xortool-xor -n -r pwd -f - > encrypt.bin
$ xxd encrypt.bin
00000000: 1812 081c 18 .....
# 用XOR算法解密,可还原成明文
$ xortool-xor -n -r pwd -f encrypt.bin
hello
虽然对称加密算法的核心是XOR,但如果加密算法只使用XOR的话,其加密的强度并不够高,很容易被破译或泄密。
下面会以一个故事展开密码学的各种算法概念及用途,背景如下:
男主:小李 ,女主:小红,男配:大李,小李的哥哥,女配:小美,小红的闺蜜
小李与小红是同班同学,专业计算机,小李对小红倾慕已久!
恋爱日记:小李写情书
为了表达爱慕之情,小李写了一封情书,写了好长时间,决定发给小红,但又不想被网络上的其他人看到!如下:
由于小李最近学习了XOR加密算法,于是使用XOR加密了情书,如下:
cat plain.txt | xortool-xor -n -r pwd -f - > encrypt.bin
可以看到,加密后的数据都是乱码了,效果很好!
小李喜出望外,将自己的成果展示给哥哥大李看!大李指出,这样的加密算法强度不够高,密码学家们可以轻易破解,开始给小李展示破译过程!
# 统计原文词频
$ grep -oP . plain.txt |sort|uniq -c|sort -nr|head
66 ,
37 你
31 的
25 我
15 是
15 不
13 一
12 想
10 会
9 都
# 统计密文词频
$ xxd -g3 -c3 -ps encrypt.bin |sort|uniq -c|sort -nr|head
66 9fcbe8
37 94cac4
31 97ede0
25 96fff5
15 96efcb
15 94cfe9
13 94cfe4
12 96f4d7
10 94cbfe
9 99f4d9
# 按词频构造替换表
$ paste <(xxd -g3 -c3 -ps encrypt.bin |sort|uniq -c|sort -nr|head|awk '{print $2}'|sed -E 's/../\\x&/g') <(grep -oP . plain.txt |sort|uniq -c|sort -nr|head|awk '{print $2}')
\x9f\xcb\xe8 ,
\x94\xca\xc4 你
\x97\xed\xe0 的
\x96\xff\xf5 我
\x96\xef\xcb 是
\x94\xcf\xe9 不
\x94\xcf\xe4 一
\x96\xf4\xd7 想
\x94\xcb\xfe 会
\x99\xf4\xd9 都
# 变成sed替换脚本
$ paste <(xxd -g3 -c3 -ps encrypt.bin |sort|uniq -c|sort -nr|head|awk '{print $2}'|sed -E 's/../\\x&/g') <(grep -oP . plain.txt |sort|uniq -c|sort -nr|head|awk '{print $2}') | awk '{printf "s/%s/%s/g; ",$1,$2}'
s/\x9f\xcb\xe8/,/g; s/\x94\xca\xc4/你/g; s/\x97\xed\xe0/的/g; s/\x96\xff\xf5/我/g; s/\x96\xef\xcb/是/g; s/\x94\xcf\xe9/不/g; s/\x94\xcf\xe4/一/g; s/\x96\xf4\xd7/想/g; s/\x94\xcb\xfe/会/g; s/\x99\xf4\xd9/都/g;
使用sed将密文按照替换表进行替换,如下:
可以发现,一部分信息被还原回来了!
大李说,虽然XOR使用密码对数据每字节进行异或后,实现了加密,但它相当于是一种替换表,每组确定的明文被替换为确定的密文,然而人类的文章大多是有统计特征的,比如在中文里"你","我"之类的字出现频率就很高,黑客收集了大量人类文字且统计出字频表,因而只要将密文中出现频率高的密文替换为字频表中的明文,然后连懵带猜就可以破译出密文信息了。
为了保守小李的秘密,大李帮小李使用AES算法将情书加密,然后发给了小红...
恋爱日记:小红回应心意
小红收到情书后,非常感动,为表达自己的心意,小红自拍了一张照片,修了好长时间,想发给小李,但同时又不想被其他人看到!
小红也了解到XOR可以进行加密,于是使用XOR算法对照片进行了加密,如下:
# bmp图片前54字节是图片元数据,这部分要保持不变
(head -c 54 image.bmp;tail -c +55 image.bmp| xortool-xor -n -r password -f -) > image2.bmp
当加密完成后,小红打开照片,发现照片确实不一样了,但还是能看出自己的大致模样。
小红心想,不行,加密算法还得再改改,这样熟人还是能认出我来!为了解决这个问题,小红同样打算使用密码学界广为流传的AES算法试试!
AES-ECB加密
小红找到了如下的代码,然后对图片加了密,如下:
(head -c 54 image.bmp;tail -c +55 image.bmp| openssl enc -aes-128-ecb -e -in - -out - -k password ) > image3.bmp
可以看到,AES算法很优秀,加密后的图片,已经完全看不清模样了,只能看到大致轮廓!
虽然这张照片已完全认不出是小红了,但小红心里还是有点疙瘩,这图片还是有点暴露了我的身材啊!
AES-CBC加密
经过一段时间搜索,小红发现AES还有其它的加密模式,如CBC,于是使用CBC模式试了下,如下:
(head -c 54 image.bmp;tail -c +55 image.bmp| openssl enc -aes-128-cbc -e -in - -out - -k password -iv 0102030405060708) > image4.bmp
太棒了!小红差点叫出声来,加密后的照片像收不到信号的黑白电视机一样,小红自已都不认识自已了,小红满意的笑了笑,将照片发给了小李。
加密模式
如上过程一样,为了使对称加密算法更可靠,密码专家们玩出了一些新花样,我们称其为加密模式,常见的加密模式有ECB、CBC、CFB、CTR、OFB等,这里仅挑选ECB与CBC介绍一下,如下:
ECB模式
ECB模式的全称是电子密码本模式(Electronic CodeBook),在ECB模式中,每个明文分组加密后,直接作为密文分组输出,如下:
这种模式其实和上面的XOR加密算法一样,每一小块数据,都使用相同的密钥和方式进行加密,所以这也解释了为啥图片加密后还能看到轮廓,因为密钥在每一小块上的扰乱效果是整齐划一的,当图片很大时,从宏观上会透出少量信息出来。
一般来说,现在已不建议再使用ECB模式加密数据了。
CBC模式
CBC模式全称是密文分组链接模式(Cipher Block Chaining),之所以叫这个名字,是因为密文分组像链条一样相互连接在一起。
可以发现,CBC模式很巧妙,它将前一组加密产生的密文分组,参与到下一组的加密过程中去了,用前面的数据扰乱后面的数据!
实际上分组加密的过程,可以想象成在图片上撒沙,其中图片就是等待加密的数据,而密钥则是沙。
对于ECB模式,就是每个区域撒上相同分布的沙,而对于CBC模式,则是每次撒的沙会变花样。
初始化向量
可以发现,在使用CBC模式时,需要一个初始化向量IV,其实它就是一个分组长度的随机bit序列,用来增强第一个分组的加密效果,那它有什么作用呢?
我们有时候也称初始化向量为盐(Salt),它的作用就像盐一样,扰乱加密后的数据,假如你使用的128bit密钥很简单,这时明文的前128bit(第一个分组)加密效果就会很差,这个分组很容易被破译,而有了初始化向量后,由于初始化向量是随机的,这会导致第一个分组也会被干扰地面目全非。
使用初始化向量还有一个好处,二次加密过程,就算是使用相同的密钥,加密出来的密文也不一样。
有个疑问是,如果解密方没有初始化向量,根本无法解密啊!
是的,所以初始化向量需要告诉解密方,一种方法是每一次加密时生成初始化向量,并将初始化向量拼接在密文开头,是的,初始化向量并不是密钥,没有保密需求,直接附在密文中也没有关系。
填充模式
分组加密算法中还有一部分就是填充算法,如NoPadding, PKCS#5, PKCS#7, ISO 10126, ANSI X9.23和ZerosPadding等。
为什么会需要填充算法呢?这是因为分组加密算法是一组一组加密的,比如分组大小是128bit的话,如果明文数据最后一组不足128bit,该怎么办呢?这时就需要填充算法了,而PKCS#7算法是目前使用得最广泛的填充算法了。
所以,在Java中要使用AES对数据进行加密时,应传递的算法名称为AES/CBC/PKCS5Padding
,其中AES是算法,CBC是加密模式,而PKCS5Padding
就是填充模式,而PKCS5Padding
实际上就是blockSize=8bytes
的PKCS#7
。
pkcs5与pkcs7区别:
流加密
流加密(stream cipher),是对数据流进行连续处理的一类密码算法。流密码中一般以1bit、8bit或32bit等为单位进行加密和解密。
常见的流加密算法有RC4、A5等,虽然它们也是对称加密算法的一类,但目前来说,分组加密算法使用得更为广泛一些,因此不做过多介绍。
非对称加密
非对称加密,又称公钥加密,使用非对称加密算法,需要生成一对密钥,公钥与私钥,然后加密方使用公钥进行加密,而解密方使用私钥进行解密。
- 私钥:是需要自己保留的私密密钥,不能让别人知道的。
- 公钥:可以公开给大众使用,以使他们可以和自己通信。
就像客户端与服务器模式一样,多个客户端拥有公钥,客户端使用公钥加密数据后发送给服务端,而加密后的数据只有持有私钥的服务器才可以解密出来。
注:实际上也可以使用私钥来加密,使用公钥来解密,后面介绍的数字签名技术,会使用这种方法。
RSA
RSA是使用最广泛的公钥算法,在1977年由罗纳德·李维斯特(Ron Rivest)、阿迪·萨莫尔(Adi Shamir)和伦纳德·阿德曼(Leonard Adleman)一起提出的。当时他们三人都在麻省理工学院工作。RSA就是他们三人姓氏开头字母拼在一起组成。
RAS实际加解密过程很简单,都是对数据进行乘方运算后取模,如下:
加密:密文 = 明文 ^ E % N,其中E、N就是两个数字
解密:明文 = 密文 ^ D % N,其中D、N就是两个数字
其中(E,N)组合在一起为公钥,(D,N)组合在一起为私钥,如下,假设E=5,N=323,D=29,可以发现234经过RSA加密后变成81、RSA解密后还原成了234:
$ bc
bc 1.07.1
Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
This is free software with ABSOLUTELY NO WARRANTY.
For details type `warranty'.
234^5%323
81
81^29%323
234
当然E、N、D不是随便得来的数字,它们是根据一系列数学过程计算而来的,有着隐含的数学关系,因此RSA还设计了一套密钥对生成算法,用以生成E、N、D这3个数字。
关于密钥对生成细节,以及RSA加解密在数学上的证明,这里不再赘述,可以参考阮一峰的解析:
https://www.ruanyifeng.com/blog/2013/06/rsa_algorithm_part_one.html
https://www.ruanyifeng.com/blog/2013/07/rsa_algorithm_part_two.html
非对称加密算法,除了RSA以外,还有如ElGamal、Rabin、ECC等,一般来说,非对称加密算法都是基于数学难题来实现的,比如RSA基于大数质因数分解难题。
而且非对称加密算法还有一个特点,就是加密后的数据,自己都无法解密,只有持另一个密钥的人才能解密。
模运算
RSA的数学原理比较复杂,但我们可以简单的认为,RSA加密后的密文,之所以能够解密,主要贡献是模运算,可以通过简单的同余定理来理解,如下:
同余:如果 p , c 对 m 求模后得到的余数相同,则称模m与p、c同余。
在同余运算规则中,有一个同余式相加运算,如下:
(A+B)%C = (A%C + B%C)%C
假设C=256(1字节大小),利用此规则,可以得到如下演算过程:
- 将x看作A,127+129作为整体看作B,得到:
(x + 127 + 129) % 256 = (x % 256 + (127 + 129) % 256) % 256 = x % 256 - 将x+127作为整体看作A,将129看作B,得到:
(x + 127 + 129) % 256 = ((x + 127 ) % 256 + 129 % 256) % 256 = ((x + 127 ) % 256 + 129) % 256 - 由1, 2可得到:
x % 256 = ((x + 127 ) % 256 + 129) % 256 - 当x小于256时,可得到:
x % 256 = x,所以 x = ((x + 127 ) % 256 + 129) % 256 - 因此,可得到一个非对称加密算法如下:
加密:(x + 127) % 256 = S (密文)
解密:(S + 129) % 256 = ((x + 127) % 256 + 129) % 256 = x (明文)
其中,公钥是(127,256),私钥是(129, 256),这个加解密过程看起来是不是和RSA很像?只不过RSA用的是模幂运算,而这里使用的是简单的相加求模运算!
可以把基于模运算的加解密,看作是在转圈圈,起始明文在如下位置:
经过加密之后,跑到快半圈,到了160(密文)的位置,如下:
再经过解密之后,跑完了1整圈,又回到了33(明文)的位置,如下:
当然,这个非对称加密算法并不安全,如下:
- 这个算法只跑1圈就回到明文了,很容易根据密文规律推算出明文,而RSA算法加解密都是用的模幂运算,会跑很多圈,所以破解难度很大,数学上称为离散对数难题。
- 这个算法公钥与私钥之和就是一圈,根据公钥很容易推算私钥,但RSA算法公钥与私钥的隐含数学关系更复杂,要想通过公钥推算私钥,需要解决大数质因数分解难题。
Linux工具命令
# 生成私钥与公钥
openssl genrsa -out rsa_private_key.pem 2048
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
私钥与公钥是由数字E、N、D组成的,如下可查看私钥、公钥文件中这些数字的值:
# 读取私钥信息
$ openssl rsa -in rsa_private_key.pem -text -noout
RSA Private-Key: (2048 bit, 2 primes)
modulus:
00:d1:ca:47:3a:c5:34:b1:2f:4e:f0:a7:29:0d:99:
de:51:89:ae:1c:1f:81:22:53:7c:fe:c1:68:62:80:
...
publicExponent: 65537 (0x10001)
privateExponent:
00:cb:f3:8b:d5:fd:dc:61:19:2d:f4:55:7e:5a:c3:
a8:d7:ba:32:f3:12:49:b7:76:55:01:52:43:c9:e7:
...
prime1:
00:f3:e7:8f:7a:00:fc:bf:72:12:8f:c1:85:81:4b:
75:a8:88:7c:5e:a6:3b:9e:d1:3c:02:53:e0:2e:f2:
...
prime2:
00:dc:31:a0:2c:da:df:c5:1e:e3:20:ca:44:1b:9e:
33:2a:69:8d:2d:7d:a4:d6:7d:eb:c1:75:cb:1f:d0:
...
exponent1:
00:a1:e0:45:b1:4b:86:73:e9:59:b8:5f:50:24:07:
d9:07:09:ce:c1:62:c2:9f:1d:6f:1e:7c:5c:85:cc:
...
exponent2:
6e:70:f9:9c:e5:df:0c:b8:b4:45:13:0e:5c:27:da:
13:f0:c3:1d:c9:02:2f:8f:12:fb:82:c0:71:e1:b9:
...
coefficient:
5e:94:e2:3f:86:97:c4:24:8d:4b:e7:e9:ed:10:bc:
e3:d5:79:3c:f8:bd:c5:33:5f:52:14:34:a0:72:83:
...
# 读取公钥信息
$ openssl rsa -pubin -in rsa_public_key.pem -text -noout
RSA Public-Key: (2048 bit)
Modulus:
00:d1:ca:47:3a:c5:34:b1:2f:4e:f0:a7:29:0d:99:
de:51:89:ae:1c:1f:81:22:53:7c:fe:c1:68:62:80:
...
Exponent: 65537 (0x10001)
使用公私钥加解密,如下:
# 加密
$ echo -n hello | openssl rsautl -encrypt -in - -inkey rsa_public_key.pem -pubin -out encrypt.bin
# 解密
$ openssl rsautl -decrypt -in encrypt.bin -inkey rsa_private_key.pem
hello
加密算法总结:
- 对称加密算法由于大多数操作都是类似XOR的位运算,加密速度很快,但像客户端-服务器这种模式,需要给每个客户端分配一个对称密钥,密钥管理难度较大。
- 非对称加密算法可以将公钥告知给所有客户端,服务端使用私钥解密,能很好的应对客户端-服务器模式,但由于需要做数学运算,加密速度较慢。
恋爱日记:提升沟通效率
小李与小红了解到了对称与非对称加密算法的优缺点,决定结合使用两种密码学算法,如下:
- 小红开始时随机生成一个对称密钥,然后使用公钥算法加密,将加密后的对称密钥发送给小李。
- 小李使用私钥将对称密钥解密出来。
- 然后小红和小李就使用对称密钥加解密数据,以完成信息互通。
- 并且,小红和小李决定每隔一天,重新生成一个新的对称密钥。
混合密码系统
嗯,他俩这办法还真不错,这种结合各种密码算法优缺点的方法,在密码学里叫混合密码系统,像SSL/TLS就是典型的混合密码系统,看看SSL/TLS中的一个密码套件TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
,它混合使用了多种密码算法,包括我们后面会介绍的ECDHE
与SHA256
。
密钥轮换
小李与小红每隔一天就更换一次对称密钥,这种定期更改密钥的方式叫密钥轮换,密钥轮换在机密通信场景也经常使用,这样就算某个密钥被泄露,也不至于所有加密数据都失去保护。
然而当大李了解到他们的这个方案后,笑了笑说:你们这个方案不满足前向安全性啊!
前向安全性
简而言之,前向安全性就是,当你的主密钥被泄露之后,在泄露时间点之前通信的数据依然是安全的,即使攻击者在网络中将你们之前的通信数据全都监听保存下来了。
像小李与小红其实发明了一种最简单的密钥协商方案,这种方案是小红使用RSA公钥将对称密钥加密后,直接通过网络发送给对方,但如果小李的RSA私钥不小心泄露了,那么他们之前每次协商的对称密钥都能被解密出来,从而通信数据也会被解密出来!
所以,这种通过加密协商密钥的方案,就不满足前向安全性。
密钥协商算法
因而,密码学家们发明了一类非常巧妙的密码学算法,不需要在网络中直接发送密钥,通信双方就能计算出同样的密钥出来,这类算法统称为密钥协商算法,如DH与ECDH。
DH(Diffie-Hellman)算法原理如下:
假设Ali和Bob需要互相通信并共享密钥
- Ali先给Bob一个明文共享参数P,G,此信息可以被任何人识别
- Ali自己生成一个随机数A(Ali的私钥) ,并不将A告诉包括Bob在内的任何人
- Bob自己生成一个随机数B(Bob的私钥) ,并不将B告诉包括Ali在内的任何人
- Ali计算(G^A % P),并传送给Bob
- Bob计算(G^B % P),并传送给Ali,通过计算 (G^A % P)^B % P = G^(A*B) % P = S 得到共享密钥
- Ali同样通过计算 (G^B % P)^A % P = G^(A*B) % P = S 得到共享密钥
DH算法安全性基于离散对数难题,当P是一个非常大的质数时,Ya=G^A % P通过Ya很难推算出A。
而ECDH是结合ECC与DH的密钥协商算法,其安全性基于椭圆曲线上的离散对数难题。
恋爱日记:残缺的照片
本来小红与闺蜜小美是形影不离的,但现在小红很少和她玩了,并且小红每天在她面前炫耀男友,惹得小美醋心大发,心中不乐,决定棒打鸳鸯,于是她隐藏在网络里,将小红发给小李的密文胡乱修改后,再转发给小李,如下:
# 小美将图片中第90000个分组(16字节)后的160000字节,篡改成随机字节
(head -c $((54+16*90000)) image4.bmp; head -c 160000 /dev/urandom; tail -c +$((54+16*90000+160000+1)) image4.bmp) > image5.bmp
然后,小李解密图片,发现自己收到的图片被解密后,女友照片中有些区域乱七八糟的,如下:
(head -c 54 image5.bmp;tail -c +55 image5.bmp | openssl enc -aes-128-cbc -d -in - -out - -k password -iv 0102030405060708) > image6.bmp
小李马上和大李沟通了此事,大李觉得,这应该是有人在网络中篡改了密文导致!
是的,AES算法虽然能保证机密性,但无法防止别人篡改密文,要想解决这个问题,有两种办法,如下:
- 使用认证加密算法可以防篡改,如AES-GCM算法,这类算法统称为AEAD算法,这里暂不做过多讨论。
- 使用单向散列算法,如MD5、SHA1、SHA256等,这是我们接下来会介绍的...
单向散列算法
单向散列算法,有时也称为(密码学)哈希算法,主要作用是生成数据的一个散列值,也称消息摘要或“指纹”,用于验证数据在传输过程中是否发生了篡改,常见的散列算法有MD5、SHA1、SHA256等等。
虽然像CRC32、CRC64这样的哈希算法,也能生成32位或64位的数据摘要,但由于它们不是为密码学领域而设计的,所以它们生成的数据散列值非常容易冲突碰撞,即不同数据生成的散列值相同。
单向散列算法有如下特点:
- 强抗碰撞性,哪怕原数据只改一bit,散列值也会发生很大的变化,要想找到两个散列值一样的对于人类有意义的数据,是非常困难的。
- 单向性,通过数据可以计算出散列值,但通过散列值无法反算出数据。
单向散列算法的用途:
- 由于原数据修改之后,散列值会发生改变,所以MD5等密码学散列算法可用来检测数据是否被篡改。
- 由于像MD5等密码学散列算法,它们通常都具有非常强的抗碰撞性,对于人类产生的有意义的不同数据,几乎不可能生成相同的散列值,所以MD5等算法有时也会用来生成数据的唯一标识。
Linux工具命令
在Linux中,可用如下命令使用单向散列算法:
# md5生成128位的散列字节
$ echo -n hello | md5sum
5d41402abc4b2a76b9719d911017c592 -
# sha1生成160位的散列字节
$ echo -n hello | sha1sum
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d -
# sha256生成256位的散列字节
$ echo -n hello | sha256sum
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 -
一般来说,生成的散列值长度越长,抗碰撞性越好,所以目前来说,一般推荐使用SHA256算法。
恋爱日记:我讨厌你!
了解了单向散列算法的作用后,小红与小李在后面的通信中使用了SHA256,如果小李发现图片的散列值与小红给的不对应,就让小红重发一遍即可。
很快小美也发现了这个情况,心里更加不愉快了,经过她一番思考,想成小红能生成散列值,我也可以啊,于是小美给小李发送了一条“我讨厌你”的消息,并且也发了这条消息的SHA256散列值。
小李收到这条消息后,大为震惊,不知是哪里惹小红不开心了,但与小红沟通后,发现小红根本就没有发这条消息过来,有人冒充小红给小李发消息,还带有散列值,不知怎么办的小李,又找到了大李。
大李笑了笑,你们是时候使用消息认证码技术了...
消息认证码
消息认证码(message authentication code)是一种确认完整性并进行认证的技术,取三个单词的首字母,简称为MAC.
消息认证码的输入包括任意长度的消息和一个发送者与接收者之间共享的密钥,它可以输出固定长度的数据,这个数据称为MAC值。
密码学家们发明了很多消息认证码算法,最常见有两类,一类是HMAC,全称Hash MAC,顾名思义就是基于密码学哈希算法实现的消息认证码算法,另一类是CMAC,全称Ciper MAC,顾名思义就是基于加密算法实现的消息认证码算法。
HMAC
而一般而言的HMAC算法,是指下面这样的标准HMAC算法。
HMAC算法特点是,可以随意搭配单向散列算法,如MD5、SHA1、SHA256等,比如在Java中的hmacWithSha256算法,就是HMAC搭配SHA256。
Linux工具命令
在Linux中,hmac256命令就是hmacWithSha256,如下:
$ echo -n 'hello'|hmac256 'secret'
88aab3ede8d3adf94d26ab90d3bafd4a2083070c3bcce9c014ee04a443847c0b
另外,我们常常见到开放平台指定签名算法时,会使用如下的方法:
sign = md5(appid + text + appsecret)
其实这就是自行发明了一种简单的HMAC算法。
另外,如果使用这种拼接方式实现MAC算法,最好在text两侧都拼接上东西,具体可见:哈希长度扩展攻击:
恋爱日记:我喜欢你!
小红与小李了解了消息认证码技术后,立马决定使用HMAC算法,这也很快被小美发现了,小美发现好像无法冒充小红发消息了,因为自己不知道他们使用HMAC时的密钥,苦苦思索中...
有一次,小红被撩嗨了,头脑发热给小李发一句“我喜欢你”,但随后小红又后悔了,觉得自己如此主动表达心意,后面恋爱过程会被小李拿捏的。
小李喜出望外,立马去找小红,结果小红否认这消息是她发的,小李与小红争执起来,说密钥只有你知我知,不是你发的,难道是我发的?
小红故意淘气地说,谁知道你是不是自恋狂啊!
小李伤心回到家后,与大李说了此事,大李安慰了一下小李,说是时候使用数字签名技术了...
数字签名
从上面的问题,我们可以发现,消息认证码无法防否认,原因是通信双方拥有相同的密钥,为了解决这个问题,需要发送方与接收方使用不同的密钥,发送方用一个密钥签名,接收方用另一个密钥验签,这就是数字签名技术。
而非对称加密技术就是使用不同密钥加解密的技术,刚好可以满足数字签名的需求,如下:
数字签名的生成与验签过程,和公钥加密的过程刚好是相反的,如下:
- 公钥加密的过程是,发送方使用公钥进行加密,然后接收方使用私钥进行解密。
- 公钥签名的过程是,签名方使用私钥生成签名,然后验签方使用公钥验签。
对于数字签名过程,需要注意两点:
- 所谓的签名其实就是私钥加密,而验签即是将签名值(即密文)使用公钥解密,然后与明文进行对比。
- 为了提升签名、验签的速度,在签名(加密)前给明文数据做了散列,而解密后与明文散列值做对比。
注:理论上,用公钥签名,私钥验签也是可以的,但由于在定义上公钥是公开给别人的密钥,而如果多方都可以使用公钥签名的话,就不符合签名的需求了。
Linux工具命令
# 生成私钥与公钥
openssl genrsa -out rsa_private_key.pem 2048
openssl rsa -in rsa_private_key.pem -out rsa_public_key.pem -pubout
# 签名
echo -n hello | openssl rsautl -sign -in - -inkey rsa_private_key.pem -out sign
# 验签
openssl rsautl -verify -in sign -inkey rsa_public_key.pem -pubin
为什么可以防否认?
因为私钥只有签名方才有啊,除了它,其他人都无法生成签名了,包括公钥持有方。
恋爱日记:分手吧!
在大李介绍了数字签名技术之后,小李与小红立马使用RSAWithSha256签名算法,当然这需要两人各生成一对密钥,如下:
- 小红自己生成一对公私钥后,将公钥发给小李,私钥则自己保留,谁都不告诉。
- 小李自己也生成一对公私钥后,将公钥发给小红,私钥也自己保留,谁都不告诉。
- 小红发消息时,同时使用自己私钥为消息计算一个签名发给小李,小李则拿小红公钥验签。
- 小李发消息时,也使用自己私钥为消息计算一个签名发给小红,小红则拿小李公钥验签。
看起来完美了,小红与小李都无法否认消息了!
可好景不长,小美为了破坏恋爱过程,发现了一种叫做中间人攻击的手段,如下:
中间人攻击
比如Alice(小红)与Bob(小李)通信,需要把Bob的公钥发送给Alice,但Alice与Bob之间有个Mallory(小美),Mallory将Bob发送的公钥截获后,将自己的公钥发给了Alice,而之后Alice实际在与Mallory通信,Mallory与Bob在通信,Mallory可以解密出所有通信内容!
于是,小美在小红与小李互发公钥时,将他们的公钥截获保存起来,然后将自己的公钥发给双方!于是,小美在中间可以为所欲为了,给双方发了一句“分手吧”,如下:
- 当小红用私钥给消息签名时,小美截获了消息,然后自己发一条消息“分手吧”给小李,同时用自己的私钥签名消息,并把签名也发给小李。
- 当小李收到消息后,使用“小红的公钥”验签,而这个公钥实际是小美的公钥,于是验签通过,小李笃定消息是小红发的。
- 小红也同理,收到“分手吧”消息,且笃定消息是小李发的。
好家伙,小李与小红伤心欲绝,差点真分手了!好在大李知道这个消息后,认真考察了小李与小红,觉得他们确实没有发这个消息,猜测可能发生了中间人攻击!
大李在安慰了小红与小李后,说:我来给你俩做担保,将你们的公钥变成证书吧!
证书
信任问题与PKI
上面之所以出现中间人攻击,是因为小红在收到小李的公钥时,并不知道这个公钥是不是小李的,反之亦然,那怎么证明这个公钥就是小李的呢?
- 一是需要在公钥上写上小李的名字,但小李可以在公钥上写"小李",小美也可以在公钥上写"小李",所以仅仅有名字还不够
- 二是需要在公钥上做数字签名,类似盖章一样,那谁来盖这个章?
回想一下,我们一生中会办各种证书,学生证、身份证、结婚证,房产证,为什么你出示结婚证,别人就会相信你结婚了?因为结婚证是国家发的啊,上面有国家机构盖的章呢,我们普通人有理由相信国家盖章的证书是真实的证书,毕竟它的章不好伪造,也没人敢伪造。
在计算机世界也一样,为了解决公钥认证问题,人们构建了公钥基础设施PKI和认证机构CA,比如小李在自己的公钥写上名字,然后由大李(CA认证机构)使用自己的私钥为其生成数字签名,然后将公钥、名字、数字签名打包成一个文件,这个文件就是小李的数字证书,就像由CA机构(大李)盖过章一样。
而小红在收到小李的数字证书后,她首先会使用大李(CA机构)的公钥来验证数字签名是否正确,然后又会确认证书上的名字是不是小李,数字签名与名字都正确后,取出证书中的公钥与小李通信。
这里面大李就类似于CA机构,大李可以私底下将自己的公钥给小李与小红。
那对于互联网上千千万万的网民,怎么会有CA机构的公钥的?这是因为CA机构联合了操作系统、浏览器厂商,预先将它们的公钥默认内置到了操作系统或浏览器中。
证书例子:
如下,是百度网站的证书
证书上可以看到这样的证书路径,这是一个递归的过程,大的CA机构可以给中等的CA机构的证书盖章,中等的CA机构又可以小的CA机构的证书盖章,如此下去会形成一棵树,所以我们的证书,一般都被根CA机构委托几次后的CA机构盖章的。
curl与openssl也可以看到部分证书信息,如下:
curl -v https://www.baidu.com
openssl s_client -connect www.baidu.com:443 -prexit
注:这里小红与小李双方都生成了证书,而在CS架构里,一般只需要服务端生成证书即可,由于客户端不需要提供证书,这也导致服务端收到的请求是不可信的,需要附加Cookie等认证机制并校验请求数据合法性。另外,SSL/TLS其实是可以配置成双向认证的。
CA机构职责
你可能还会有疑问,我自己生成一个公钥,然后在公钥上写上名字baidu.com,然后给CA机构签名,那不一样可以实施中间人攻击?
确实有这样的可能性,但CA机构并不是随便给证书签名的,它会让你出具各种证明材料并严格审查,只有认定你确实是baidu才会在证书上签名。
并且,只有CA机构的私钥不泄露,签名才可靠,为了保证自己的私钥不被泄露,需要对私钥进行严格的访问控制,甚至派专人值守在私钥放置处,因此,CA机构签发证书是需要收钱的,就好像国家保证你的安全需要收税一样。
恋爱日记:欢乐时光
终于,使用了证书后,小红与小李迎来了他们的欢乐时光!然而,小美在了解到了社会工程学攻击后,竟然也偷偷的与大李约会了...
密码算法建议
- 加密算法,建议使用AES,加密模式使用CBC或GCM,普通安全级别AES128足够了,但考虑到量子计算机的威胁,绝密场景建议使用AES256。
- RSA算法,要保证安全性,至少需要使用2048位的密钥长度。
- 密钥协商算法,为保证前向安全性,建议使用ECDH密钥协商算法,且密钥长度至少384位。
- 哈希认证算法,目前不建议使用MD5、SHA1,HMAC算法目前还是非常安全的。
往期内容
原来awk真是神器啊
Linux文本命令技巧(上)
Linux文本命令技巧(下)
字符编码解惑