前言
(1)本文不涉及源码、底层。只是讲解大概的密码演变过程和基本概念。能让接触到相关名词的人知道这些名词是干嘛的,为什么要有它。专业人士可以当作概念梳理,非专业人士可以当作科普。
(2)本文你将了解到的概念:明文通信、无密钥密文通信、对称加密、非对称加密、信息摘要、数字签名、CA与数字证书、https、常用的密钥扩展名。
(3)本文应用实践有:用java的Keytool生成密钥,keystore格式转pem,openssl生成私钥和证书。
一、概念
明文通信
不是私密的信息可以采用明文。
无密钥密文通信
对于私密数据,如何远距离传输并且在被其他人看到的情况下,还能保证数据的私密性?
最简单想到的就是通信双方约定好一种加密方式,对要说的话进行加解密,如图:
发送方将要发送的信息进行enc加密,接收方通过dec解密。
比如我要说的话是:123。我们提前约定好我会将我发的数字每位+1,这样我发出去的就是234。你收到我发的信息后,将每一位减1就能得到真实的信息。
为了让偷听者不能从密文中恢复出明文,必须保证算法不被外人所知。如果别人知道了你的加密算法,就能轻而易举破解。这样做有很多弊端,比如:
- 如果算法泄露,则必须设计新的算法,而设计加密算法并不容易。如果同时有很多用户的使用这套算法,换一套算法就更麻烦。
- 加密解密的人总要知道算法,所以算法的保密很难做到。
- 算法被设计出来,肯定需要很多人去测试它的安全性和正确性,这样知道这个算法的人就会很多,无法保证算法不被泄露。
虽然有这些弊端,但事实上人们历来都是这么干的。
直到Auguste Kerckhoffs 在 1883年提出了一条原则:加密算法不应该是秘密,即便落入敌人之手,也不会带来不便 。这条原则被现代密码学广泛接受,并被称为柯克霍夫原则(Kerckhoff’s Principle)。后来,信息论的奠基者香农把这条原则形象地表述为:“敌人知道系统。”
通俗的说就是算法不应该是秘密,但是必须有一个秘密信息参与到算法中,才能保证信息的安全性。这个秘密信息就是密钥。
对称加密
通信双方约定好一个密钥,然后用这个密钥进行加密算法。
比如我要说的是:123。加密算法会让我发的数字每位+x,这个x具体是多少就是我们通信双方要约定好的密钥了。
这样就实现了加密算法是公开的,但是只要密钥不被盗取,信息就不会泄漏。而且如果密钥泄漏,我们可以更改密钥即可。
常见的对称加密有: DES、2DES和3DES,AES。
但是这样又出现了一个问题,就是通信双方如何约定密钥?在网络上,发的任何信息都有可能被劫持,所以对称加密的问题就是通信双发如何能拿到密钥。
非对称加密
对称加密的问题在于互联网环境下没法传递密钥,应为密钥有可能泄漏。
非对称加密有两个密钥,一个公钥一个私钥。私钥只有消息接收者自己持有。公钥加密后只有私钥能解密,私钥加密后只有公钥能解密。
用公钥加密后的信息只有私钥的持有者能解密,如果双方想要用非对称加密进行通信的话,只要双方各持有一个公钥和一个私钥。然后各自用对方的公钥加密要发的数据,就能不被其他人破解。或者只要A有一个公钥和一个私钥,然后N约定好一个对称加密的密钥,将这个密钥用公钥加密然后发给A,A收到后双方就可以用对称加密来通信。
总结上面这段就是,如果用非对称加密来通信有两种方式:一种是通信双方各持有一个公钥和私钥,然后用各自的非对称加密算法来通信。另一种是用非对称加密算法来约定对称加密算法中的密钥。之后用对称加密算法来通信。
常见的非对称加密算法有:DSA,ECC,RSA,DH
但是上面存在一个问题,通信双方都是私钥的持有者,来接收公钥加密后的信息,那么怎么保证自己发出的公钥加密信息不会被他人篡改和替换?
数字签名
要想解决自己发出的公钥信息不会被篡改和替换,只要在信息上留下别人无法修改的记号就可以了。这就是数字签名。
只要信息发送者在信息中加入用自己私钥加密的一段信息,消息接收者用消息发送者的公钥能解开那段信息,就能保证信息没有被别人替换。那么加密一段什么信息比较好呢?这就要引出消息摘要算法。
消息摘要
消息摘要不是加密算法,他是不可逆的。它是对一段信息进行处理然后得到一串定长的消息摘要,同样的信息内容得到的消息摘要是相同的,不同的信息内容得到的消息摘要是不同的。
所以一般用消息摘要可以判断信息内容是否被修改,而且应为不管消息内容有多少,得到的消息摘要都是一样长度的。
常见的信息摘要有:MD5、SHA1。
所以现在是不是能实现完美的加密通信了呢?双发各持有一个公钥和一个私钥。要发消息时,先对消息内容进行消息摘要。在对消息内容用对方的公钥加密。最后再用自己的私钥对消息摘要加密形成数字签名。这样保证接收方收到内容时,内容不会被泄漏伪造和替换。
CA数字证书
但是回到非对称加密的起点,公钥怎么公开又是个问题。如果你虽然对外公开了你的公钥。但是你把公钥发给别人的时候被别人替换成他的公钥怎么办?你可能会说,用数字签名。你刚刚才说数字签名和保证内容不被替换和修改。
但是数字签名的前提是你要通信的那个人已经拿到了你的公钥,然后你用私钥对消息签名,他才能用你的公钥来判断是不是你的私钥签的名。现在的问题是他怎么能正确的拿到你的公钥,如果你的公钥被别人替换,那么你的后续的数字签名就没有用了。
那么如下图。既然在一开始传递公钥时,小红没法自己签名。那么找一个大家的认可的第三方(CA),把小红的个人信息和公钥告诉第三方,第三方对这些内容进行数字签名(相当于盖了一个公章),形成一个数字证书。然后小明用第三方的公钥如果能解开数字签名(鉴别公章是不是假的),就说明里面的内容是得到权威认证的。而且里面的内容有你的公钥和个人信息,这样小明就得到了小红的公钥,而且这个公钥不可能是被别人替换或者修改过的。
现在问题来了,上图的第四步,小明在验证小红的证书时。需要验证数字签名确实是权威CA签的,需要用CA的公钥才能解开数字签名。那么小明怎么得到CA的公钥呢?
其实,信任是有起点的。
CA 不仅为他人生成证书,也生成自己的证书,CA 为自己生成的证书里包含了CA的公钥。
CA 的证书在电脑、手机等设备出场的时候就会预置在系统里、浏览器里。
因此,当小明验证小红的证书时,会在系统里寻找能够解开小红证书的CA 公钥,若是找到则说明小明证书的颁发机构是可信任的,既然信任了该证书,那么从证书里取出的公钥,小明也认可是小红的。
如果在本机中没找到对应的公钥,那么小明就会认为这个证书是不可信的,就会提示用户是否要继续访问。
HTTPS
https是应用层协议,它会结合传输层和应用层之前的ssl一起使用,实现加密传输。(ssh也是加密协议,通常用在客户端远程访问)。https大概流程如下:
- 客户端通过发送Client Hello报文开始SSL通信。报文中包含客户端支持的SSL的指定版本、加密组件(Cipher Suite)列表(所使用的加密算法及密钥长度等)。
注意:客户端还会附加一个随机数,这里记为A。 - 服务器可进行SSL通信时,会以Server Hello报文作为应答。和客户端一样,在报文中包含SSL版本以及加密组件。服务器的加密组件内容是从接收到的客户端加密组件内筛选出来的。
注意:这里服务器同样会附加一个随机数,发给客户端,这里记为B。 - 之后服务器发送Certificate报文。报文中包含公开密钥证书。(具体的数字签名请看证书一节)
- 最后服务器发送Server Hello Done报文通知客户端,最初阶段的SSL握手协商部分结束。
- SSL第一次握手结束后,客户端会对服务器发过来的证书进行验证,如果验证成功,解密取出证书中的公钥。(具体查看证书一节)
接着,客户端以Client Key Exchange报文作为回应。报文中包含通信加密中使用的一种被称为Pre-master secret的随机密码串。该报文使用从证书中解密获得的公钥进行加密(其实就是服务器的公钥)。 - 客户端继续发送Change Cipher Spec报文。用于告知服务端,客户端已经切换到之前协商好的加密套件(Cipher Suite)的状态,准备使用之前协商好的加密套件加密数据并传输了。
- 客户端发送Finished报文。该报文包含连接至今全部报文的整体校验值(也就是HASH值),用来供服务器校验。
- 服务器接收到客户端的请求之后,使用私钥解密报文,把Pre-master secret取出来。接着,服务器同样发送Change Cipher Spec报文。
- 服务器同样发送Finished报文,用来供客户端校验。
- 服务器和客户端的Finished报文交换完毕之后,SSL连接就算建立完成。当然,通信会受到SSL的保护。从此处开始进行应用层协议的通信,即发送HTTP请求。
- 应用层协议通信,即发送HTTP响应。
- 最后由客户端断开连接。断开连接时,发送close_notify报文。上图做了一些省略,这步之后再发送TCP FIN报文来关闭与TCP的通信。
相关文件扩展名
私钥、公钥、证书这三个相关的文件一般有以下扩展名:
常用后缀名:
证书(Certificate) - *.cer *.crt
私钥(Private Key) - *.key
证书签名请求(Certificate signing request) - *.csr
证书吊销列表(Certificate Revocation List) - .crl
X.509是常见通用的证书格式。所有的证书都符合为PublicKeyInfrastructure(PKI)制定的 ITU-T X509 国际标准。
pem(base64)和der(二进制)是证书的编码方式,以.pem结尾的文件既可以是证书也可以是私钥。(X.509是证书的内容格式,pem是编码方式)
.jks和.p12是是一种容器格式,可以同时保存多个证书和私钥。
注意,后缀名只是一种命名规范,只是让使用者能知道这个文件代表这什么。
普通的pem文件内容:
-----BEGIN PRIVATE KEY-----
ksldfjs
ksdjflsdf
...
....
sfjlskjdfljf
ksdfjlsdkf
-----END PRIVATE KEY-----
二、keytool
在java中,jdk提供了管理公钥、私钥和证书的工具keytool。
在keytool中提供了一个叫密钥库的东西用来保存多个密钥和证书,密钥库的默认格式为jks,命名一般叫xxx.store。
常用的命令如下:
(1)生成密钥并放入指定密钥库:
keytool -genkey -alias privatekeys -keysize 1024 -keystore privateKeys.store -validity 3650
-alias :别名,指定密钥对的名称。让使用者通过该名称来操作密钥。(应为一个密钥库会有多个密钥)
-keyalg :指定密钥生成算法RSA,默认DSA。DSA只能用来签名,RSA既可以用来签名也能用来加密。(签名是私钥加密,公钥解密。加密是公钥加密,私钥解密)
-keystore:指定密钥库,如果没指定默认将密钥生成在默认的一个.store文件中。密钥库扩展名可以是.store或.jks。
-keysize: 密码长度1024
-validity:有效期,3650表示10年。
(2)从密钥库中导出crt证书:
eytool -export -alias privatekeys -file certfile.cer -keystore privateKeys.store
(3)将证书导入到公钥库:
keytool -import -alias publiccert -file certfile.cer -keystore publicCerts.store
我们通常把第一步生成的称为私钥库,其中包含私钥和证书。这里生成的叫公钥库,其中只含有证书。
私钥库是服务端使用,公钥库是给客户端使用。
(4)查看密钥库信息
keytool -v -list -keystore file.keystore
(5)将jks转为p12文件
keytool -importkeystore -srckeystore x:\xxx\xxx.jks(jks文件路径) -srcstoretype JKS -deststoretype PKCS12 -destkeystore x:\xxx\xxx.p12(后缀改为p12)
三、openssl
1、格式转换
通常keytool生成的jks格式的密钥库,只用在java相关的程序中使用,例如tomcat。如果要在其他应用中使用,就需要进行格式转换。
例如nginx中使用的就是pem格式的证书和私钥,其中私钥扩展名使用.key,公钥扩展名使用.crt。当然你都取名为pem也是没问题的。
要想将jks格式的密钥库转为pem格式,需要以下步骤:
(1)keytool将jsk转为p12文件
(2)p12转pem
openssl pkcs12 -nodes -in csii.p12 -out csii.pem
应为p12文件可能包含多个私钥和证书,所以生成的pem文件中也可能包含多个私钥和证书。
我们打开转换后的pem文件,将其中-----BEGIN CERTIFICATE-----和-----END CERTIFICATE-----部分的内容复制到cert.pem中,把-----BEGIN PRIVATE KEY-----和-----END PRIVATE KEY-----部分的内容复制到pri_key.pem中。这样我们就得到了私钥文件pri_key.pem和证书文件cert.pem。
(3)如果你想得到公钥,可以使用:
openssl x509 -in cert.pem -pubkey -out pb.pem
这样就得到了公钥文件pb.pem。
2、openssl生成私钥和证书
如果你想直接生成私钥和证书,而不是通过keytool生成的密钥库进行格式转换,可以使用如下方法:
openssl genrsa -des3 -out server.key 2048 #生成私钥文件server.key
openssl req -new -key server.key -out server.csr #生成证书请求文件server.csr
cp server.key server.key.org # 备份一下 server.key
openssl rsa -in server.key.org -out server.key #生成无秘钥 server.key
openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt #通过私钥和证书请求文件生成证书