HTTPS真的安全吗?
作者 | 苏格拉凯
来源 | 凯哥的Java技术活(ID:sugelakai)
为什么HTTP不安全?
什么是安全?
我们可能需要从最开始来说。如果用户想要输入密码,然后点击登录到你的服务器,这个密码在网络上通过网关,通过路由器,通过ISP运营商,通过了很多跳,终于到达了你服务器的机房,终于,你收到了这个密码,是的你收到了,中国联通也收到了,钓鱼网站也收到了,这个密码在网络上光着屁股走了一圈!
我们来抓包试试看。
首先写一个Controller,当做服务端。
然后用Postman进行接口调用。
然后用wireshark抓包
可以看到在HTTP的环境下,抓包本地的lo网卡的数据包,能够拿到http的post的数据。
所以这时候肯定是不够安全的。
我们要防止什么?一个是防偷窥,一个是防钓鱼。
防偷窥也就是说在网络中你传递的数据要保证别人看不到,或者说看不懂。
防钓鱼也就是说你在和正确的网站进行数据交互,不是钓鱼网站。
如何是好?
安全,首先浮现在脑海里面的肯定是加密,客户端和服务端商量好一个秘钥,然后用加密后的数据进行通信。
这样当然是可以的。如果要是这样的话,那我们必须严格保密该秘钥,就像可口可乐的秘方,一定要保证这个秘钥不会被第三者知道,否则一旦泄漏的话,大家都有可能解密了。
但是这样会有个问题,这个密码怎么传输呢?如果你只有一个用户,你要保证这个秘钥只有你俩知道,那你就只有两个方法,要么,你自己给客户送过去,要么,武装押运。
那如果你有10亿用户呢?
似乎玩不下去了,只要这个秘钥在网络中传输,就一定会被别人知道。别人一旦知道了你的秘钥,就能够破解你的数据。还是不安全。
有没有一种技术,就算拿了我的钥匙,但是你也解不开我的锁。这样一种技术是否存在?
非对称加密
什么是非对称加密?
公开密钥密码学(英语:Public-key cryptography)也称非对称式密码学(英语:Asymmetric cryptography)是密码学的一种算法,它需要两个密钥,一个是公开密钥,另一个是私有密钥;公钥用作加密,私钥则用作解密。使用公钥把明文加密后所得的密文,只能用相对应的私钥才能解密并得到原本的明文,最初用来加密的公钥不能用作解密。由于加密和解密需要两个不同的密钥,故被称为非对称加密;不同于加密和解密都使用同一个密钥的对称加密。公钥可以公开,可任意向外发布;私钥不可以公开,必须由用户自行严格秘密保管,绝不透过任何途径向任何人提供,也不会透露给被信任的要通信的另一方。
以上摘自维基百科。
说白了,两个秘钥,一个叫公钥,一个叫私钥,最大的特点是公钥加密,你公钥解不开。哎,这不就是你拿了钥匙,但是你就是开不开这把锁吗?
非对称加密,使用最广泛的是RSA算法。
一切看起来似乎都很美好!但是这样真正解决了问题了吗?
我如果伪造一个公钥呢?
在一切的最开始,服务器有一对钥匙(公钥,私钥)。现在要把公钥发给客户,还是那个问题,怎么发?
是通过网络发送的话,这时候如果有恶意的技术人员,在客户端收到这个公钥之前,他伪造了一个公钥,发送给了客户。和客户通信用的是假的公私钥,和服务端通信用的是真的公私钥。
过程如下图:
这是最开始,你有一对钥匙,要把公钥发送给用户。
然后公钥发送出去,在网络中传输。被恶意的服务器抓包了,得到了你的公钥。
不巧,恶意服务器也自己生成了一对秘钥。
然后把“恶意”的公钥发给了用户。
接下来,恶意服务器就可以肆无忌惮的偷窥篡改数据了!
就像下图这样,和客户通信用恶意的公私钥,和服务器通信用真正的公私钥。
说的高大上一点,这叫中间人攻击。
崩盘了吗?
搞这么复杂,还是不能保证网络数据的安全?
铁子!怎么办!似乎又回到了最原始的办法,武装押运!
非对称加密这么好,还是不能保证数据的安全性。这问题出在哪呢?
仔细回想一下发现,思路一直是传递公钥,传递公钥。如果我不在网络中传递呢?是不是行了?
确实是,用户上网指定是要有电脑或者手机或者平板,那如果我直接把公钥嵌入进去操作系统呢?这不就不会在网络中传输了吗!
证书
要把你的证书做到到操作系统里面,不是一个简单的事,是有专门的可信机构进行认证的,当然,这里说的是正版操作系统。
那么现在你的操作系统有哪些公钥被内嵌进来了呢?
如果是Windows,可以在IE的Internet选项里面打开如下的页面:
当然里面不只有公钥,还有其他的一些信息,例如颁发者,签名信息等数据,是X509的格式标准的一种证书。关于X509的格式这里不做展开介绍,我们暂时只需要认为这是一个公钥就可以了。
内置于操作系统的就是CA机构的公钥证书,什么是CA呢,就是Certificate Authority,也就是证书颁发机构。
CA也有一对公钥和私钥。当你需要使用安全加密的通信的时候,你就把你的公钥和你的信息提交到CA机构,CA机构会对提交的信息进行Hash散列,然后用私钥进行加密,最后将这段密文,公钥,以及信息组装成X509证书。
以后在网络上就传输这个证书就行。
那么用户的电脑是怎么确定这个证书的有效性的呢?
首先会对证书进行拆解,得到服务器的信息,加密后的密文,服务器的公钥。
然后使用同样的hash进行散列,同时,使用内嵌在操作系统的公钥进行解密。如果散列值一致,说明证书有效。
安全了吗?
那我们分析上述下中间人攻击是否还能够成功?
首先,操作系统必须是正版的!并且我们是信任CA这个东西的,如果CA也不相信,那确实只能武装押运了。这就好像你信任武装押运的人员吗?如果还不信,那么就只能自己负责押运了!
如果中间人下发了一个没有被CA认证的证书,那么你的浏览器解析不了这个证书,那么就会出现提示告警,就好像这样:
如果你执意要点,那么不好意思,我也不能保证这个证书里的公钥是好人!
如果是CA认证过的,就会一切正常。会有一把锁的样子,提示你是安全的。
可以查看证书:
但是如果你不是正版的操作系统,那么就有可能有伪造的CA证书,那么安全就不能保证了。
证书怎么生成呢?
如何生成证书?
这里我们使用java自带的工具keytool来生成证书,官方文档如下
https://docs.oracle.com/javase/8/docs/technotes/tools/windows/keytool.html
1. 生成根证书
keytool genkeypair -alias root -keyalg RSA -keysize 2048 -ext bc:c -keystore root.jks -keypass password -storepass password
2. 生成CA证书
keytool genkeypair -alias ca -keyalg RSA -keysize 2048 -ext bc:c -keystore ca.jks -keypass password -storepass password
3.先导出root证书:
keytool -exportcert -rfc -keystore root.jks -alias root -storepass password > root.pem
4. 用root签名CA
keytool -keystore ca.jks -storepass password -certreq -alias ca \
| keytool -keystore root.jks -storepass password -gencert -alias root -ext bc=0 -ext san=dns:ca -rfc > ca.pem
5. 将root和ca证书导入到jks中:
keytool -keystore ca.jks -storepass password -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore ca.jks -storepass password -importcert -alias ca -file ca.pem
到这里就完成了模拟CA认证机构,接下来就模拟认证机构签名你的证书过程
1. 先生成RSA秘钥对
keytool -genkeypair -alias server -dname cn=server -validity 10000 -keyalg RSA -keysize 2048 -keystore my-keystore.jks -keypass password -storepass password
2. 用CA签名公钥
keytool -keystore my-keystore.jks -storepass password -certreq -alias server \
| keytool -keystore ca.jks -storepass password -gencert -alias ca -ext ku:c=dig,keyEnc -ext "san=dns:localhost,ip:192.1.1.18" -ext eku=sa,ca -rfc > server.pem
导入证书链到jks中
keytool -keystore my-keystore.jks -storepass password -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore my-keystore.jks -storepass password -importcert -alias ca -file ca.pem
keytool -keystore my-keystore.jks -storepass password -importcert -alias server -file server.pem
到此为主,就完成了。
为了方便,这里准备了一个shell脚本,大家可以自行测试。
#!/bin/bash
rm *.jks 2> /dev/null
rm *.pem 2> /dev/null
echo "===================================================="
echo "Creating fake third-party chain root -> ca"
echo "===================================================="
# generate private keys (for root and ca)
keytool -genkeypair -alias root -dname "cn=Local Network - Development" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore root.jks -keypass password -storepass password
keytool -genkeypair -alias ca -dname "cn=Local Network - Development" -validity 10000 -keyalg RSA -keysize 2048 -ext bc:c -keystore ca.jks -keypass password -storepass password
# generate root certificate
keytool -exportcert -rfc -keystore root.jks -alias root -storepass password > root.pem
# generate a certificate for ca signed by root (root -> ca)
keytool -keystore ca.jks -storepass password -certreq -alias ca \
| keytool -keystore root.jks -storepass password -gencert -alias root -ext bc=0 -ext san=dns:ca -rfc > ca.pem
# import ca cert chain into ca.jks
keytool -keystore ca.jks -storepass password -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore ca.jks -storepass password -importcert -alias ca -file ca.pem
echo "===================================================================="
echo "Fake third-party chain generated. Now generating my-keystore.jks ..."
echo "===================================================================="
# generate private keys (for server)
keytool -genkeypair -alias server -dname cn=server -validity 10000 -keyalg RSA -keysize 2048 -keystore my-keystore.jks -keypass password -storepass password
# generate a certificate for server signed by ca (root -> ca -> server)
keytool -keystore my-keystore.jks -storepass password -certreq -alias server \
| keytool -keystore ca.jks -storepass password -gencert -alias ca -ext ku:c=dig,keyEnc -ext "san=dns:localhost,ip:192.1.1.18" -ext eku=sa,ca -rfc > server.pem
# import server cert chain into my-keystore.jks
keytool -keystore my-keystore.jks -storepass password -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore my-keystore.jks -storepass password -importcert -alias ca -file ca.pem
keytool -keystore my-keystore.jks -storepass password -importcert -alias server -file server.pem
echo "================================================="
echo "Keystore generated. Now generating truststore ..."
echo "================================================="
# import server cert chain into my-truststore.jks
keytool -keystore my-truststore.jks -storepass password -importcert -trustcacerts -noprompt -alias root -file root.pem
keytool -keystore my-truststore.jks -storepass password -importcert -alias ca -file ca.pem
keytool -keystore my-truststore.jks -storepass password -importcert -alias server -file server.pem
试想,如果有人制作了一个root和ca证书,嵌入到操作系统,然后给你的PC安装这个操作系统,然后他做中间人攻击。这时你的浏览器没有给你提示异常,你的账号密码可就不保了。
呼吁铁子们,支持正版!
抓包测试
我们使用上面的证书,再写一个简单的测试程序,进行抓包,结果如下:
可以看到在进行TCP握手后,继续追加了TLS握手,后续的发送的数据都是加密的,只要保证公钥的合法性,这些数据是不会被非法服务器破解的,因为,公钥加密,公钥解不开!
总结
没有数据的绝对安全,安全只是相对的,因为,如果你电脑没锁屏,上个厕所的功夫被别人安装了个Root证书,完了。
数据安全,首先保持乐观积极的心态,相信世界上好人多。然后做好自己的安全保护。