理解 HTTPS 原理,SSL/TLS 协议

本贴最后更新于 1800 天前,其中的信息可能已经时移世异

为什么要使用 HTTPS

当我们使用 HTTP 协议时,传输的数据是不安全的,因为所有在客户端和服务端往来的数据都是明文:

为了解决这些问题,需要在 HTTP 协议中加入一个安全机制,由此并产生了 HTTPS,我们可以认为 HTTPS = HTTP + TLS/SSL。TLS/SSL 的引入解决了安全问题,而上层应用协议还是 HTTP。

历史

SSL(Secure Sockets Layer)中文称作“安全套接层”,TLS(Transport Layer Security),中文称作“传输层安全协议”。

  1. 1994 年,网景(NetScape)公司设计了 SSL 1.0
  2. 1995 年,SSL 2.0,存在严重漏洞
  3. 1996 年,SSL 3.0,得到大规模应用
  4. 1999 年,IETF 对 SSL 进行标准化,发布了 TLS 1.0
  5. 2006 年和 2008 年,TLS 进行了两次升级,分别为 TLS 1.1 和 TLS 1.2

在应用层,我们习惯将两者并称 TLS/SSL,因为它们设计大致相同,我们可以认为它们是同一个事物在不同历史阶段的名称。

前面我们介绍过,HTTPS 可以认为是 HTTP + TLS/SSL,所以我们只需要了解 TLS/SSL 原理即可。在进入原理之前,我们需要了解两个基础概念:数字证书、证书授权中心。

证书与授权

TLS/SSL 协议

  1. 由客户端发起握手,告诉服务器客户端支持的 TLS/SSL 版本、数据加密算法、以及一个随机数
    1.1. 服务端确认支持这个版本的 TLS/SSL、加密算法,并生成一个随机数
    1.2. 服务端将证书(带公钥)、服务端生成的随机数返回给客户端
  2. 客户端检查证书是否可信(和已有的 CA 列表对比,看是否是已有 CA 颁发的证书),并生成第三个随机数 PreMasterSecret
  3. 客户端使用证书带的公钥将 PreMasterSecret 进行加密,并通过之前交换的数据生成一个 Hash 值,发送给服务端,请求变更编码
    3.1. 服务端校验 Hash(确认不是假的客户端),并使用自己证书的私钥解密出 PreMasterSecret
    3.2. 服务端根据之前的随机数和约定的加密算法,生成用于加密后续传输数据的会话密钥 SessionSecret
    3.3. 服务端根据之前交换的数据生成一个 Hash 值,发送给客户端,确认开始变更编码
  4. 客户端校验 Hash (确认不是假的服务端),并生成会话密钥
  5. 客户端使用会话密钥加密数据,并发送给服务端
    5.1. 服务端使用会话密钥解密数据,执行业务逻辑后产生数据
    5.2. 服务端使用会话密钥加密数据,返回给客户端
  6. 客户端使用会话密钥解密数据,完成一次和服务端的数据交换

在这个过程中,有几个关键点:

在握手阶段,安全与否的关键在于 PreMasterSecret 是否能够被破解,虽然理论上通过 RSA 算法加密是比较安全的,但还是有破解的可能性。最安全的做法是不发送 PreMasterSecret,而是根据一系列参数由客户端和服务端分别计算出 PreMasterSecret,这个算法就是迪菲-赫尔曼密钥交换

另外,TLS/SSL 不只适用于 HTTP 的安全问题,它也可以和其他应用层协议搭配使用。

应用

通过上述介绍,我们得知使用 SSL/TLS 需要做如下准备:

下面我们针对自建 CA 做一个完整的示例(证书相关使用 Linux 的 openssl 命令,客户端、服务端使用 Golang)。

建立 CA

颁发证书

最终我们需要的就是公钥 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 不在系统自带的“受信任的根证书颁发机构”中。

参考

打赏 50 积分后可见
50 积分 • 3 打赏
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    60 引用 • 182 回帖 • 560 关注
  • HTTPS
    93 引用 • 263 回帖 • 3 关注
  • CA
    2 引用 • 12 回帖
  • golang

    Go 语言是 Google 推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发 Go,是因为过去 10 多年间软件开发的难度令人沮丧。Go 是谷歌 2009 发布的第二款编程语言。

    433 引用 • 1344 回帖 • 667 关注

赞助商 我要投放

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...
  • kuuyee

    好文! 👍

    有完整的例子吗?go 语言的

  • 88250
    作者

    @kuuyee 上面的示例代码已经比较完整了,改下证书的实参就可以的。

  • someone

    我也想学下,但好难啊

  • someone

    其实博主这样写还是太粗糙了,其实可以这样,直接看 open ssl 源代码,从底层分析 ssl 协议的几个过程,里面的加密套件或则加密算法等等,这样更好理解。
    我的微信:chenyu_920310 求加