为什么要使用 HTTPS
当我们使用 HTTP 协议时,传输的数据是不安全的,因为所有在客户端和服务端往来的数据都是明文:
- 第三方可以获取到真实数据
- 第三方可以篡改数据
- 第三方可以冒充服务端或客户端
为了解决这些问题,需要在 HTTP 协议中加入一个安全机制,由此并产生了 HTTPS,我们可以认为 HTTPS = HTTP + TLS/SSL。TLS/SSL 的引入解决了安全问题,而上层应用协议还是 HTTP。
历史
SSL(Secure Sockets Layer)中文称作“安全套接层”,TLS(Transport Layer Security),中文称作“传输层安全协议”。
- 1994 年,网景(NetScape)公司设计了 SSL 1.0
- 1995 年,SSL 2.0,存在严重漏洞
- 1996 年,SSL 3.0,得到大规模应用
- 1999 年,IETF 对 SSL 进行标准化,发布了 TLS 1.0
- 2006 年和 2008 年,TLS 进行了两次升级,分别为 TLS 1.1 和 TLS 1.2
在应用层,我们习惯将两者并称 TLS/SSL,因为它们设计大致相同,我们可以认为它们是同一个事物在不同历史阶段的名称。
前面我们介绍过,HTTPS 可以认为是 HTTP + TLS/SSL,所以我们只需要了解 TLS/SSL 原理即可。在进入原理之前,我们需要了解两个基础概念:数字证书、证书授权中心。
证书与授权
- 数字证书(Digital Certificate)是用来证明公钥(非对称密钥算法中用于加密的密钥)所有者身份的。我们人人都可以自己生成一个公钥,但是这个公钥是否能代表是你的,这个认证的过程需要一个权威机构执行,这个机构就是证书授权中心。
- 证书授权中心(Certificate Authority)负责证书颁发。CA 是行业内信得过的组织机构,它具有权威性,由它颁发的证书大家都相信是可靠的。
TLS/SSL 协议
- 由客户端发起握手,告诉服务器客户端支持的 TLS/SSL 版本、数据加密算法、以及一个随机数
1.1. 服务端确认支持这个版本的 TLS/SSL、加密算法,并生成一个随机数
1.2. 服务端将证书(带公钥)、服务端生成的随机数返回给客户端 - 客户端检查证书是否可信(和已有的 CA 列表对比,看是否是已有 CA 颁发的证书),并生成第三个随机数 PreMasterSecret
- 客户端使用证书带的公钥将 PreMasterSecret 进行加密,并通过之前交换的数据生成一个 Hash 值,发送给服务端,请求变更编码
3.1. 服务端校验 Hash(确认不是假的客户端),并使用自己证书的私钥解密出 PreMasterSecret
3.2. 服务端根据之前的随机数和约定的加密算法,生成用于加密后续传输数据的会话密钥 SessionSecret
3.3. 服务端根据之前交换的数据生成一个 Hash 值,发送给客户端,确认开始变更编码 - 客户端校验 Hash (确认不是假的服务端),并生成会话密钥
- 客户端使用会话密钥加密数据,并发送给服务端
5.1. 服务端使用会话密钥解密数据,执行业务逻辑后产生数据
5.2. 服务端使用会话密钥加密数据,返回给客户端 - 客户端使用会话密钥解密数据,完成一次和服务端的数据交换
在这个过程中,有几个关键点:
- 前两次的随机数(客户端随机数、服务端随机数)是明文传输的
- 非对称密钥算法只被使用了一次,即客户端使用证书公钥加密 PreMasterSecret,服务端使用证书私钥解密出 PreMasterSecret
- 应用数据的传输使用的是对称密钥算法,客户端/服务端都使用会话密钥进行加/解密
在握手阶段,安全与否的关键在于 PreMasterSecret 是否能够被破解,虽然理论上通过 RSA 算法加密是比较安全的,但还是有破解的可能性。最安全的做法是不发送 PreMasterSecret,而是根据一系列参数由客户端和服务端分别计算出 PreMasterSecret,这个算法就是迪菲-赫尔曼密钥交换。
另外,TLS/SSL 不只适用于 HTTP 的安全问题,它也可以和其他应用层协议搭配使用。
应用
通过上述介绍,我们得知使用 SSL/TLS 需要做如下准备:
- 数字证书:可以找权威的证书授权中心颁发证书(有付费的,也有免费的),也可以自己做 CA,然后自己给自己颁发证书
- 服务端使用证书,比如配置 NGINX 来使用
- 客户端使用 SSL/TLS 协议和服务端进行通讯,如果是自己的 CA 颁发的证书,还需要在客户端导入 CA 的根证书
下面我们针对自建 CA 做一个完整的示例(证书相关使用 Linux 的 openssl 命令,客户端、服务端使用 Golang)。
建立 CA
- 生成 CA 私钥:
openssl genrsa -out ca.key 2048
- 生成 CA 根证书:
openssl req -new -x509 -days 3650-key ca.key -out ca.crt
颁发证书
- 生成证书私钥:
openssl genrsa -out auto.pem 1024
- 生成证书公钥:
openssl rsa -in auto.pem -out auto.key
- 生成签名请求:
openssl req -new -key auto.pem -out auto.csr
,其中的 Common Name 一定要填写客户端访问时的域名,并且不能是 IP - CA 签名(颁发)证书:
openssl x509 -req -sha256 -in auto.csr -CA ca.crt -CAkey ca.key -CAcreateserial -days 3650 -out auto.crt
最终我们需要的就是公钥 auto.key
以及证书 auto.crt
。
服务端使用证书
http.ListenAndServeTLS(Auto.Server, "auto.crt", "auto.key", nil)
客户端发起请求
这里假设客户端已经获得了服务端的证书。
// getClient 根据传入的 url 实参中协议部分返回适当的 HTTP 客户端.
func getClient(url string) *http.Client {
if !strings.Contains(url, "https://") {
return &http.Client{}
}
certName := strings.Split(url, "https://")[1]
certName = strings.Split(certName, ":")[0]
// Load client cert
cert, err := tls.LoadX509KeyPair("certs/"+certName+".crt", "certs/"+certName+".key")
if err != nil {
logger.Error(err)
}
// Load CA cert
caCert, err := ioutil.ReadFile("certs/ca.crt")
if err != nil {
logger.Error(err)
}
caCertPool := x509.NewCertPool()
ok := caCertPool.AppendCertsFromPEM(caCert)
if !ok {
logger.Error("Load CA cert failed")
}
// Setup HTTPS client
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs: caCertPool,
}
tlsConfig.BuildNameToCertificate()
transport := &http.Transport{TLSClientConfig: tlsConfig}
return &http.Client{Transport: transport}
}
其中载入了 CA 证书,原因就是因为我们自建的 CA 不在系统自带的“受信任的根证书颁发机构”中。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于