Linux教程

Linux随笔13-gawk数组、TLS密钥交换与https通信过程

本文主要是介绍Linux随笔13-gawk数组、TLS密钥交换与https通信过程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Contents

  • 1. gawk数组
    • 1.1. gawk数组基础
    • 1.2. gawk数组应用
  • 2. TLS密钥交换过程
    • 2.1. 两种加密方式 - 对称加密和非对称加密
    • 2.2. TLS
    • 2.3. TLS密钥交换过程
      • 2.3.1. 几个TLS术语(TLS Glossary)
      • 2.3.2. 基于RSA的握手过程
      • 2.3.3. 基于DH的握手过程 - Ephemeral Diffie-Hellman handshake
  • 3. https通信过程
  • 4. References

1. gawk数组

gawk是GUN组织实现的awkawk最初是由Alfred V. Aho, Brian W. Kernighan, Peter J. Weinberger, Addison-Wesley这4个人于1988年在Unix系统上实现的,用于系统信息的格式化报告打印。

[root@LiuXianQiE ~]# ls -l `which awk`
lrwxrwxrwx. 1 root root 4 May 11  2019 /usr/bin/awk -> gawk
[root@LiuXianQiE ~]# 

从上述输出可见,在Linux系统上,仍然保留了awk这个命令,但是作为gawk的软链接形式存在。

1.1. gawk数组基础

gawk中,也支持条件和逻辑判断结构(if…else if…else,switch)、循环迭代结构(for, while, do-while)、内建函数和自定义函数,以及这里要用到的数组特性。
在此前的博客中,介绍过bash中支持的数组,分为两类:索引数组和关联数组,参见Linux随笔10-Ubuntu网络配置、非交互式远程主机登录以及shell中的数组应用(冒泡排序数组中的元素),而gawk中使用的则是关联数组。
gawk数组可以直接应用在BEGIN{}语句体、END{}语句体以及循环语句体{}中,其基本使用形式为array_name[key]=arr_value,比如person[name]=zhangsan; person[gender]=male。如果arr_value是字符串,需要用引号将其括起来,否则gawk会将其解释为变量名。
上述创建了数组,那如果要迭代数组中的元素,gawk中也提供了一个类似于bash中的for循环一样的迭代方式:for (i in array_name) {do-something},使用这种方式就可以迭代数组中的元素了。
如果要判断某个索引是否存在于数组中,那么可以执行idx in array_name,如果索引idx在数组中,那么上述表达式会返回1,如果不在数组中,那么上述表达式将会返回0。
下面通过几个示例进行演示:

判断索引是否在数组中

[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; > person["gender"]="Male"; print "name" in person, "sex" in person}'
1 0
[root@LiuXianQiE ~]#

上述创建了一个名为person的数组,其中包含了两个元素,分别为name和gender,在构建数组的时候,数组的key需要用引号括起来,且该引号需要与语句体的引号相区别,语句体使用单引号,语句体内部需要将字符串使用双引号括起来,不能使用双引号将语句体括起来。具体如下所示:

[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; person["gender"]="Male"; print "name" in person, "sex" in person}'
1 0
[root@LiuXianQiE ~]# gawk "BEGIN{person['name']='zhangsan'; person['gender']='Male'}"
gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'}
gawk: cmd. line:1:              ^ invalid char ''' in expression
gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'}
gawk: cmd. line:1:              ^ syntax error
[root@LiuXianQiE ~]# gawk "BEGIN{person['name']='zhangsan'; person['gender']='Male'; print 'name' in person, 'sex' in person}"
gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'; print 'name' in person, 'sex' in person}
gawk: cmd. line:1:              ^ invalid char ''' in expression
gawk: cmd. line:1: BEGIN{person['name']='zhangsan'; person['gender']='Male'; print 'name' in person, 'sex' in person}
gawk: cmd. line:1:              ^ syntax error
[root@LiuXianQiE ~]# 

如果要引用数组中的元素,直接使用array_name[idx]的形式即可。具体如下所示:

[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; person["gender"]="Male"; print "name="person["name"], "Gender="person["gender"]}'
name=zhangsan Gender=Male
[root@LiuXianQiE ~]# 

通过for循环遍历上述数组的内容,此时效果如下所示:

[root@LiuXianQiE ~]# gawk 'BEGIN{person["name"]="zhangsan"; person["gender"]="Male"; for(i in person) {print person[i]}}'
zhangsan
Male
[root@LiuXianQiE ~]#

