去年某客户需要使用HTTP/2业务来做人工直播内容审核用。具体操作应该为一个大屏幕,一个chrome浏览器,浏览器使用HTTP/2可以复用一个连接,向节点同时请求几十路流,随后进行审核。

由于彼时公司所使用的cache软件和前端反向代理软件均不支持HTTP/2,所以在客户要求的区域,搭建了两台NGINX服务的机器,由客户使用hosts访问的形式进行服务。

近期该客户所使用的证书过期,而由于该两台机器为人工配置,未自动更新新的证书,导致该服务异常。而客服人员所提供的新的证书没有包含私钥,导致证书一度无法更新。

之前对于HTTPs业务一直未有真的接触,此次趁机学习一记。

加密方式与应用

对称加密

对称加密的主要原理是,数据交互双方使用事先通过一定途径约定好的秘钥对需要传输的消息进行加密,并使用同样的秘钥对收到的加密消息进行解密。

由于使用的加解密的秘钥一样,所以称为对称加密。常见的对称加密方式有DES、3DES、TDEA、Blowfish、RC2、RC4、RC5、IDEA、SKIPJACK、AES等。

非对称加密

非对称加密区别于对称加密的一点是,数据交互双方持有的秘钥不一样,一般用户持有的秘钥称为公钥,而服务端持有的秘钥为私钥。公钥可以公开,服务端对于任何请求方都可以响应其公钥。而私钥只能是服务端持有。

当需要进行加密通信时,服务端将公钥提供给用户并携带一个随机数,用户利用公钥和随机数将需要传输的消息加密。服务端通过私钥解密,得到明文消息。

服务端对需要响应的消息利用hash算法生成摘要,并利用私钥将摘要加密,形成数字签名。当客户端收到响应后,利用公钥解开数字签名,得到摘要,然后对收到的消息利用同样的hash算法生成摘要,对比两者摘要是否相同。如果相同,则认为响应消息来自指定服务端。

常见的非对称加密有:RSA、Elgamal、背包算法、Rabin、D-H、ECC(椭圆曲线加密算法)。

实际应用

非对称加解密的计算消耗要比对称加解密大很多,所以实际应用一般采用两者的结合达到速度和安全的两全其美。即:使用非对称机密方式传输对称加密的秘钥。用对称加密传输实际的消息。

CA, certificate authority

剩下的问题是如何在在非对称加密时,把服务器的公钥传输给请求方了。若是直接传输,公钥在传输过程如果发生劫持被替换成其他公钥怎么办。

以上问题总结下来便是如何证明收到的公钥便是便是实际请求响应方给的呢。

此处引入一个数字摘要的概念。消息摘要是将公钥和其他证书申请者的信息作为hash输入(预映射)而得到的hash值。由于hash唯一性,如果中途被篡改,则相应的消息摘要将发生变化,无法校验通过。

为了防止中间劫持方修改整个公钥和其他预映射的信息,并生成新的消息摘要,服务端在提供消息摘要时,会使用有权威性的证书授权中心的私钥进行加密,而这些权威性的证书授权中心的公钥则预设在我们的操作系统或者浏览器中。当中途证书被劫持篡改后,将无法校验通过。

上述权威性的证书授权中心即所谓的CA,certificate authority,比较著名的有Symantec,GeoTrust和新兴的免费的Let's Encrypt等。

HTTPS交互

使用cURL发起一次HTTPS的请求,cURL记录的响应如下:

/usr/local/curl-7.55.1/bin/curl --http2 --cacert /etc/pki/tls/certs/ca-bundle.crt https://pull-flv-l1-hs-admin.pstatp.com/live/livestream.flv -v
*   Trying 110.242.21.20...
* TCP_NODELAY set
* Connected to pull-flv-l1-hs-admin.pstatp.com (110.242.21.20) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* Cipher selection: ALL:!EXPORT:!EXPORT40:!EXPORT56:!aNULL:!LOW:!RC4:@STRENGTH
* successfully set certificate verify locations:
*   CAfile: /etc/pki/tls/certs/ca-bundle.crt
 CApath: none
