http不安全?
http属于明文传输协议,在客户端和服务端数据交互过程中数据无加密,而且通讯双方没有任何认证校验,很容易遭到劫持,监听和篡改。
传输过程如下:
当老王(客户端)要给老李(服务端)送信,在信件邮寄过程中,很多快递员都经手,这就导致了大量快递员都可以查看信件内容(内容明文,所以可以查看),如果信件中包含了银行卡号和密码,或者其他账号密码的话,中间大量的快递员都可以看到,安全性较低。
http 一定不安全?
http明文传输我们无法改变,那么我们可以对传输的参数加上一定的校验,这个校验可以是客户端和服务端口头约定一定的处理算法,比如:http://baidu.com?name=老王&age=31&hash=560bec183a4da191,后边的这个hash就是字段经过简单处理成name老王age31经过md5加密的密文,当被中间人劫持了,中间人不知道我们的加密算法,它只能更改name=老李&age=100,后边的hash的值,中间人无法正确计算出来。那么服务端接收到这条消息的时候,经过计算,hash参数值和计算出来的不一致则丢弃即可。
经过了校验算法过滤,普通中间人已经无法直接进行攻击服务器了。那么有没有更安全的方式了呢?
https
要解决http的明文问题,就要引入加密身份和验证机制。
如果服务器给客户端的消息是密文,只有服务器和客户端才能读懂,就可以保证数据的保密性。同时,在交换数据之前先验证对方合法身份,就可以保证通信双方的安全,那么问题来了,服务端加密的数据客户端如何解密呢? 这时,服务器必须要把对称加密的密钥告诉客户端,客户端才可以利用对称密钥解开密文的内容,但是服务器将明文对称密钥发送给客户端,那么中间人也是可以劫持,但是如果服务器以密文方式发送给客户端,客户端又是如何解开这个密文呢?
其实在TLS握手阶段,服务器把公钥明文发送给客户端,客户端将对称密钥用公钥加密给服务端,后续的数据传输使用对称密钥加密即可。
这样子似乎不够严谨,在三次握手阶段客户端请求被中间人劫持,中间人也是可以拦截公钥和对称密钥来进行篡改数据的。那么如何解决这个问题呢?
中间人收到服务器发送给客户端的公钥,并没有发送给客户端,而是中间人自己将公告保存,然后伪造了假公钥传输给客户端,之后,客户端把对称密钥使用伪造公钥进行加密传输,中间人劫持之后解密之后可以查看对称密钥的,然后中间人将解密出来的数据使用真正的公钥进行加密传输给服务端,服务端利用私钥进行解密。至此完成通讯。
如图所示:
为了解决这类问题,出现了数字证书概念,服务器首先生成公私钥,将公钥提供给相关的机构(CA),CA将公钥放入数字证书并将数字证书颁布给服务器,此时服务器就不简单的把公钥给客户端了,而是给客户端一个证书,数字证书加入了一些数字签名机制,保证了数字证书一定是服务器给客户端的。中间人伪造的证书没有经过CA认证,此时,客户端和服务器就知道通信被劫持了。
综上所述对称加密(公钥和私钥)交换对称密钥+数字证书验证身份(验证公钥是否是伪造的)+利用对称密钥加密后续传输的数据==安全。
TCP三次握手过程
如图所示,三次握手是:
- 客户发送
SYN=1,seq=x给服务端,客户端进入SYN_SENT状态 - 服务端收到
SYN=1,就知道这个请求连接的,然后发送SYN=1,ACK=q,seq=y,ack=x+1,这里的ACK=1表示是确认包ack字段有效,ack=x+1表示已经收到了x及x以前的包了,下次请发送x+1给我。SYN=1表示是请求连接,TCP是全双工,客户端要连接服务端,那么服务端也要链接客户端。不过是SYN=1和ACK=1合并在了一个包中。此时服务端为SYN_REVD状态。 - 客户端收到
SYN=1,ACK=q,seq=y,ack=x+1数据,就知道服务端已经连接上了,但是需要告诉服务端,自己(客户端)也可以接收服务端数据了,就发送ACK=1,seq=x+1,ack=y+1。此时客户端处于ESTABLISHED状态。 - 服务端收到来自客户端的
ACK=1,seq=x+1,ack=y+1,就知道客户端已经收到了seq=y的包了,至此握手完成。此时服务端处于ESTABLISHED状态。
网上找了一个动态图,很形象
抓包验证
使用Wireshark抓包验证一下tcp的三次握手。
我们使用Wireshark来抓简书的包,设置过滤条件ip=161.117.186.50,则和该ip的交互全部过滤出来了。
客户端发送SYN
首先客户端发送SYN=1,seq=0给服务端。
首次随机的seq为0,所以发送消息为
SYN=1,seq=0.
SYN设置了,其他位未设置,则表示该包是同步包,请求建立链接。
服务端
服务端收到了链接请求,自然同意了然后发送了ACK=1,ack=1,TCP为全双工,那么服务端也是需要请求客户端简历链接的,也发送SYN=1,seq=0,合并到一起,则是SYN=1,ACK=1,seq=0,ack=1,如下图所示:
握手完成
当客户端收到了服务端的请求和验证包SYN=1,ACK=1,seq=0,ack=1,则知道自己已经连接上服务端了,那么服务链接自己,还是需要客户端再次发送ACK=1给服务端,让服务端知道一下的。
所以客户端发送ACK=1,ack=1,seq=1给服务端,服务端收到之后开始监听客户端的端口。至此握手完成。
四次挥手
说完了三次握手肯定要复习下四次挥手的。
这张图上说的很搞笑也很容易理解,当客户端不再不需要给服务端传输数据的时候,则需要断开连接了。
开始未断开连接前,双方处于ESTABLISHED状态。
-
客户端会发送
FIN=1,seq=x给服务端,此时客户端处于FIN_WAIT1状态。 -
服务端收到之后,会告诉 客户端我已经收到了,则发送
ACK=1,seq=y,ack=x+1,表示已经收到你的断开请求了。此时服务端处于CLOSE_WAIT状态,客户端处于FIN_WAIT2状态。 -
当服务端不再不需要给客户端传输数据,则发送
FIN=1,seq=z,ack=x+1给客户端,此时客户端进入LAST_ACK状态。 -
客户端收到了断开请求,则会发送
ACK=1,ack=z+1,seq=x+2表示接受了服务端的断开请求。至此 四次挥手完成。
但是在最后第4个步骤,有可能服务端一直收不到客户端的ACK包,那么他会启动一个定时器,计时2MSL(即是Maximum Segment Lifetime),一个报文的最大生命周期,当1MSL之后服务端没收到响应则还会再次发送一个FIN=1 seq=z,ack=x+1,当2MSL之后,则丢弃该链接。
验证四次挥手
-
客户端主动断开连接,发送
FIN=1,ACK=1,seq=706,ack=1244. -
服务端收到来自客户端主动断开连接请求,发送确认包和终止包,合二为一:
FIN=1,ACK=1,seq=1244,ack=707.
- 客户端收到服务端的确认包和断开连接请求,发送确认包给服务端:
ACK=1,seq=707,ack=1245.
四次挥手完成。
断开连接一定4次吗?
不一定,当服务端接到来自客户端的断开连接请求时可以将FIN=1,ACK=1,seq=y,ack=x+1同时传输给客户端,那么图中第二第三步则合并成一个包。
TLS握手
TLS握手是发生在三次握手之后的,因为双方想要发送数据验证的基础是双方可以相互通信,相互通信必须在握手完成之后。
1. client hello
客户端第一步 client hello,生成session ID,和支持的26中加密算法,让服务端选择。
1.1 random_c
在客户端问候中,前四个字节是时间戳,28字节随机数(random_c),后面会用到这个随机数。
1.2 session ID
session ID为了减少握手次数,第二次可以携带该ID即可,除非由于其他原因需要重新握手。 session ID 目前大多数都支持,缺点是session ID只保留在一台服务器上,当客户端发送到另外的服务器上,则无法恢复对话,session ticket 为 解决这个问题而生,目前只有Firefox和Chrome支持。
1.3 密文族(Cipher Suites)
本次测试 Chrome浏览器支持26中加密算法,他们通常写法是密钥交换算法+对称加密算法+哈希算法已TLS_AES_128_GCM_SHA256为例:
握手时采用TLS协议进行密钥交换,用RSA签名和身份认证,握手后的通信使用AES对称算法,密钥长度为128位,分组模式是GCM,摘要算法是SHA384用于消息认证和产生随机数。
说道TLS,就不得不谈OpenSSL,他是著名开源密码学工程库和工具包,几乎支持所有公开的加密算法和协议,已经成为了事实上的标准,许多软件都会使用它来实现TLS功能,包括Web的Apache、Nginx等。
1.4 Server Name扩展
当我们去访问一个站点时,一定是先通过DNS解析出站点对应的ip地址,通过ip地址来访问站点,由于很多时候一个ip地址是给很多的站点公用,因此如果没有server_name这个字段,server是无法给与客户端相应的数字证书的,Server_name扩展则允许服务器对浏览器的请求授予相对应的证书。
服务器收到client hello会回复 Server hello/Certificate/Certificate Status三个数据包。
2. Server hello
2.1 我们得到了服务器的以Unix时间格式记录的UTC和28字节的随机数 (random_S)
2.2 Seesion ID,服务端对于session ID一般会有三种选择 (稍会儿我们会看到隔了半分钟,第二次抓包就有这个Session ID):
(1)恢复的session ID:我们之前在client hello里面已经提到,如果client hello里面的session ID在服务端有缓存,服务端会尝试恢复这个session;
(2)新的session ID:这里又分两种情况,第一种是client hello里面的session ID是空值,此时服务端会给客户端一个新的session ID,第二种是client hello里面的session ID此服务器并没有找到对应的缓存,此时也会回一个新的session ID给客户端;
(3)NULL:服务端不希望此session被恢复,因此session ID为空。
2.3 在client hello提供了加密族,这里服务端选择了TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,意味着服务端会采用ECDHE_RSA算法进行密钥交换,通过AES_128_GCM对称加密来加密数据,利用SHA384进行保证数据完整性。
3 Certificate 、 server key exchage 和 Server Hello Done
3.1 Certificate
在前面的
https原理研究中,我们知道为了安全的将公钥发给客户端,服务端会把公钥放入数字证书中并发给客户端(数字证书可以自签发,但是一般为了保证安全会有一个专门的CA机构签发),所以这个报文就是数字证书,3063 bytes就是证书的长度。
3.2 server key exchage
3.3 Server Hello Done
我们抓的这个包是Certificate 、 server key exchage 和 Server Hello Done合并到一起的包。
4、 客户端验证 证书真伪
客户验证证书的合法性,如果验证通过才会进行后续的通信,否则根据错误提示和操作,验证合法性如下:
-
- 证书链的可信性 trusted certificate path;
-
- 证书是否吊销, 有两类方式离线CRL和OCSP,不同客户端行为不同;
-
- 有效期验证
-
- 域名验证
5、 密钥交换
过程较复杂,大概如下:
-
1.首先,客户端利用
CA证书实现身份认证,服务端在sercer hello之后,如果是用ECDHE交换密钥算法则需要发送server exchange key给客户端,否则则不用。 -
2.客户端收到
server exchange key,也发送服务器client exchange key,使用(server exchange key client exchange key)经过ECDHE算法,生成另外一个pre-master。现在随机数有random_c、random_s和pre-master,利用这三个随机数就可以生成用于加密会话的主密钥,叫做Master Secret。 服务端在收到客户端的client exchange key利用这2个key也同样生成pre-master,然后同样是用PRF算法生成主密钥Master Secret。当使用非ECDHE算法,则client exchange key为随机数,经过ECDHE算法计算得出pre-Master,然后和random_c、random_s计算得出主密钥Master-Secret。 -
- 服务端收到
pre-master,使用私钥解密后,取出pre-master,之前保存的random_c,random_s,利用这三个参数生成主密钥,算法如下:
- 服务端收到
master_secret = PRF(pre_master_secret,"master secret",ClientHello.random+ServerHello.random)
RSA握手过程
刚才说的和刚才传统TLS握手过程,有2点不同:
-
- 使用
ECDHE算法实现密钥交换,而不是RSA,所以服务端会发出Server key exchange消息
- 使用
-
- 因为使用
ECDHE,客户端不用等到发回Finished确认握手完毕就立即发出HTTP报文,省去一个消息往返时间的时间浪费。这个叫TLS False Start,
- 因为使用
传统的TLS握手过程只是服务端少了server exchange key,客户端不再使用server exchange key和client exchange key生成pre-master,而是客户端直接生成随机数,然后用服务器的公钥加密通过client exchange key消息发送给服务器,进服务器利用私钥解密,双方都实现了共享的三个随机数,就可以生成密钥了。
所有过程总结到一张图上:
Session 复用
当 第一次建立连接后,服务器会将该id存储到队列中,当客户端下次请求时,会将通过client hello传输过去,服务器会校验该 Session id,假如存在,就返回Change Cipher Spec、Encrypted Handshare Message,就可以直接使用了。不存在的话,重新走上边所讲的TLS握手过程。
上面将的是1.2版本,那么再1.3版本有什么优化呢?
TLS 1.3
在1.3 中服务端在接收到Client hello,则根据Client params、Client Random、Server Random、Server params四个参数使用ECDHE算法计算得出pre-master,然后使用Client 随机数C、Server 随机数S、pre-master经过HKDF计算得出主密钥,然后就可以进行加密通信了。客户端在收到Server Certificate和Server Random、Server params之后,经过验证证书有效,然后使用HKDF算法使用上边的4个参数即可计算得出主密钥,客户端然后通知服务端可以进行加密通讯了。
相比TLS1.2,握手阶段减少了服务端与客户端发送
Server/Client Key Exchange,以及减少握手阶段的明文展示次数,只能看到明文Client hello与Server hello,其他的都被密钥加密。