https
http 存在的问题
众所周知, http 都是明文传输, 这带来了很大的安全风险. 如果连接的 wifi 或者别的什么东西里有个不怀好意的人可以轻松读取和篡改发送和响应的内容.
比如登录时如果用的是 http 那么中间人可以直接读取用户的账号和密码, 直接盗号或者做别的事情了: 也有可能偷偷在响应的页面里加点自己的东西比如小广告之类的, 或者直接重定向到钓鱼网站.
为了避免这种情况, https 就出现了.
https 的概念
https 其实是 http+tls(tls 相当于 ssl 的升级版), 在四层模型中是在应用层上的.
https 的加密
一般加密有可逆和不可逆的, 可逆的里面按密钥又分对称的和非对称的. 在 https 里肯定时要选择可逆的算法, 问题就是对称算法和非对称算法了.
使用对称算法
使用对称算法的话, 那么客户端需要准备一个密钥用来加密和解密, 并且要把这个给密钥发给服务器. 可能会约定某几个字节是密钥, 其余的是普通数据. 对称算法计算很快, 性能消耗少.
但是这种做法存在一个问题, 密钥太容易泄露了. 中间人很容易猜出哪些是密钥, 最后跟 http 没什么区别了.
使用非对称算法
使用非对称算法客户端和服务端都需要准备一份公私钥, 公钥加密, 私钥解密. 客户端和服务端需要像一个办法交换私钥, 不然解密不了对方的数据.
这里相当于一个传递宝箱的问题, 我印象中是某一个故事里的, 有一个人 A 想向另一个人 B 寄一个贵重物品, 这两个人都有一把锁和一把钥匙, 锁只能被对应的钥匙打开. 假设这个邮递员是贪婪的, 只要有机会就会拿走贵重物品, 问怎么安全地把贵重物品交给另一个人.
A 不太可能上了锁把箱子跟钥匙一起给邮递员, 这样邮递员直接跑路了. 只能先上锁, 然后交给邮递员.
邮递员拿不到里面的东西, 只能原样交给 B.
B 也不太可能把锁和钥匙一起交给邮递员, 邮递员虽然不能立刻拿到里面的东西, 但是钥匙被拿到了. 也只能先上锁再交给邮递员, 邮递员交给 A.
A 再用自己的钥匙把自己的锁打开, 交给邮递员.
此时还有一个 B 的锁, 邮递员还是拿不到.
最后 B 用自己的钥匙打开自己的锁, 拿到了贵重物品.
私钥交换也类似, 在握手过程中, 客户端对自己的私钥进行加密, 然后发送给服务端.
服务端使用自己的公钥再一次进行加密, 然后发送给客户端.
客户端使用自己的私钥进行解密, 然后发给服务端.
服务端再用自己的私钥解密, 就得到了客户端的私钥了.
同样的, 客户端想要获取服务端的私钥也通过这种办法.
之后想要交换信息, 就只需要用自己的公钥加密, 发送过去, 对方再用拿到的私钥解密就可以了.
先不说安不安全的问题, 这种非对称算法会消耗比较多的性能. 如果服务器处理大量请求很可能会宕机. 因此还需要进行调整.
同时使用对称算法和非对称算法
对称算法快, 但是密钥和数据容易泄露: 非对称算法慢, 但是私钥和数据不容易泄露. 既然这样的话, 只要使用非对称算法传输对称算法的密钥, 就可以安全地传输密钥了, 之后再用密钥进行对称加解密.
服务端持有一份公钥和私钥. 在 tls 握手时, 客户端生成一个随机数发送给服务端(client hello), 然后服务端也生成一个随机数发送给客户端(server hello).
服务端再把自己的公钥发送过去, 客户端拿到公钥, 然后生成一个随机数, 用公钥加密发过去. 服务端再用私钥解密.
这样客户端和服务端有三个相同的随机数, 再用这三个随机数生成一份相同的密钥. 为了验证确实是一样的, 在正式发送数据前客户端会使用密钥加密一份数据发送过去进行验证.
证书
这样做既安全, 就快速了, 大概?
还是之前的两人传递贵重物品和贪婪的邮递员. 现在两人已经约定好了怎么握手怎么传输数据. 但是邮递员自己也准备了一个锁和钥匙.
A 先上锁, 然后交给邮递员. 邮递员自己也上一把锁, 然后给回 A.
由于 A 不知道上锁的到底是谁, 以为是 B, 于是解锁, 交给邮递员.
邮递员再用自己的钥匙开锁, 这样就拿到了贵重物品.
这里的问题在于 A 根本无法确定是谁上的锁, 客户端无法确定对方是谁. 因此需要一个办法确认对方真的是服务端, 于是公证人出现了.
公证人自己有一对私钥和公钥, 大家需要保存他们的公钥. 每一个想要安全通信的服务器需要带上自己的一些信息比如公钥、域名等, 交给公证人. 公证人将这些信息哈希化后, 使用私钥加密得到一个数字签名, 然后把原本的信息跟签名放在一起交给服务器. 这份数据就是证书.
服务器保存好证书, 之后在握手的时候需要把证书发给客户端, 客户端对上面的基础信息进行哈希化, 然后再用公证人的公钥对数字签名解密, 对比两份结果, 一样的话那说明对面确实是服务端.
一个公证人可能不太够, 于是出现了很多公证人, 还出现了二级、三级公证人. 保存所有公证人的公钥不太可能, 于是需要有一个证书链. 大家只保存一级公证人的公钥, 其它公证人签发证书需要写上上一级公证人. 客户端只需要顺着这个链子, 就能找到一级公证人, 然后就可以验证了.
https 就安全了吗?
这样就安全了吗?有没有可能中间人伪造一份证书呢?不太可能, 因为客户端是用保存在自己设备上的公钥进行验证的, 除非中间人拿到了公证人的私钥, 不然过不了这个验证.
不过其实还是有办法的. 不知道大家有没有用过 Fiddler, 它就可以监听 https 流量. 但是按上面的说法中间人是没办法对 https 流量加密解密的. 这里其实不算中间人攻击吗?我也不太确定. Fiddler 在解析 https 流量时, 会先要求你安装它的一个证书. 安装了它的证书之后, 那证书伪造就很简单了.
如果有一个人或者有一个程序, 能够在你的电脑安装根证书, 那么 https 的中间人攻击依旧是存在的. 所以尽量不要安装奇奇怪怪的证书或者下载奇奇怪怪的软件吧.
https 完全隐私吗?
在1.11http时, 我们尝试过解析http://www.baidu.com
的 ip 并使用 ip 发送请求.
curl --url http://39.156.66.10/ --header 'host: baidu.com'
不知道有没有试过向https://39.156.66.10/
发送请求, 如果试过的话那么应该会得到一个报错.
执行一下这个
curl --url https://39.156.66.10/ --header 'host: baidu.com' -v
可以找到这一行报错SSL: no alternative certificate subject name matches target host name '39.156.66.10'
这其实是跟 1.11http 里遇到的问题是一样的, 都是反向代理的问题. 在 tls 握手中, 客户端发送client hello
后, 服务端需要发送server hello
和证书. 在这里服务端不知道该返回什么证书, 于是返回了一个默认证书. 我们这里客户端在证书上没有找到 39.156.66.10 这个域名, 于是报错了, 证书不匹配.
这里还在 tls 握手阶段, host
字段是在 http 传输阶段发送的, 服务端没办法根据host
字段返回正确的证书.
这样的话, 服务端要怎么知道返回哪个证书?为了解决这个问题, https 要求客户端在client hello
中需要加上一个 SNI 字段, 表明服务端要返回哪个证书. 在这里我们没有设置 SNI 字段, 因此是空的, 服务端只能返回默认证书.
如果希望使用 https 根据指定 ip 访问网站的话, 可以这样:
curl https://baidu.com --resolve baidu.com:443:39.156.66.10
由于 SNI 字段的存在, 因此我们在网上冲浪时即使使用 https 也不是完全隐私的, 中间人依旧可以监视你大概在干什么, 也可以根据 SNI 禁止某些网站的访问.
https 的 tls 握手过程
可以打开 WireShark, 使用 curl 发送一个 https 请求
curl https://baidu.com
筛选栏里筛选出ip.addr == 110.242.68.66 and tls
, 这里的 110.242.68.66 是 DNS 对 baidu.com 的解析结果, 根据实际情况修改.
大概能看到这个东西
其实还有 TCP 的一些东西, 不过不太重要.
第一个数据包是 Client Hello, 带有 SNI 字段. 点进去可以看到Handshake Type
, Version
之类的字段, 还有一个随机数.
第二个数据包是 Server Hello, 里面也有一个随机数.
第三个是服务端把自己的证书发过来了, 还有一个 Server Key Exchange, 这个不是 tls 必有的, 只有服务端需要临时密钥的密钥交换算法或者证书信息不足的情况才需要. Server Hello Done 表示服务端发完所有信息.
第四个是客户端再生成一个随机数并加密, 然后发过去. Change Cipher Spec表示自己已经做好了准备, 接下来我们开始加密通话吧. Encrypted Handshake Message是把握手信息加密, 发过去让服务端验证.
第五个是服务端表示自己也准备好了.
然后就是加密通话了.
最后的Encrypted Alert是表示加密通话结束了, 警告接下来的是非加密, 不要再发信息了.