上述就是遍历数组的过程。

有了上述的基础,下面通过gawk的数组功能,实现访问httpd服务的IP地址统计,并将超过100的IP地址封掉。

1.2. gawk数组应用

为此,需要准备两台虚拟机,一台虚拟机作为httpd服务器,一台作为客户端,并发发起连接请求。

  1. 准备服务器

    在服务器上,安装httpd服务,此处使用Ubuntu20.04.2 LTS作为服务器,在其上安装httpd服务,执行命令apt install -y apache2即可。安装完成之后,执行命令systemctl enable --now apache2.service即可将httpd服务设置为开机自动运行,并且立即启动该服务。
    然后将准备主页内容:

    root@ubuntu20u04:~# echo 'This is a apache2 web service on Ubuntu20.04.2 LTS server' > /var/www/html/index.html 
    root@ubuntu20u04:~# cat /var/www/html/index.html
    This is a apache2 web service on Ubuntu20.04.2 LTS server
    root@ubuntu20u04:~# curl localhost
    This is a apache2 web service on Ubuntu20.04.2 LTS server
    root@ubuntu20u04:~# 
    

    至此,服务器端准备完成。

  2. 准备客户端

    客户端采用CentOS 7.6系统,由于要在客户端发情并发连接,所以需要在客户端上执行ab命令,而该命令是由httpd-tools这个软件包提供的,所以需要在客户端上安装该软件包。安装的命令为yum install -y httpd-tools即可。
    查看安装后的结果如下:

    [root@c7u6-ha1 ~]# rpm -qf `which ab`
    httpd-tools-2.4.6-88.el7.centos.x86_64
    [root@c7u6-ha1 ~]# 
    

    从上述输出可见,ab命令是由httpd-tools这个软件包提供的。

服务器端和客户端都准备完成了,接下来在客户端通过ab命令发起并发连接测试。具体如下所示:

[root@c7u6-ha1 ~]# ab -b 5120 -n 80000 -c 8000 http://ubuntu20u04/index.html
This is ApacheBench, Version 2.3 <$Revision: 1430300 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
                                              
Benchmarking ubuntu20u04 (be patient)            
Completed 8000 requests                                                  
Completed 16000 requests                              
Completed 24000 requests                         
Completed 32000 requests                                   
Completed 40000 requests                                   
Completed 48000 requests                                  
Completed 56000 requests                        
Completed 64000 requests                                 
Completed 72000 requests                                                       
Completed 80000 requests                      
Finished 80000 requests                                        

Server Software:        Apache/2.4.41
Server Hostname:        ubuntu20u04
Server Port:            80

Document Path:          /index.html
Document Length:        10918 bytes

Concurrency Level:      8000
Time taken for tests:   29.736 seconds
Complete requests:      80000
Failed requests:        225
   (Connect: 0, Receive: 0, Length: 225, Exceptions: 0)
