游戏服务器TLS实践

游戏服务器TLS实践

四月 15, 2020

#游戏服务器TLS实践

客户端和服务器的TCP通信需要加密, 这里首选TLS(Transport Layer Security 传输层安全性协议

安全传输层协议(TLS)用于在两个通信应用程序之间提供保密性和数据完整性。

TLS原理浅析

TLS可以防止数据交换时被窃听篡改

The TLS protocol aims primarily to provide privacy and data integrity between two or more communicating computer applications.

  1. 每个连接都会独立生成秘钥对传输数据进行加密, 生成秘钥的过程就是TLS握手

  2. 为了保证秘钥不被窃听和修改, 我们需要对秘钥生成的过程进行加密

    通信双方通过交换一个密文,通过这个密文来生成秘钥。TLS使用RSA来交换密文
    客户端随机生成一个数, 使用公钥加密, 服务器使用私钥解密

    RSA加密算法是一种非对称加密算法。加密密钥(即公开密钥)PK是公开信息,而解密密钥(即秘密密钥)SK是需要保密的。加密算法E和解密算法D也都是公开的。虽然解密密钥SK是由公开密钥PK决定的,由于无法计算出大数n的欧拉函数phi(N),所以不能根据PK计算出SK。

    之所以使用RSA来进行加密随机数, 而不是整个通信数据, 是因为

    由于进行的都是大数计算,使得RSA最快的情况也比DES慢上好几倍,无论是软件还是硬件实现。速度一直是RSA的缺陷。一般来说只用于少量数据加密。RSA的速度是对应同样安全级别的对称密码算法的1/1000左右。

    使用证书认证(CA, certificate authority)防止中间人攻击。

  3. 对称加密算法有很多, TLS为了扩展性, 通信双发在一开始需要交换各自支持的加密算法, 然后选取一个双方都支持的算法。

下面使用流程图来解析整个握手过程

lbb-001-tls.png

###go代码实践

#####服务器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
func main() {
cert, err := tls.LoadX509KeyPair("server.crt", "server.key")
if err != nil {
panic(err)
}

certPEMBlock, err := ioutil.ReadFile("ca.crt")
if err != nil {
panic(err)
}
capool := x509.NewCertPool()
capool.AppendCertsFromPEM(certPEMBlock)

cfg := &tls.Config{
Certificates: []tls.Certificate{cert},
ClientCAs:capool,
ClientAuth: tls.RequireAndVerifyClientCert,
}
listener, err := tls.Listen("tcp", ":2000", cfg)
if err != nil {
panic(err)
}

for {
conn, err := listener.Accept()
if err != nil {
println(err.Error())
continue
}
go func() {
txtReader := textproto.NewReader(bufio.NewReader(conn))
line, err := txtReader.ReadLine()
println(line)
if err != nil {
println(err.Error())
return
}

textWriter := textproto.NewWriter(bufio.NewWriter(conn))
err = textWriter.PrintfLine("OK\n")
if err != nil {
println(err.Error())
return
}
}()
}
}

######LoadX509KeyPair

加载成对的秘钥, 私钥: server.key, 证书: server.crt

LoadX509KeyPair reads and parses a public/private key pair from a pair of files. The files must contain PEM encoded data

server.key里的内容就是RSA中的私钥, 可以用以下命令获取(openssl命令的具体参数可以用man openssl查看)

1
openssl genrsa -out server.key 1024

The genrsa command generates an RSA private key, which essentially involves the generation of two prime numbers.

server.crt是RSA中的证书, 是用CA的私钥对公钥和相关信息进行签名生成的证书,从证书里面可以拿到公钥

server.crt的生成过程分为两步

生成证书请求, 这一步就是用私钥和用户信息生成一份证书请求信息server.csr

1
openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=GZ/L=ZH/O=KSS/OU=KSS/CN=xxx/emailAddress=xxx@xxx"

The req command primarily creates and processes certificate requests in PKCS#10 format. It can additionally create self-signed certificates, for use as root CAs, for example.

然后根据server.csr和ca私钥生成证书server.crt

1
openssl ca -in server.csr -cert ca.crt -keyfile ca.key -out server.crt -days 3650

The ca command is a minimal certificate authority (CA) application. It can be used to sign certificate requests in a variety of forms and generate certificate revocation lists (CRLs).

Certificates 存放多个证书链, 是用来在TLS握手阶段交换证书用的。
######ClientCAs ClientAuth 服务器验证客户端证书

ClientCAs是服务器用来存储验证客户端证书的一组根CA证书

ClientCAs defines the set of root certificate authorities that servers use if required to verify a client certificate by the policy in ClientAuth.

证书之间是链式关系, 比如是用证书A签名证书B, 如果我们信任证书A, 那么也会信任证书B。在上述代码中我们把ca证书(ca.crt)设置为根证书,表明我们信任ca证书。服务器在握手阶段拿到客户端的证书, 看其证书链是否受信任(用ca公钥解密,如果成功说明是用根证书签发的)。

根证书一般来自于权威机构。当前也可以自签发生成根证书

1
2
3
# cakey 和 ca.csr 和之前的生成方式一样
openssl x509 -req -days 365 -sha1 -extensions v3_ca -signkey cakey.pem
-in ca.csr -out certs/ca.cer

#####客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func main() {
cert, err := tls.LoadX509KeyPair("client.crt", "client.key")
if err != nil {
panic(err)
}

certPEMBlock, err := ioutil.ReadFile("ca.crt")
if err != nil {
panic(err)
}
capool := x509.NewCertPool()
capool.AppendCertsFromPEM(certPEMBlock)

cfg := &tls.Config{
Certificates: []tls.Certificate{cert},
RootCAs:capool,
ServerName:"xxxxx",
//InsecureSkipVerify:true,
}

conn, err := tls.Dial("tcp", "localhost:2000", cfg)
if err != nil {
panic(err)
}

textWriter := textproto.NewWriter(bufio.NewWriter(conn))
err = textWriter.PrintfLine("hello world\n")
if err != nil {
panic(err)
}

txtReader := textproto.NewReader(bufio.NewReader(conn))
line, err := txtReader.ReadLine()
println(line)
if err != nil {
println(err.Error())
return
}
conn.Close()
}

RootCAs是用来验证服务器的证书的一组根证书
ServerName验证服务器的主机名