* TLSv1.2 (OUT), TLS header, Certificate Status (22):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS change cipher, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.pstatp.com
*  start date: Dec 21 00:00:00 2017 GMT
*  expire date: Dec 26 12:00:00 2020 GMT
*  subjectAltName: host "pull-flv-l1-hs-admin.pstatp.com" matched cert's "*.pstatp.com"
*  issuer: C=US; O=DigiCert Inc; OU=www.digicert.com; CN=RapidSSL RSA CA 2018
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x10cc020)
> GET /live/livestream.flv HTTP/2
> Host: pull-flv-l1-hs-admin.pstatp.com
> User-Agent: curl/7.55.1
> Accept: */*
> 
* Connection state changed (MAX_CONCURRENT_STREAMS updated)!
< HTTP/2 200 
< date: Sun, 25 Mar 2018 03:08:54 GMT
< content-type: video/x-flv
< cache-control: no-cache
< expires: -1
< pragma: no-cache
< 
Warning: Binary output can mess up your terminal. Use "--output -" to tell 
Warning: curl to output it to your terminal anyway, or consider "--output 
Warning: <FILE>" to save to a file.
* Failed writing body (0 != 13)
* Closing connection 0
* TLSv1.2 (OUT), TLS alert, Client hello (1):

可以看到cURL在tcp建联后,进入了TLSv1.2的SSL层数据交互。

首先Client端发起Client Hello,会提供4字节的unix时间和用于后续使用的28字节随机数random_C。并且会携带客户端支持所有加密套件(Cipher Suites)名称,以供服务器选择,如下图一所示。

图一
图一

并且可能会在扩展信息(Extension)中携带请求URL的server name信息,方便服务器决定使用哪本SNI证书。关于SNI证书,可以查看维基百科。请求如下图二所示。

图二
图二

服务端收到Client Hello后会相应响应给客户端Server Hello,会提供服务端的4字节的unix时间和用于后续使用的28字节随机数random_S,并且选定客户端提供的支持的加密套件中的一种,如下图三中所示,选择是**TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256**,其具体的含义为:

  • 基于TLS协议;

  • 使用ECDHE作为秘钥交换算法;

  • RSA作为签名认证;

  • AES_128_GCM作为对称加密算法;

  • SHA256作为摘要算法;

再深入的信息,估计是要看几本大部头的书才能理解了。

图三
图三

服务端在发送Server Hello之后,便会将相关的证书(包含公钥)发送给客户端,该操作称为Certificate,如下图四所示,其中包含的证书信息如下图五所示。

图四
图四
图五
图五

此次加密通信中,使用的是ECDHE算法作为秘钥交换算法。所以服务端在发送Certificate消息后还会给出用于ECDHE加密使用的信息,称作Server Key Exchange。如下图六,服务端提供了加密算法所需的Pubkey以及对数据做了一次签名。

图六
图六

到此,服务端所需传递的数据均已完成,服务端发送一个Server Hello done消息,表示完成。如下图七。

图七
图七

接下来轮到客户端表演了。在收到服务端的Server Key Exchange之后,客户端会进行秘钥交换,做相应的Client Key Exchange动作,交换一些加密参数。如下图八。

此时,不出意外,服务端和客户端就可以进行加密通信了。客户端发送ChangeCipher Spec消息,表示接下来开始加密通信,随后发送一个加密的握手消息Encrypted Handshake Message进行验证。如下图八。

图八
图八

服务端收到客户端开始加密通信的消息和加密的握手消息后,相应的也响应一个开始加密通信的消息Change Cipher Spec,并且也发送一个加密的握手消息Encrypted Handshake Message。如图十所示。

图九
图九

至此,加密通道完成建立,开始进行加密通信。直到客户端发起Encrypted Alert,表示结束为止。

总结下来的流程图大致如此:

Client->Server: Client Hello
Server->Client: Server Hello
Server->Client: Certificate
Server->Client: Server Key Exchange
Server->Client: Server Hello Done
Client->Server: Client Key Exchange
Client->Server: Change Cipher Spec
Client->Server: Encrypted Handshake Message
Server->Client: Change Cipher Spec
Server->Client: Encrypted Handshake Message
Client->Server: Application Data
Server->Client: Application Data
Client-->Server: ...
Server-->Client: ...
Client->Server: Encrypted Alert

详细的加密算法基本也不用了解。太高深了。

NGINX证书配置

NGINX作为proxy使用时的相关配置。

对外域名为pull.test.com,启用HTTPHTTPS,并且启用HTTP2

server {
	server_name pull.test.com;
	listen [::]:443 ssl http2 reuseport;
	listen [::]:80 http2 reuseport; 
	ssl_certificate /SSL_cert/test.com.crt;
	ssl_certificate_key /SSL_cert/test.com.key;
	ssl_trusted_certificate /SSL_cert/test.com.ca;    
	ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK; 
	            
	location / {
		set $s_host ori.pull.test.com;
		proxy_set_header Host $s_host;
		more_set_headers "Access-Control-Allow-Origin:*";
		proxy_pass http://127.0.0.1:xxx;
	}
	//test URL
	location = /live/livestream.flv {
		set $s_host test.test.com;
		proxy_set_header Host $s_host;d
		proxy_pass http://127.0.0.1:xxx;
	}
}