Write errors:           0
Total transferred:      892847592 bytes
HTML transferred:       870988694 bytes
Requests per second:    2690.38 [#/sec] (mean)
Time per request:       2973.561 [ms] (mean)
Time per request:       0.372 [ms] (mean, across all concurrent requests)
Transfer rate:          29322.47 [Kbytes/sec] received

Connection Times (ms)
              min  mean[+/-sd] median   max
Connect:        0  198 588.0      0    3015
Processing:    10 1191 5111.2     42   26819
Waiting:        0 1129 4992.9     41   26818
Total:         30 1389 5312.8     42   29329

Percentage of the requests served within a certain time (ms)
  50%     42
  66%     45
  75%     53
  80%     79
  90%   1253
  95%   6266
  98%  27186
  99%  27460
 100%  29329 (longest request)

上述命令中,-c 8000表示并发发起8000个连接;-n 80000表示session中执行的请求数。
上述即为客户端发起的连接测试。接下来查看服务器端的访问日志,具体如下所示:

root@ubuntu20u04:~# cat /var/log/apache2/access.log | head -n10
127.0.0.1 - - [13/Jun/2021:09:34:35 +0800] "GET / HTTP/1.1" 200 11173 "-" "curl/7.68.0"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
192.168.122.16 - - [13/Jun/2021:09:42:36 +0800] "GET /index.html HTTP/1.0" 200 11192 "-" "ApacheBench/2.3"
root@ubuntu20u04:~# 

要统计服务器上各个IP地址的访问次数,可以由几种方式,具体如下所示:

  1. 通过基本的文本处理命令
    root@ubuntu20u04:~# cat /var/log/apache2/access.log | cut -d' ' -f1 | sort -n | uniq -c
      3 127.0.0.1
     241206 192.168.122.16
    root@ubuntu20u04:~# 
    
    这种方式是最基本的形式,借助cat命令将文件内容打印到标准输出流中, cut命令按照指定的分隔符提取出指定的字段, sort命令对提取出的字段内容进行排序, 最后通过uniq命令对排序的结果进行统计,进而得出各个IP地址的访问次数。
  2. 通过基本的文本处理命令结合gawk命令实现
    root@ubuntu20u04:~# gawk '{print $1}' /var/log/apache2/access.log | sort -n | uniq -c
    3 127.0.0.1
    241206 192.168.122.16
    root@ubuntu20u04:~#
    
    这种形式跟上面的形式差不多,只不过此处不再通过cat结合cut命令提取指定的字段信息,而是通过gawk命令提取指定的字段信息。然后仍然是借助sort命令对提取的字段信息进行排序,最后通过uniq命令对排序结果进行统计。
  3. 只通过gawk命令实现统计
    root@ubuntu20u04:~# gawk '{ip[$1]++} END{for(i in ip) {print i, ip[i]}}' /var/log/apache2/access.log 
    127.0.0.1 3
    192.168.122.16 241206
    root@ubuntu20u04:~#
    
    这个操作只用到gawk命令,但是用到了gawk内部提供的数组和for循环迭代数组的特性,ip[$1]表示以IP地址作为数组的key,而ip[$1]++的含义就是将相同的IP地址出现的次数加在一起,效果等同于ip[$1]+=1,然后将得到的结果作为value存放在ip[$1]中,最终结果就是ip[127.0.0.1]=3,即本地回环地址访问了3次。

有了IP地址的访问次数统计,那么就可以通过ipables命令将对应的IP地址封禁了。要封禁对应的IP地址,可以在iptables的filter表的INPUT链中添加规则:源地址为待封禁IP,目标地址为本机IP,且从eth0网卡接口访问本机80端口的连接,全部丢弃。规则如下所示:

root@ubuntu20u04:~# iptables -t filter -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
root@ubuntu20u04:~#

root@ubuntu20u04:~# iptables -t filter -A INPUT -s 192.168.122.16 -d 192.168.122.30 -i eth0 -p tcp --dport 80 -j DROP
root@ubuntu20u04:~# iptables -t filter -L INPUT
Chain INPUT (policy ACCEPT)
target     prot opt source               destination         
DROP       tcp  --  192.168.122.16       192.168.122.30       tcp dpt:http
root@ubuntu20u04:~# 

上述就给filter表的INPUT链添加了一条规则,在指定的端口的时候,需要指定数据包类型是TCP还是UDP。此时,就无法在1921.68.122.16这台虚拟机上访问192.168.122.30这台虚拟机的httpd服务了。具体如下所示:

[root@c7u6-ha1 ~]# curl ubuntu20u04
^C
[root@c7u6-ha1 ~]# 

上述在执行的时候,会一致处于等待状态,没有信息返回,这是由于在iptables规则中指定了目标动作为DROP,这个操作会直接将数据包丢弃,但是不给客户端返回任何信息。
如果想看到返回信息,可以将iptables规则中的目标动做从DROP修改为REJECT即可。具体如下所示:

root@ubuntu20u04:~# iptables -t filter -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    DROP       tcp  --  192.168.122.16       192.168.122.30       tcp dpt:http
root@ubuntu20u04:~# iptables -t filter -R INPUT 1 -s 192.168.122.16 -d 192.168.122.30 -i eth0 -p tcp --dport 80 -j REJECT
root@ubuntu20u04:~# iptables -t filter -L INPUT --line-numbers
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         
1    REJECT     tcp  --  192.168.122.16       192.168.122.30       tcp dpt:http reject-with icmp-port-unreachable
root@ubuntu20u04:~#

此时在客户端访问如下所示:

[root@c7u6-ha1 ~]# curl ubuntu20u04
curl: (7) Failed connect to ubuntu20u04:80; Connection refused
[root@c7u6-ha1 ~]# 

但是在宿主机仍然可以访问,具体如下所示:

[root@LiuXianQiE ~]# curl ubuntu20u04
This is a apache2 web service on Ubuntu20.04.2 LTS server
[root@LiuXianQiE ~]#

上述过程,如果写成脚本,然后放在计划任务中,如果每10分钟的连接数超过100,就将这个IP地址封禁。通过实时监控系统的连接数,可以在一定程度上抵御DOS攻击。

脚本内容如下所示:

#!/bin/bash

#**********************************************
# FileName: deny_ddos.sh
# Author: 六弦企鹅
# Created: 2021.06.13
# Modified: 2021.06.13
# Version: 0.1
# Description: 检测http连接数,超过指定数量的IP封禁
# Usage: bash deny_ddos.sh
#**********************************************

# root@ubuntu20u04:~# ss -tnap | gawk -F' +|:' '/:22/ && /[0-9]+.{3}[0-9]+/ && ! /0.0.0.0/ && ! /127.0.0.1/ {ip[$6]++} END{for(i in ip) print i, ip[i]}'
# 192.168.122.16 1
# 192.168.122.1 1
# root@ubuntu20u04:~# 

local_ip=192.168.122.30
ss_res=`ss -tnap | gawk -F' +|:' '/:80/ && /[0-9]+.{3}[0-9]+/ && ! /0.0.0.0/ && ! /127.0.0.1/ {ip[$6]++} END{for(i in ip) print i, ip[i]}' | sed -re ':label; N; s/\n/@/; t label'`

IFS_OLD=$IFS
IFS=@

for line in ${ss_res}
do
    #echo $line
    ipaddr=`echo $line | cut -d' ' -f1`
    ipnum=`echo $line | cut -d' ' -f2`
    if [ ${ipnum} -gt 100 ]; then
        iptables -t filter -A INPUT -s ${ipaddr} -d ${local_ip} -i eth0 -p tcp --dport 80 -j DROP
        echo ${ipaddr} - ${ipnum} 
    fi
done

上述脚本可以将80端口的连接IP做统计,并将脚本执行时连接超过100个的IP封禁。
将上述脚本写入到计划任务里面,每隔10分钟检查一次,具体如下所示:

root@ubuntu20u04:~# crontab -e 
crontab: installing new crontab
root@ubuntu20u04:~# crontab -l
# m h  dom mon dow   command
*/10 * * * * bash /root/deny_ddos.sh

上述就是每隔10分钟执行一次这个脚本,检查一次主机的80端口连接情况。

2. TLS密钥交换过程

2.1. 两种加密方式 - 对称加密和非对称加密

密钥交换,自然涉及到加密算法,而常用的加密算法,按照加密与解密所用密钥是否一致,可分为:对称加密非对称加密

  • 对称加密:加密与解密使用相同的密钥,这种加密方式在日常生活中用的比较普遍,比如电脑的登录密码、手机的指纹解锁、人脸识别等等。这些都是通过事先输入的加密密钥对设备进行加密,解密的时候通过此前已经输入到设备中的密码、指纹信息、人脸信息对设备进行解密。即加密设备和解密设备用的是相同的密钥。
    对称加密的优点是加密解密速度快,效率高;缺点是当加密对象增多的时候,管理的密钥也会变多,且密钥不易在公开场合分发,而且无法确定数据来源。
    常用的对称加密算法包括:DES(Digital Encryption Standard), 3DES(Tripple DES), AES(Advanced Encryption Standard)。其中DES采用56位密钥,现在已经有被穷举破解的报道;3DES则是将数据分块,然后对每个数据块使用3次DES加密算法进行加密,安全性比DES更高;AES算法将数据划分为128位的数据块,并对这些数据块应用密钥加密,而可选的密钥长度包括128位、192位和256位,这种加密算法数据 安全性更高。
  • 非对称加密:加密与解密使用不同的密钥,通过事先生成公私密钥对,然后使用公钥对数据进行加密,私钥实现数据解密,同时无法通过公钥逆推出对应的私钥,所以安全性更加能够得到保障。由于私钥负责解密,且私钥不对外分发,公钥对外分发,用于实现数据加密。这种加密方式在日常生活中也有应用,比如网银的U盾,就是利用的非对称加密技术实现的,银行事先生成公私密钥对,并把公钥留在银行服务器中,私钥下发到用户的U盾中,当用户需要转帐的时候,插上私钥就可以实现数据的加密解密和身份验证了。另外,在Linux的ssh服务上,也是通过非对称加密的方式实现无密码登录的,在这个过程中,在发起登录的主机上生成公私密钥对,然后将公钥发送到要登陆的目标主机上,然后就可以在发起登录的主机上无密码登录目标主机了。
    非对称加密的特点是使用两个密钥,一般情况下是公钥用于加密,私钥用于解密,公钥可以对外发布,数据通信过程中,通过公钥对通信数据进行加密,能够确保数据安全性,缺点是加密解密速度慢,效率低。
    常用的非对称加密算法包括:RSA(Rivest Shamir Adleman), DSA (Digital Signature Algorithm), ECC (Elliptic Curve Cryptography), Diffie-Hellman Key Agreement等。其中,RSA可以用于数据加密和数字签名;DSA只能用于数字签名;ECC类似于RSA,能够实现数据加密和数字签名, 而且其能够很好的适用于蜂窝设备(cell devices);Diffie-Hellman Key Agreement用于机密信息的密钥共享。

2.2. TLS

TLS(Transport Layer Security)主要用在web安全传输上,用于数据加密。TLS的两个主要目标就是:机密性(Confidnetiality)身份验证(Authentication),这两点对于互联网通信来说,都是十分重要的。

  • 机密性(Confidnetiality):通信双方之间的信息传递采用对称密钥进行加密,确保传输的信息只能被双方理解。在TLS中,现在通常使用AES加密算法进行信息加密;较老的浏览器可能还在使用不太安全的3DES加密算法或者RC4算法。
  • 身份验证(Authentication):就是确保通信的对端确实是与自己建立通信关系的那个实体,这个过程是通过公钥实现的,Web站点通过证书和公钥向客户端浏览器证明其身份,而这个过程中,浏览器要信任web站点的证书,需要做两件事情:证明证书所有者是第三方可信(操作系统预先安装了受信任的根证书)以及证明该证书是可信的。

Web站点的证数中就包含了其公钥,如果Web站点能够证明其控制着对应的私钥,那么就认为这个站点就是证书的所有者。而在浏览器这边,如果这个证书是被系统预先安装的根证书信任的证书机构(Certificate Authority,CA)颁发的证书,同时包含了这个Web站点的域名信息,那么就认为这个Web站点的证书是可信的。

而在Web上下问环境中,机密性和身份验证是通过建立共享密钥和证明证书所有关系的过程来完成的。这个过程会中,双发会收发一系列消息,被称为握手(HandShake)。

2.3. TLS密钥交换过程

TLS有两种类型的握手:一种是基于RSA,一种是基于Diffie-Hellman。这两种加密算法都引领着现代加密技术。这两种握手方式的区别仅在于密钥建立和身份验证所实现的主体不同,具体如下表所示:

Key EstablishmentAuthentication
RSA handshakeRSARSA
DH handshakeDHRSA/DSA

RSA和DH握手过程都有其各自的优缺点,RSA握手过程只是用了一个RSA公钥加密算法操作;DH握手过程除了RSA握手过程中相同的RSA证书操作之外,还有一步额外的DH操作。如果给定的证书是RSA证书,那么RSA握手过程的计算速度更快。公钥加密算法,比如RSA、DH在加密过程中都会消耗很多CPU资源,也是整个TLS握手过程中最慢的部分。
DH握手过程中需要运行两个算法,这样的操作除了增加计算时常之外,也有其优势,那就是可以使密钥建立独立于服务器的私钥完成。这就能够在一定程度上确保连接的前向机密性(forward secrecy),也就是说,即便在私钥被暴露的情况下也可以保护会话不会被解密。另外,DH握手过程中,也可以不开启RSA证书来提高性能。使用ECDSA(Elliptic Curve DSA)证书以及Elliptic Curve Diffie-Hellman Key Agreement的DH握手过程也会比一步操作的RSA握手过程更快。

2.3.1. 几个TLS术语(TLS Glossary)

为了更好的理解握手过程,需要了解下面几个术语的含义:

  1. Session key:这是TLS握手过程的最终结果,即共享密钥。它使对称加密算法的密钥,允许客户端和服务器把将要发送的消息使用这个密钥进行加密,然后发送加密后的信息。

  2. Client random:客户端随机值,这是客户端创建的32字节序列值,对于每个不同的连接,这个值都是不一样的。其中包含4个字节的时间戳以及28个字节的随机值。最近Google Chrome浏览器为了避免形成客户端指纹信息,省略掉了4个字节的时间戳,改为32字节全部为随机值。

  3. Server random:与***Clinet random***含义相同,只不过是由服务器端生成的。

  4. Pre-main secret:48字节的数据块,它可以使用伪随机数函数(“PseudoRandom Function” ,PRF)将***Client random***和***Server random***组合在一起生成共享密钥***Session key***。

  5. Cipher suite:构建TLS连接的组合算法唯一标识符,它为下面的每一步定义了一个算法:

    • 密钥建立(key establishment),典型的算法是DH的变种算法或者RSA算法
    • 身份验证,证书的类型
    • 机密性,对称加密
    • 完整性,哈希函数

    比如"AES128-SHA"定义了一个会话使用如下算法:

    • RSA用于隐式密钥建立
    • RSA用于隐式身份验证
    • 块加密链(CBC)模式的128位AES加密用于确保数据的机密性
    • 160位的SHA(Secure Hashing Algorithm)用于确保数据完整性

    除了上述之外,还有更复杂的实现套件:“ECDHE-ECDSA-AES256-GCM-SHA384”,这个套件定义了一个会话任务中使用如下算法:

    • ECDHE (Elliptic Curve Diffie-Hellman Ephemeral)密钥交换技术用于实现密钥建立(Key Establishment)
    • ECDSA (Elliptic Curve Digital Signature Algorithms)用于身份验证
    • GCM (Galois/Counter mode)模式下的256位AES加密算法确保数据的机密性
    • 384位SHA算法用于确保数据的完整性

有了上述理解,就可以深入了解TLS的两种握手过程了。

2.3.2. 基于RSA的握手过程

注意,在这个握手过程中,没有任何数据是被Session key加密的,因为此时还未生成双方都能识别的Session key。所以这个过程中数据都是明文传输的。具体过程如下图所示:
在这里插入图片描述
上图的通信过程的各个步骤如下:

  • Message1: "Client Hello"
    客户端(一般是浏览器)发送的"Hello"消息中包含了客户端想要使用的协议版本,以及发起握手所需要的一些其他信息,比如Client random, 密码组件列表(a list of cipher suites),现代浏览器也会包含其想要查找的主机名,即SNI (Server Name Indication),SNI允许Web服务器主机的相同IP地址上存在多个域名。
  • Message2: "Server Hello"
    服务器端接收到客户端发送来的"Hello"消息之后,会从发送的数据包中提取相关参数,准备建立握手。其中包括确定握手类型相对应的密码组件的选择。而服务器的"Hello"信息中包含了Server random、服务器选择的密码组件以及服务器的证书文件。在证书文件中包含了服务器的公钥和域名。
  • Message3: "Client Key Exchange"
    客户端在接收到服务器发送过来的"Hello"信息之后,验证证书是否可信,以及证书所有者是否是要连接的目标服务器。验证通过之后,客户端会生成随机的 Pre-main secret 密钥,然后用服务器证书中包含的公钥文件对这个密钥进行加密,并将加密后的结果发送给服务器。
    服务器在收到客户端发送过来的上述密文之后,使用自己的私钥文件将密文解密,从中提取出 Pre-main secret 密钥。至此,通信双方都具有了相同的 Pre-main secret 密钥了,与此同时,在客户端和服务器端全部具有 Client randomServer random,此时客户端和服务器端就可以生成相同的 Session key了。至此,服务器端和客户端之间就可以在随后的数据交换中使用生成的 Session key 对数据进行加密,并将加密后的数据发送给对方。
    上述握手过程在客户端和服务器端之间互换了"Finished"信息之后,就算完成了。实际过程是客户端使用 Session key将消息"Client Finished"加密之后发送给服务器端;类似的服务器端也会使用 Session key将消息"Server Finished"这个信息加密之后发送给客户端。在双方生成了一致的 Session key之后,双方之间的通信就都是使用这个对称密钥进行加密的了。由于这个对称密钥是根据双方的一些信息在各自的主机上生成的,所以这个对称密钥并没有在网上直接进行传输,也就避免了被破解的可能。
    上述握手过程在一步操作中结合了密钥交换和身份验证,其背后的逻辑就是服务器要能够正确的派生出 Session key,那么它就必然是私钥的所有者,因为只有私钥所有者才能解密出客户端使用服务器的公钥加密的数据,所以服务器也就必然是证书的所有者。
    上述握手过程不足的地方在于,双方之间的通信安全性得以保证的前提是服务器的私钥是安全的。如果服务器的私钥被泄露,那么获得私钥的第三方将能够使用私钥解密出 Pre-main secret,并据此派生成相同的 Session key对称密钥,然后第三方就可以通过 Session key解密出通信双方的全部消息了。这种情况在证书过期或者被吊销的情况依然能够实现。所以,这也就催生了即便在私钥被泄露的情况下依然能够确保消息机密性的握手过程,也就是下面将要介绍的基于DH的握手过程。

2.3.3. 基于DH的握手过程 - Ephemeral Diffie-Hellman handshake

Ephemeral Diffie-Hellman handshake是另一种TLS的握手过程,它使用了两种不同的机制:一个是用于建立可共享的Pre-main secret,另一个是服务器身份验证。这个密钥特性依赖于Diffie-Hellman密钥一致性算法(Diffie-Hellman key agreement algorithm)。

在Diffie-Hellman中,双方通过不同的密钥交换消息来获得相同的共享密钥,这个握手过程依赖于一个简单的数学原理:对一个整数g计算a次幂的结果,对该结果计算其b次幂,写成数学表达式为(ga)b=gab,这个最终结果与对整数g计算b次幂的结果,然后对该结果计算其a次幂,写成数学表达式为(gb)a=gab,这两个结果是相同的。而这也也是为什么双方可以使用不同的密钥交换消息来获得相同的共享密钥的原理。该算法的工作过程如下所示:

  • 主机A有一个密钥a,A将消息使用密钥a加密之后得到密文消息ga,随后将这个密文消息发送给主机B
  • 主机B有一个密钥b,B将消息使用密钥b加密之后得到密文消息gb,随后将这个密文消息发送给主机A
  • 主机A接收到B发送过来的密文消息gb之后,使用自身的密钥a对这个密文消息进行幂运算,得到新的消息(gb)a=gab,即主机A计算B发过来的密文消息,经过计算之后得到新的消息gab
  • 主机B接收到A发送过来的密文消息ga之后,使用自身的密钥b对这个密文消息进行幂运算,得到新的消息(ga)b=gab,即主机B计算A发送过来的密文消息,经过计算之后得到新的消息gab
  • 至此,双方就有了相同的共享密钥gab

上述操作对于一般的数来说,经过gab计算之后会得到很大的数,而对于一个数的n次方根执行上述计算,则不会得到很大的数,也是更高效的方式。除此之外,还可以改变问题空间,同样奏效,这是通过将一个固定大小的数除以一个大素数然后取余数这种计算方式实现的(However, we can change the problem space and make it work. This is done by restricting the computation to numbers of a fixed size by always dividing the result of a computation by big prime number and taking the remainder.)。而这被称为算数取模,对算数取模的结果计算其n次方根,被称为离散对数问题。

Diffie-Hellman密钥一致性(Diffie-Hellman key agreement)算法的一个变种就是使用椭圆曲线(Elliptic Curves, ECDHE),更多关于椭圆曲线的信息,参见素数这篇文章的介绍。通过使用这种固定大小的Diffie-Hellman密钥一致性算法就可以派生出共享密钥 Session key了。

Diffie-Hellman握手的过程具体如下所示:
在这里插入图片描述
上图的具体通信过程,如下所示:

  • Message1: "Client Hello"
    类似于基于RSA的握手过程,客户端发给服务器端的消息"Hello"中包含了协议版本、Client random、密码组件列表以及可选的SNI (Server Name Indication),即要连接服务器的域名信息。如果客户端使用ECDHE加密算法,那么还会包含该算法所支持的曲线列表。
  • Message2: "Server Hello"
    在服务器端接收到客户端发送过来的"Hello"消息之后,服务器将会从中提取出随后建立握手所需要的参数信息,比如ECDHE加密算法所支持的曲线列表等。而服务器的"Hello"消息中,则包含了服务器的Server random、服务器所选择的密码套件以及服务器的证书信息。基于RSA的握手和基于Diffie-Hellman握手从这点开始,将会产生区别,后者将会产生新的消息类型。
  • Message3: "Server key exchange"
    为了开始DH密钥交换过程,服务器将会提取一些启动参数,并将它们发送给客户端 - 类似于我们前面讨论过的ga,服务器也需要一种方式来证明其拥有对应的私钥,所以服务器在这点会利用其私钥计算一个所有消息的签名值,然后在这个消息中将Diffie-Hellman参数和签名值一并包含在这个消息中发送给客户端。
  • Message4: "Client key exchange"
    客户端在接收到服务器端发送过来的消息之后,会对证书可信性以及证书的所有者进行验证,检查证书是否可信以及证书的所有者是否是客户端要连接的服务器。同时还会对服务器发送过来的签名值进行验证,随后客户端将自己的Diffie-Hellman握手的客户端这一半的信息(也就是此前讨论的gb)发送给服务器。
    至此,服务器端和客户端都可以从DH参数(此前讨论过的gab)中计算出Pre-main secret密钥信息,通过这个计算出的Pre-main secret密钥以及Client randomServer random,服务器端和客户端就可以派生出相同的共享密钥Session key了。随后双方会发送一个简短消息表示下一条消息将会使用共享密钥加密之后传送。
    类似于RSA密钥交换过程,在双方完成了"Finished"消息传送之后,密钥交换过程就完成了。客户端和服务器端随后的消息都会使用这个Session key共享密钥进行对称加密之后然后传输给对方。

上述就是TLS的密钥交换过程。

3. https通信过程

所谓HTTPS (Hypertext Transfer Protocol Secure)通信在HTTP通信协议的基础上,增加了SSL/TLS用于加密传输的数据。HTTPS的初中是通过加密通信双方传输的数据实现对用户隐私的保护以及通信双方传输数据的完整性。另外,为了验证访问的目标服务器的身份,还需要有一个权威的证书机构(CA)来给服务器颁发证书,同时浏览器所在操作系统上会安装这些CA机构的受信根证书。CA颁发给服务器的证书中,包含了服务器的域名信息、服务器的公钥信息,与服务器公钥信息配对的私钥信息则保存在服务器上。有了证书、公私密钥等信息,就可以实现服务器与客户端之间的数据加密和身份验证了。

下面以客户端对https://www.domain.com这个域名的访问为例,说明HTTPS的通信过程,具体如下图所示:
在这里插入图片描述

  1. 客户端在浏览器中发起https://www.domain.com这个域名的访问请求,浏览器开始与目标服务器的TCP协议的443号端口建立连接,并向服务器发送所用的协议版本、加密组件列表以及Client random等信息,这个过程中传输的数据都是未经加密的明文数据。
  2. 服务器端在申请证书的时候,生成了公私密钥对,并且在生成的证书中,嵌入了自己的公钥信息、域名信息、Server random等。
  3. 服务器端在接收到客户端发来的HTTPS请求之后,将自己的证书发送给客户端,从中提取出客户端的Client random以及所用的加密组件列表等信息。
  4. 客户端在接收到服务器返回的服务器证书之后,TLS通过与服务器端的握手过程验证证书的有效性、是否可信以及证书的所有者是否为要访问的目标服务器,这些都验证通过之后(具体验证过程参见上面的TLS通信的握手过程),客户端根据从服务器证书中提取出的内容生成随机的Pre-main secret密钥。
  5. 客户端将生成的Pre-main secret密钥通过服务器端的公钥加密之后,发送给服务器端。与此同时,由于客户端此前接收到了服务器端的Server random信息,结合自身的Client random以及这里生成的Pre-main secret随机密钥计算出共享密钥Session key,由于计算这个密钥所需要的因素通过上述通信过程,服务器端和客户端之间的内容是一致的,所以这个共享密钥将会与后面服务器上计算出来的共享密钥是相同的。在此步之前的双方通信过程都是未经任何加密处理的明文通信过程。
  6. 服务器端接收到这个加密的信息之后,利用自己的私钥解密之后,从中提取出客户端生成的Pre-main secret随机密钥,并根据此前获得的客户端的Client random以及自身的Server random等信息,利用这三者计算出共享密钥Session key。服务器端在此处计算出的共享密钥应该与客户端上计算出来的共享密钥是相同的,因为这个共享密钥的计算方式是一一致的,双方上所拥有的彼此的信息也是一致的。
  7. 随后通信双方发送共享密钥已经生成完成,随后的数据将使用共享密钥加密之后进行传输,至此,HTTPS的通信过程的握手阶段完成,随后开始密文数据传输。
  8. 客户端和服务器端之间通过上述建立起来的共享密钥Session key,采用对称加密的方式将数据加密之后发送给对方,接收方收到数据之后,使用相同的Session key对收到的数据进行解密。至此,双方之间的数据通信都会采用共享密钥进行的对称加解密的方式来完成。

上述即为HTTPS的通信过程。

4. References

[1]. Keyless SSL: The Nitty Gritty Technical Details
[2]. Asymmetric Encryption
[3]. AES
[4]. ECDSA: The digital signature algorithm of a better internet
[5]. A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography
[6]. Network: Briefly on encryption and decryption in the HTTPS workflow

转载请注明出处!

这篇关于Linux随笔13-gawk数组、TLS密钥交换与https通信过程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!