我们现在经常听说谁谁谁密码被盗了,谁谁谁信息又被劫持了。其中有一个原因:绝大部分网站用的是http这个明文协议。你以为很安全的在password框里填了隐藏的密码,他却一字一句明明白白的写到了网络上。于是乎好多网站开始从http迁移到https(至少登录部分)。我也准备做同样的事情,因此抽时间和小伙伴tt一起研究了一下https。
刚开始看https的时候,各种头大。国内网上讲相关的资料虽然一大堆,但是大部分是相互的抄,内容多而乱,且没有把事情讲清楚。后来查阅了一些外文资料(包括rfc、wikipedia等),读了JSSE的源代码以后,基本把这个事情的来龙去脉看懂了大部分,但是涉及到很多很细节的东西还是觉得不是完全懂,如有疏漏和错误,敬请大家指正和原谅 :-)
这篇文章的目标:用尽量简单和有趣的语言,把这个复杂的东东讲述清楚。所以,接下来我打算分成三部分来聊聊我理解的Https:
1、入门篇:主要用通俗的语言讲讲Https是什么东东,以及他大体的工作方式;
2、技术篇:结合抓包工具和源代码,分析Https的通讯流程和细节;
3、理论篇:不是特别深入的聊聊一些跟Https相关的算法。
====入门篇的分割线====
What’s HTTPS?
简单的说,https就是给http带了一个安全套,即使别人拿到了信息,也不知道这个里面装的啥。客户端(包括browser、手机app等)和服务器每次发http包的时候,都对这个包加个密,让第三者看到的只是加密后的乱码(我只想对你说:你猜你猜你猜猜猜),到对端以后再解密。
这个安全套,原来是叫SSL(Secure Sockets Layer),最先是Netscape弄出来的,后来哥们儿完蛋了,就慢慢变了名字,叫TLS(Transport Layer Security Protocol)。具体的区别可以去wikipedia搜索TLS,他们之间的升级细节讲述的非常详细(这一点百度百科真的差的有点远~)。
这个安全套跑在TCP的上层,在TCP连接完成后且HTTP启动前,协商一些跟加密相关的工作,完成协商之后,就可以对要发送的http包加密/解密了。
那他到底协商了些啥呢?其实就是保证安全的几个问题:
1、服务器要证明自己是靠谱的、安全的,不然给一个假网站发加密的密文就跟裸奔没啥区别
2、服务器和客户端通讯需要的加密算法和加密密钥
就跟当年天地会和韦小宝通信一样,先要亮出身份,证明自己,然后再拿出暗语的书信。
ComeOn! How TLS works?
第一步,服务器证明自己是靠谱的。
一个哥们儿XX说他是天地会的。如果你是韦小宝,你会怎么确认他的身份呢?
其中有一种方案可能是这样的:他会说S1是他师傅,如果你知道S1并和他确认了,就ok了。如果不认识,就继续问S1的师傅S2……一直问道陈近南,只要陈近南确认了,那就可以证明他了。看起来好像设计模式里面的责任链 XX -> S1 -> S2 -> … -> ROOT
服务器证明自己也是同样的逻辑,服务器S0有一个证书,说我是谁谁谁,这个证书由上级签发机构S1核准,如果你本地有这个S1的证书,那验证一下就可以了。如果没有,就问S1的签发机构S2。直到根的签发机构。如果本地认证找到了其中任何一级的证书,就认为S0是靠谱的。否则就是不靠谱。S0 -> S1 -> S2 -> … -> Root CA
实际上非常像工商局发的营业执照,你上面有我盖的红坨坨才是靠谱的。
上图就是淘宝的认证级联关系。
这些靠谱的证书内置在操作系统、jdk等地方(百度或者谷歌上搜索“https数字证书设置”相关内容就可以看到)。
此图就是我本机证书列表的一部分。
这个就是基本逻辑,说白了,就是找一个我们都公认靠谱的人来证实你的靠谱。
第二步,协商加密算法+密钥。
加密和摘要算法有很多,常见的比如RSA、AES、DES、MD5、SHA等等。
大家把他们这样来分:
1、加密/解密算法:能加密同时能反解的,就是加解密算法。按照加解密的密钥是否一样,又分为对称和非对称算法。比如对称加密算法:AES、DES;非对称加密算法:RSA。
2、摘要算法:就是只用来做摘要、签名、验证防止被别人篡改,基本不能反解(有可能可以通过碰撞暴力破解)。比如:MD5、SHA。
那服务器和客户端接下来就协商一下,我们要用什么加密解密算法和密钥防止别人看见,用什么摘要算法,防止别人篡改。
一般来讲,对称加密算法效率会比非对称高,所以通常选择对称加密的AES较多。双方通过某种方式协商出一个密钥,后面就通过这个密钥和加密算法进行加解密。
客户端发送一个:“地振高冈,一派溪山千古秀”
服务端回复一个:“门朝大海,三河合水万年流”
整个过程大体就是这样,后面双方就开始发HTTP的加密包,对方解包得到对应的HTTP数据。
世界一下就清晰了,对吗?
No No No 其实还是很复杂滴…… 如果要想了解详细的技术内容,就让我带着你继续往下看(你敢不敢跟我来)
===技术篇的分割线===
工欲善其事,必先利其器
为了做详细的分析,我做了几个准备工作:
1、装了一个wireshark,用来抓取网络包
2、写了一个java程序,打开debug运行(java -Djavax.net.debug=all TestHttps),用来看交互细节
import java.net.URL;
import java.net.URLConnection;
public class TestHttps
{
public static void main(String[] args) throws Exception
{
final URL url = new URL("https://www.taobao.com");
final URLConnection conn = url.openConnection();
conn.connect();
}
}
3、找到openjdk源代码:http://grepcode.com/
通过前两个工作可以看到网络交互的过程和详细的数据包,第三个可以用来分析整个流程的代码。
(注:以下涉及到代码的分析,都是基于JDK8进行的,如果因为版本原因,相关函数和代码行数对接不上,请大家查找对应版本的代码)
好了,准备工作做好了,我们开始吧!
抓个包,先看看门道
先给taobao同学发个请求吧:curl https://www.taobao.com,看到整个交互过程大体是这样的(我把tcp三次握手,ACK包等无关的数据包都过滤掉了,只剩TLS相关的数据包):
上图有几个交互数据都合并到一个TCP包进行发送了,比如漂蓝的那一行(No = 49)的TCP包实际上包含了三个TLS包(Certificate、Server Key Exchange、Server Hello Done),下面分析的时候,我就把这个包展开。
Client Server
Client Hello ->
<- Server Hello
<- Certificate
<- Server Key Exchange
<- Server Hello Done
Client Key Exchange ->
Change Cipher Spec ->
Encrypted Handshake Message ->
<- Change Cipher Spec
<- Encrypted Handshake Message
Application Data ->
<- Application Data
Encrypted Alert ->
上面抓的包全部展开就是这样的效果。怎么样,是不是差不多也看了个大概?我来翻译翻译吧。
Client Server
Client Hello你好!
Server Hello嗯,你好!
Certificate我的证书给你,验证我吧
Server Key Exchange这是我给你的加密密钥相关的东东
Server Hello Done好,我说完了
Client Key Exchange这是我给你的加密密钥相关的东东
Change Cipher Spec准备转换成密文了哦
Encrypted Handshake Message%……&*4 (密文思密达)
Change Cipher Spec我也转换密文了
Encrypted Handshake Message#%&……* (密文思密达)
Application Data%&¥&%*……(HTTP密文数据)
Application Data**……&%(HTTP密文数据)
Encrypted Alert警告(实际就是说完了,拜拜~)
看起来是不是很简单呢?
这实际上就是文章一开始,我说的要解决的两个大问题:
1、认证server端的靠谱性
2、交换加密算法和密钥
具体每个包里面都发了哪些数据?server端靠谱性是如何来证明的?加密算法和密钥是怎么交换的?接下来让我一一给你道来。
具体的交互流程和代码的实现
我们就按命令逐个来分析一下。
C:Client Hello
可以看到发送了很多数据,但是最关键的几个数据:
1、TLS的版本
2、随机数:这个是用来生成最后加密密钥的影响因子之一,包含两部分:时间戳(4-Bytes)和随机数(28-Bytes)
3、session-id:用来表明一次会话,第一次建立没有。如果以前建立过,可以直接带过去。
4、加密算法套装列表:客户端支持的加密-签名算法的列表,让服务器去选择。
5、压缩算法:似乎一般都不用
6、扩展字段:比如密码交换算法的参数、请求主机的名字等等
这一段的java实现,是在sun.security.ssl.HandshakeMessage.ClientHello里面:
S:Server Hello
当服务器收到客户端的问候以后,立即做出了响应:
大体内容和客户端差不多,只是把加密算法的套装列表换成了服务器选择支持的具体算法。
通过这一步,客户端和服务器就完成了加密和签名算法的交换。这里的TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256拆分开看就是:TLS协议,用ECDH密钥交换算法交换对称加密密钥相关的参数,用RSA算法做签名,最后使用AES_128_CBC做内容的对称加密,SHA256做摘要。
具体实现在:sun.security.ssl.HandshakeMessage.ServerHello
S:Certificate
这一步很关键,是服务器给客户端展示证书的时候。