标题:一文揭开https原理的真实面貌(简明扼要,附代码说明)
引言:https真的是非对称加吗吗,非对称性能差,网络传输流量高,它如何能做到高效?证书被窃取是否意味着数据有泄密的可能?CA机构的原理是什么…………
1、加密方式
1.1、对称加密
client 与 server共用指定的加密算法与密钥;
- 优点:加解密性能高;
- 缺点:密钥有泄露风险;
server肯定面向所有的client,所以加密算法应该是公开透明的,一旦密钥泄露被窃取,那么在这些窃取者面前,server与client之间的通信无疑于明文;
1.2、非对称加密
两把密钥(称为公钥和私钥),用公钥加密的密文只有用私钥才能解开出明文,同样用私钥加密的密文也只有用公钥才能解出明文;
- 优点:相对对称加密来说安全性高,但不是绝对安全;
密钥管理方便,一般私钥有server保管,然后给client分发公钥;
- 缺点:加解密性能差,相对对称加密,速度要慢几个数量级;
为什么不是绝对安全?
假如server向client分发公钥时,这个过程肯定是明文的,万一这个公钥被中间人劫持到了,那么它就可以劫持后续server向client传输的报文,并且解码称明文,所以非对称加密,只能单向保证client到server数据传输的安全性;
2、https加密方式
2.1、双向非对称
如上文介绍,https不可贸然采取密钥容易被窃取的对称加密,然而非对称中一组公钥私钥只能保证单向传输的安全性,那么用两组公钥私钥,是不是就能保证双向传输的安全性了。
即:client拥有一对公钥c私钥c,server也拥有一对公钥s私钥s,二者在握手初期分别交换各自公钥,然后发送报文时用对方的公钥加密,接收报文时用私钥解密。
- client与sever握手时,将自己的公钥c通过明文发给server;
- sever接收到公钥c,然后将自己的公钥s通过明文答复给client;
- client拿到公钥s后,在后续发送报文时,通过server的公钥s加密,由于非对称的原因,即使有中间人在之前窃取到公钥s,它也无法解密,server收到报文后用自己的私钥s进行解密;
- server向client答复报文时,通过client的公钥c加密,由于非对称的原因,即使有中间人在之前窃取到公钥s,它也无法解密,client收到报文后用自己的私钥c解密;
- 如此一来,在不考虑web漏洞的情况下,是可以双向保证数据的安全性了
但是:
非对称加密性能本来就差,采用两组公私钥的话,http请求的效率会被严重的拖垮,断然不可取的
2.2、非对称+对称
如上文介绍,非对称已经可以满足单向数据传输的安全性了,那么是不是可以在一次安全的单向传输过程中,将对称密钥发送给对方,后续的所有业务的加解密都通过它来完成,再不可见的情况下实现安全的对称加密;
sever拥有一对公钥s私钥s,
- client与sever握手请求后,server将自己的公钥s通过明文答复给client;
- client收到公钥s后,生成一个随机字符串,保存在本地,用公钥s加密后,发送给server,由于非对称的原因,即使有中间人在之前窃取到了公钥s,它也无法解密,sever收到随机字符串密文,解码后保存在当前会话中;
- 后续client与server之间的通信,都是由这个随机字符串完成加解密,它就是对称加密密钥;
由于只是握手建立会话时使用的非对称加密,server内部会为每一个client维护者各自的session,通过client的sessionId关联,对称密钥保存在client的本地和server对应的session中,每次请求client都会携带sessionId,所以业务数据的处理中都是对称加解密,保证了请求的性能
2.3、绝对安全?
web安全想要做到滴水不漏,是很难的。但是那些略施手段就能够攻破的方式,必须得慎重处理。试想一下这样的场景:
假如在client与server握手阶段,有个不声不响黑客proxy的出现在链路中间,client并不知情;
- client与server握手请求时,根据数据传输链路,先到proxy,proxy将握手请求透传给server;
- server将自己的公钥s按请求来源原路返回给了proxy,proxy收到公钥s后,保存下来,并替换为自己的公钥p返给了client;
- client收到公钥p后,毫无感觉,以为这就是server的公钥,正常生成随机字符串,采用公钥p加密后,按照请求链路传递给了proxy,proxy拿到随机字符串,用自己的私钥p解密保存到本地,然后用公钥s加密后传递给server;
- server收到随机字符串密文,用自己的私钥s解密保存当前会话中;
- 第一次数据包请求,client用随机字符串加密后,按照请求链路发送到了proxy,proxy用事先存储的随机字符串解密,查看后,再用随机串加密后传递给了server,同样,server->proxy->client也是一样;
- 一招经典的狸猫换太子手法完成,后续的所有数据报文传输,在proxy看来,都是明文且都能被自己任意篡改后再发出去的;
这种黑客proxy的方式称为中间人攻击,要想规避它,需要创建一个法则:client如何证明收到来自的server的公钥s,一定就是server的。好比方:我通过邮寄链路收到了你的信笺,我怎么证明这个信笺是你写的,而不是坏人的恶意源,除非信封上落款了你的身份证号。
2.4、何谓CA
server需要开启 合法安全 https的前提条件是,先要向CA机构申请专属的数字证书,数字证书里面包含server的域名信息,公钥,CA信息等。在client与server握手时,server其实是把证书返给了client,client从证书解析出公钥。
如此就可以很好的保障我怎么证明你是你的问题,只有那些合法的server才可以从CA中申请到有效的证书,而那些黑客proxy自然不能正常申请,
2.5、证书防伪
将证书原本的内容进行hash运算,然后加密,生成签名。这也是众多防篡改方案的实现逻辑(诸如:归档日志,用户信息……);
- CA机构有自己的非对称加密公钥C私钥C,先对明文内容做hash运算,然后用私钥C对hash后的值加密,就是数字签名了;
- 明文和数字签名共同组成了数字证书,然后颁发给server;
- client在与server握手阶段,拿到证书,解析出明文和数字签名;
- 每个client都有自己的CA信息库,自然也有CA的公钥C了(比如浏览器,比如自己本地),拿到证书后,用CA的公钥C解密数字签名,然后再对明文进行hash运算,这时候运算结果应该是等于解密的数字签名,表示证书未被篡改。
本地CA库:表示只要是这些机构颁发的证书,都是合法的,其余的皆为不合法(不合法形式表现在握手阶段,浏览器会弹出警告(自己搭建https client也会有证书检查的配置信息),下文会以代码形式讲述);
3、搭建https服务
3.1、自签名证书
介绍一款工具,可以快速构建自己的数字证书,便于https的测试。
mac安装
brew install mkcert
brew install nss # 如果使用Firefox浏览器的话
Linux安装
推荐拉取源码自行编译,可以把编译后的二进制包加入环境变量中(选择性);
git clone https://github.com/FiloSottile/mkcert
cd mkcert
go build -ldflags "-X main.Version=$(git describe --tags)"
使用详情简介(更多参数,参考GitHub地址的readme):
-cert-file FILE, -key-file FILE, -p12-file FILE
FILE:自定义输出路径。
-client
为客户端身份验证生成证书。
-ecdsa
使用ECDSA密钥生成证书。
-pkcs12
生成一个“.p12”PKCS #12文件,也称为“.pfx”文件,包含旧版应用程序的证书和密钥。
-csr CSR
根据提供的CSR生成证书。与冲突除-install和-cert文件之外的所有其他标志和参数。
生成证书和私钥;
[root@VM-218-88-centos /]# mkcert -key-file key.pem -cert-file cert.pem ikejcwang.vip *.ikejcwang.vip
Created a new certificate valid for the following names 📜
- "ikejcwang.vip"
- "*.ikejcwang.vip"
Reminder: X.509 wildcards only go one level deep, so this won't match a.b.ikejcwang.vip ℹ️
The certificate is at "cert.pem" and the key at "key.pem" ✅
It will expire on 15 November 2024 🗓
自签名证书的CA(很重要,下文会看到现象)
# ca路径:/root/.local/share/mkcert,可反复使用,或者重新生成
# 重新生成方式
mkcert -install
3.2、搭建server
使用上述生成的证书,创建一个简单的https服务,如下所示
const https = require('https');
const fs = require('fs');
let options = {
cert: fs.readFileSync('./cert.pem', 'utf-8'),
key: fs.readFileSync('./key.pem', 'utf-8'),
}
https.createServer(options).on("request", (req, res) => {
console.log("接收请求")
console.log("url:"+req.url)
console.log("headers:"+JSON.stringify(req.headers))
let d = ""
req.addListener("data",chunk=>{
d += chunk
})
req.addListener("end", ()=>{
console.log(d)
})
res.statusCode = 200
res.end(JSON.stringify({name: 'ikejcwang'}));
}).listen(443, "0.0.0.0")
3.3、浏览器打开
配置域名 www.ikejcwang.vip
指向127.0.0.1,然后访问https://www.ikejcwang.vip/
,https默认监听端口443,会看到惊人的一幕。
3.3.1、不被认可的CA
这就是不被client认可的CA,颁发的证书自然在握手阶段就受到了高危提示,试想,CA会给一个黑客proxy颁发证书吗?即使黑客proxy伪造了CA信息,那在这一步也会显露出来了,中间人攻击的场景被拦了下来。
3.3.2、解决办法
其实用于测试https而已,正常在合规CA机构申请的证书,是没有问题的,这里想要跳过client的安全检查,操作方式有两种,第一是手动设置不检测,在浏览器界面,空敲键盘:thisisunsafe,即可跳过,
或者展开高级选项,点击继续前往,也可跳过
如下,这是绕过检查后的响应:
手动导入CA
非必要时刻无需进行,除非是专业机构的CA信息过期了需要手动更新。将上述mkcert的CA信息导入当前机器环境中也可跳过安全检查,并且浏览器认可该https
4、结尾
综上分析来看,https在一定程度上,是能够很好的保障CS交换数据的安全性,当然如果说本机存储的随机字符串(对称加密的key)泄漏后造成数据安全的丢失,那不在https流程管辖范围之内,应该是浏览器安装了恶意插件或者本地存在木马病毒所致,属于机器网络安全层面了的。