tls握手协议
一些概念:
Cipher suite(加密套件):这是一个由一组加密算法的名称组成的唯一标识符,在上图第 1 步中,用于客户端向服务端询问 TLS 通信采用的加密方案
每种Cipher的名字里包含了四部分信息,分别是:
1.密钥交换算法,用于决定客户端与服务器之间在握手的过程中如何认证,用到的算法包括RSA,Diffie-Hellman,ECDH,PSK等
2.加密算法,用于加密消息流,该名称后通常会带有两个数字,分别表示密钥的长度和初始向量的长度,比如DES 56/56, RC2 56/128, RC4 128/128, AES 128/128, AES 256/256
3.报文认证信息码(MAC)算法,用于创建报文摘要,确保消息的完整性(没有被篡改),算法包括MD5,SHA等。
4.PRF(伪随机数函数),用于生成“master secret”。
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA,从其名字可知,它是
1.基于TLS协议的;
2.使用ECDHE、RSA作为密钥交换算法;
3.加密算法是AES(密钥和初始向量的长度都是256);
4.MAC算法(这里就是哈希算法)是SHA。
1.密钥交换算法,一般是一个非对称加密算法,用于图中客户端使用公钥加密预主密钥(Premaster secret),服务端使用私钥解密的过程
2.加密算法,一般是一个对称加密算法,以握手生成的 Session key 为公钥,加密来自应用层的消息体
3.报文认证信息码(MAC)算法,一般是一个散列(Hash)函数,所以通常又称为 HMAC,用于对内容进行数字签名(digital signature)即验证消息完整性,以及进行对端的身份验证。
client random:这是一个由客户端生成的 32 字节序列,在每次连接中都是唯一的,一般应该以 4 位的时间戳 + 28 位的随机字节组成
server random:由服务端生成
Pre-Master(预主密钥):这是一个 48 字节的 blob 数据,一般是结合客户端随机数与服务端随机数,使用伪随机函数(Pseudorandom Function)生成的
Session key(会话密钥):这是 TLS 握手的最终目的,在握手的最后,客户端与服务端都能够拥有这个 Session key,因此可以将其作为对称加密的密钥,对双方的信息进行加解密
握手的具体过程:
1.Client Hello:客户端发送一条 Hello 信息发起握手,消息包含客户端要使用的 TLS 协议版本、Client random、Cipher suite、以及一些其他的必要信息
2.Server Hello:服务端收到客户端的 Hello 信息,根据信息中提供的加密套件列表,决定最终的握手类型。最后返回给客户端一条 Server Hello 消息,消息包含服务端随机数、最终确定使用的加密套件列表、以及服务端的证书(如 HTTPS 证书,证书中包含 RSA 生成的公钥和服务器的域名)
3.客户端验证证书,生成预主密钥:Server Hello 送来的证书经验证证书可信且站点归属正确后,客户端会将 Client random 与 Server random 结合,使用伪随机函数(Pseudorandom Function)生成 Pre-master secret,并使用证书中的公钥加密预主密钥,将其发送到服务端
4.服务端使用私钥解密,获取预主密钥:服务端接收到加密后的 Pre-master secret,使用之前 RSA 生成的与公钥配对的私钥进行解密,获得与客户端相同的 Pre-master secret
5.生成 Session key:此时客户端与服务端都通过密钥交换的过程,得到了相同的 Client random、Server random 与 Pre-master secret,客户端与服务端便可以各自推导出相同的 Session key,后续的通信内容则会使用这个 Session key 作为 AES_128_GCM 的密钥进行加解密
6.客户端与服务端交换加密的握手结束消息,并进行 HMAC 验证:生成会话密钥(Session key)之后,客户端与服务端会使用 Session key 加密消息体,交换一次 Finish 消息,表示握手正式完成,确认双方都能正常的使用 Session key 进行加密的数据传输,同时在 Finish 消息中使用 HMAC 进行数字签名验证以验证握手阶段的完整性
参考:juejin.cn/post/693943…
github.com/halfrost/Ha…
证书的签发(Signing)和认证(Verification)的过程:
签发证书的步骤:
Signing阶段,首先撰写证书的元信息:签发人(Issuer)、地址、签发时间、过期失效等;当然,这些信息中还包含证书持有者(owner)的基本信息,例如owner的DN(DNS Name,即证书生效的域名),owner的公钥等基本信息。 通过Issuer(CA)的证书指定的Hash算法将信息摘要提取出来; Hash摘要通过Issuer(CA)私钥进行非对称加密,生成一个签名密文; 将签名密文attach到文件证书上,使之变成一个签名过的证书。
验证证书的步骤:
Verification阶段,浏览器获得之前签发的证书; 将其解压后分别获得“元数据”和“签名密文”; 将Issuer(CA)的证书指定的Hash算法应用到“元数据”获取摘要A; 将"签名密文"通过Issuer(CA)的公钥解密获得摘要B。 比对摘要A和摘要B,如果匹配,则说明这个证书是被CA验证过合法证书,里面的公钥等信息是可信的。 在Verification阶段,解密Signature获得摘要需要通过签发者(Issuer)的公钥,又该如何获得这个公钥,同时确保这个公钥是有效的呢?就是下面的证书链的内容
创建ssl证书
自签名:
1.生成私钥
$ openssl genrsa -out server.key 2048
2.生成 CSR (Certificate Signing Request)
$ openssl req \
-subj "/C=CN/ST=Tianjin/L=Tianjin/O=Mocha/OU=Mocha Software/CN=test1.sslpoc.com/emailAddress=test@mochasoft.com.cn" \
-new \
-key server.key \
-out server.csr
3.生成自签名证书
$ openssl x509 \
-req \
-days 3650 \
-in server.csr \
-signkey server.key \
-out server.crt
私有 CA 签名:
1.创建 CA 私钥
$ openssl genrsa -out ca.key 2048
2.生成 CA 的自签名证书
$ openssl req \
-subj "/C=CN/ST=Tianjin/L=Tianjin/O=Mocha/OU=Mocha Software/CN=Server CA/emailAddress=test@mochasoft.com.cn" \
-new \
-x509 \
-days 3650 \
-key ca.key \
-out ca.crt
3.生成需要颁发证书的私钥
$ openssl genrsa -out server.key 2048
4.生成要颁发证书的证书签名请求,证书签名请求当中的 Common Name 必须区别于 CA 的证书里面的 Common Name
$ openssl req \
-subj "/C=CN/ST=Tianjin/L=Tianjin/O=Mocha/OU=Mocha Software/CN=test2.sslpoc.com/emailAddress=test@mochasoft.com.cn" \
-new \
-key server.key \
-out server.csr
5.用 2 创建的 CA 证书给 4 生成的 签名请求 进行签名
$ openssl x509 \
-req \
-days 3650 \
-in server.csr \
-CA ca.crt \
-CAkey ca.key \
-set_serial 01 \
-out server.crt
链接:www.jianshu.com/p/e5f46dcf4…
demo
先创建证书
server.go:
package main
import (
"bufio"
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
"net"
)
func handleConn(conn net.Conn) {
defer conn.Close()
r := bufio.NewReader(conn)
for {
msg, err := r.ReadString('\n')
if err != nil {
log.Println(err)
return
}
println(msg)
n, err := conn.Write([]byte("world\n"))
if err != nil {
log.Println(n, err)
return
}
}
}
func main() {
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
log.Println(err)
return
}
certBytes, err := ioutil.ReadFile("../client/client.crt")
if err != nil {
panic("Unable to read cert.pem")
}
clientCertPool := x509.NewCertPool()
ok := clientCertPool.AppendCertsFromPEM(certBytes)
if !ok {
panic("failed to parse root certificate")
}
config := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientAuth: tls.VerifyClientCertIfGiven,
ClientCAs: clientCertPool,
}
ln, err := tls.Listen("tcp", ":443", config)
if err != nil {
log.Println(err)
return
}
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil {
log.Println(err)
continue
}
go handleConn(conn)
}
}
client.go
package main
import (
"crypto/tls"
"crypto/x509"
"io/ioutil"
"log"
)
func main() {
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
log.Println(err)
return
}
certBytes, err := ioutil.ReadFile("../server/server.crt")
if err != nil {
panic("Unable to read cert.pem")
}
clientCertPool := x509.NewCertPool()
ok := clientCertPool.AppendCertsFromPEM(certBytes)
if !ok {
panic("failed to parse root certificate")
}
conf := &tls.Config{
ServerName: "17.chl.com",
RootCAs: clientCertPool,
Certificates: []tls.Certificate{cert},
//InsecureSkipVerify: true,
}
conn, err := tls.Dial("tcp", "127.0.0.1:443", conf)
if err != nil {
log.Println(err)
return
}
defer conn.Close()
n, err := conn.Write([]byte("hello\n"))
if err != nil {
log.Println(n, err)
return
}
buf := make([]byte, 100)
n, err = conn.Read(buf)
if err != nil {
log.Println(n, err)
return
}
println(string(buf[:n]))
}
执行命令
$ go run server.go
$ go run client.go
tls.Config
server:
&tls.Config{
ClientAuth: tls.VerifyClientCertIfGiven,
Certificates: []tls.Certificate{cert},
ClientCAs: certPool,
}
client:
&tls.Config{
ServerName: "17.chl.com",
RootCAs: clientCertPool,
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: true,
}
Certificates:
Certificates 包含一个或多个证书链以呈现给连接的另一端。 自动选择与对等方要求兼容的第一个证书。
ClientAuth:
1.NoClientCert 表示在握手期间不应请求客户端证书,并且如果发送任何证书,则不会对其进行验证。
2.RequestClientCert 表示在握手期间应请求客户端证书,但不要求客户端发送任何证书。
3.RequireAnyClientCert 表示在握手过程中需要请求客户端证书,并且至少需要客户端发送一个证书,但不需要该证书是有效的。
4.VerifyClientCertIfGiven 表示在握手期间应请求客户端证书,但不要求客户端发送证书。 如果客户端确实发送了证书,则它必须是有效的。
5.RequireAndVerifyClientCert 表示握手时需要申请客户端证书,并且要求客户端至少发送一个有效证书。
ClientCAs:
ClientCAs 定义了服务器设置的一组根证书颁发机构,如果需要通过 ClientAuth 中的策略验证客户端证书。
RootCA:
RootCA 定义了客户端在验证服务器证书时使用的一组根证书颁发机构。 如果 RootCAs 为零,则 TLS 使用主机的根 CA 集。
InsecureSkipVerify:
InsecureSkipVerify 控制客户端是否验证服务器的证书链和主机名。如果 InsecureSkipVerify 为 true,则 TLS 接受服务器提供的任何证书以及该证书中的任何主机名。 在这种模式下,TLS 容易受到中间人攻击。 这应该仅用于测试。