Go 语言快速入门指南:TLS 安全传输层协议

开发 前端
传统环境有丰富的工具和既定的安全最佳实践,但对于云原生应用来说,情况就不一样了,云原生应用往往采取几个小的、往往是短暂的微服务的形式。

安全问题,让人既爱又恨,不管是云原生应用还是本地或其他,安全是任何应用程序的一个重要特性。但往往很多时候,安全问题被忽视,成为最后考虑的问题,这样的结果就是:一旦出现问题,就会带来灾难性结果。

传统环境有丰富的工具和既定的安全最佳实践,但对于云原生应用来说,情况就不一样了,云原生应用往往采取几个小的、往往是短暂的微服务的形式。虽然这种架构提供了巨大的灵活性和可扩展性优势,但它也为潜在的攻击者创造了一个明显的机会:服务之间的每一次通信都是通过网络传输的,从而使其被窃听和篡改。

1.TLS 简要介绍

TLS(Transport Layer Security,安全传输层),是一种加密协议,旨在通过计算机网络提供通信安全。TLS 是建立在传输层 TCP 协议之上的协议,服务于应用层,它的前身是 SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由 TCP 进行传输的功能。

TLS 确保没有第三方可以窃听或篡改任何消息。它的使用是无处不在和广泛的,它在 RFC 8446 中进行了描述。

当客户端通过普通 HTTP 连接到服务器时,它开始在完成标准 TCP 握手(SYN-> SYN -ACK-> ACK)后立即发送 TCP 数据包包裹的明文数据。使用 TLS ,情况更加复杂。

图片

如上图中,完成 TCP 握手后,服务器和客户端执行 TLS 握手,以接收双方共同的私钥并以此进行特定的会话。然后,利用公钥加密来保证它们之间交换的所有数据安全。尽管这个过程发生了很多事情,但这是 TLS 层为我们实现的东西。我们只需要正确设置TLS服务器(或客户端)即可;

GO 中 HTTP 和 HTTPS 服务器之间的实际差异很小。

2.TLS 的优点

  • 加密: TLS/SSL 可以帮助使用加密保护传输的数据
  • 互操作性:TLS/SSL 适用于大多数 Web 浏览器,包括 Microsoft Internet Explorer 以及大多数操作系统和 Web 服务器
  • 算法灵活性:TLS/SSL 为安全会话期间使用的身份验证机制、加密算法和散列算法提供操作
  • 易于部署:许多应用程序 TLS/SSL 临时在 windows server 2003 操作系统上
  • 使用方便:因为我们在应用层下实现了 TLS/SSL,所以它的大部分操作对客户端是完全不可见的

3.公钥加密技术

在介绍 Go 使用 TLS 中如何设置 HTTPS 服务器的代码之前,让我们谈谈证书。在上面的图中,您会注意到服务器是其第一个 ServerHello 消息的一部分,将证书发送给客户端。正式地,这些被称为 X.509 证书,由 RFC 5280 描述。

证书是将服务器的公钥及其身份和受信任机构(通常是证书授权)签名的标准方法。举个例子,在以前通过网上银行进行支付时,需要一个安全的双方都信任的机构颁发一个凭证。

比如当访问 www.xxbank.com 时,它带有 xxbank 的公钥证书——被受信任的机构(Certificate Authority,简称 CA),这个证书签名可以有多个。(由 A 签名的银行 key 可以由 B 签名,该 key 可以由 C 签名,等等)

TLS 依赖公钥加密法对信息进行加密,在这种情况下,双方都拥有自己的密钥对。其中包括一个免费公开的公钥,和一个只有其所有者知道的私钥。

任何人都可以使用公钥对信息进行加密,但只有用相应的私钥才能对其进行解密。使用该协议,希望进行私密通信的双方可以交换他们的公钥,然后可以用公钥来保证所有后续通信的安全,而这种方式只能由持有相应私钥的预定收件人的主人来阅读。

图片

服务器检查它们都支持的最高 SSL/TLS 版本,从一个客户端选项(如果它支持一个)中选择一个密码套件,并可选择选择一种压缩方法。在此基本设置完成后,服务器提供其证书。此证书必须由客户端本身或客户端信任的一方信任。在验证了证书并确定该服务器确实是他声称的人(而不是中间人)之后,交换了一个密钥。这可以是一个公钥,“PreMasterSecret”,也可以是任何东西,具体取决于密码套件。

服务器和客户端现在都可以计算对称加密的密钥。握手完成,两台主机可以安全通信。通过完成来关闭连接。 TCP 连接双方都会知道连接被不当终止。连接不会因此而受到损害,只会中断。

4.Go 内置的安全服务函数

net/http 的设计者友好地提供了一个我们熟悉的 TLS 的变体:ListenAndServe 函数,我们已经很熟悉了。

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

ListenAndServeTLS​ 的使用和感觉几乎与 ListenAnd Serve​ 完全一样,只是它有两个额外的参数:certFile​ 和 keyFile​。如果你碰巧有证书和私钥的 PEM 文件,那么服务 HTTPS 加密连接只是将这些文件的名称传递给 ListenAndServeTLS 的问题。

http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)

这看起来超级方便。为了能够本地测试,使用自签名证书通常非常有用。自签名证书是带有公共密钥 P 的某些实体 E 的证书,但该密钥不是由已知证书授权签名,而是由 P 本身签名。虽然自签名的证书还有一些其他合法用途,但我们将重点介绍它们在这里进行测试的用途。

GO 的标准库对与加密,TLS 和证书相关的所有内容都有很好的支持。让我们看看如何在 GO 中生成自签名的证书!

使用crypto/ecdsa,crypto/elliptic​ 和 crypto/rand 包生成一对密钥对:

privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatalf("Failed to generate private key: %v", err)
}

创建 证书模板:

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Failed to generate serial number: %v", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"My Corp"},
},
DNSNames: []string{"localhost"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(3 * time.Hour),

KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

每个证书都需要一个唯一的序列号;通常,证书局将将这些存储在某些数据库中,但是对于我们本地的需求,将有一个随机的 128 位编号可以做到。这是摘要的前几行所做的。

接下来是 x509.certificate 模板。有关字段含义的更多信息,请参见 Crypto/X509 包文档以及 RFC5280 。我们只需注意,证书有效期为 3 小时,仅对 localhost 域有效。

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
}

该证书是由模板创建的,并用我们之前生成的私钥签名。请注意,&template​ 都在 createCertificate​的 template​ 和 parent 参数中传递。后者是使该证书自我签名的原因。

我们拥有服务器及其证书的私钥(其中包含公共密钥等)。现在剩下的就是将它们序列化为文件。首先,证书:

pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if pemCert == nil {
log.Fatal("Failed to encode certificate to PEM")
}
if err := os.WriteFile("cert.pem", pemCert, 0644); err != nil {
log.Fatal(err)
}
log.Print("wrote cert.pem\n")

然后,生成私钥:

privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
log.Fatalf("Unable to marshal private key: %v", err)
}
pemKey := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
if pemKey == nil {
log.Fatal("Failed to encode key to PEM")
}
if err := os.WriteFile("key.pem", pemKey, 0600); err != nil {
log.Fatal(err)
}
log.Print("wrote key.pem\n")

我们将证书和密钥序列化为 PEM 文件,看起来像这样(用于证书):

-----BEGIN CERTIFICATE-----
MIIBbjCCARSgAwIBAgIRALBCBgLhD1I/4S0fRZv6yfcwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEChMHTXkgQ29ycDAeFw0yMTAzMjcxNDI1NDlaFw0yMTAzMjcxNzI1NDla
MBIxEDAOBgNVBAoTB015IENvcnAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASf
wNSifB2LWDeb6xUAWbwnBQ2raSQTqqpaR1C1eEiy6cgqUiiOlr4jUDDiFCly+AS9
pNNe8o63/Gab/98dwFNQo0swSTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYI
KoZIzj0EAwIDSAAwRQIgYlJYGIwSvA+AmsHe8P34B5+hlfWEK4+kBmydJ65XJZMC
IQCzg5aihUXh7Rm0L1K3JrG7eRuTuFSkHoAhzk4cy6FqfQ==
-----END CERTIFICATE-----

5.HTTPS 服务器

例如:

package main

import (
"io"
"log"
"net/http"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, TLS!\n")
})

// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem.
log.Printf("About to listen on 8080. Go to https://127.0.0.1:8080/")
err := http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)
log.Fatal(err)
}

参考链接:

Go HTTPS servers with TLS安全问题,让人既爱又恨,不管是云原生应用还是本地或其他,安全是任何应用程序的一个重要特性。但往往很多时候,安全问题被忽视,成为最后考虑的问题,这样的结果就是:一旦出现问题,就会带来灾难性结果。

传统环境有丰富的工具和既定的安全最佳实践,但对于云原生应用来说,情况就不一样了,云原生应用往往采取几个小的、往往是短暂的微服务的形式。虽然这种架构提供了巨大的灵活性和可扩展性优势,但它也为潜在的攻击者创造了一个明显的机会:服务之间的每一次通信都是通过网络传输的,从而使其被窃听和篡改。

6.TLS 简要介绍

TLS(Transport Layer Security,安全传输层),是一种加密协议,旨在通过计算机网络提供通信安全。TLS 是建立在传输层 TCP 协议之上的协议,服务于应用层,它的前身是 SSL(Secure Socket Layer,安全套接字层),它实现了将应用层的报文进行加密后再交由 TCP 进行传输的功能。

TLS 确保没有第三方可以窃听或篡改任何消息。它的使用是无处不在和广泛的,它在 RFC 8446 中进行了描述。

当客户端通过普通 HTTP 连接到服务器时,它开始在完成标准 TCP 握手(SYN-> SYN -ACK-> ACK)后立即发送 TCP 数据包包裹的明文数据。使用 TLS ,情况更加复杂。

图片

如上图中,完成 TCP 握手后,服务器和客户端执行 TLS 握手,以接收双方共同的私钥并以此进行特定的会话。然后,利用公钥加密来保证它们之间交换的所有数据安全。尽管这个过程发生了很多事情,但这是 TLS 层为我们实现的东西。我们只需要正确设置TLS服务器(或客户端)即可;

GO 中 HTTP 和 HTTPS 服务器之间的实际差异很小。

7.TLS 的优点

加密: TLS/SSL 可以帮助使用加密保护传输的数据

互操作性:TLS/SSL 适用于大多数 Web 浏览器,包括 Microsoft Internet Explorer 以及大多数操作系统和 Web 服务器

算法灵活性:TLS/SSL 为安全会话期间使用的身份验证机制、加密算法和散列算法提供操作

易于部署:许多应用程序 TLS/SSL 临时在 windows server 2003 操作系统上

使用方便:因为我们在应用层下实现了 TLS/SSL,所以它的大部分操作对客户端是完全不可见的

8.公钥加密技术

在介绍 Go 使用 TLS 中如何设置 HTTPS 服务器的代码之前,让我们谈谈证书。在上面的图中,您会注意到服务器是其第一个 ServerHello 消息的一部分,将证书发送给客户端。正式地,这些被称为 X.509 证书,由 RFC 5280 描述。

证书是将服务器的公钥及其身份和受信任机构(通常是证书授权)签名的标准方法。举个例子,在以前通过网上银行进行支付时,需要一个安全的双方都信任的机构颁发一个凭证。

比如当访问 www.xxbank.com 时,它带有 xxbank 的公钥证书——被受信任的机构(Certificate Authority,简称 CA),这个证书签名可以有多个。(由 A 签名的银行 key 可以由 B 签名,该 key 可以由 C 签名,等等)

TLS 依赖公钥加密法对信息进行加密,在这种情况下,双方都拥有自己的密钥对。其中包括一个免费公开的公钥,和一个只有其所有者知道的私钥。

任何人都可以使用公钥对信息进行加密,但只有用相应的私钥才能对其进行解密。使用该协议,希望进行私密通信的双方可以交换他们的公钥,然后可以用公钥来保证所有后续通信的安全,而这种方式只能由持有相应私钥的预定收件人的主人来阅读。

图片

服务器检查它们都支持的最高 SSL/TLS 版本,从一个客户端选项(如果它支持一个)中选择一个密码套件,并可选择选择一种压缩方法。在此基本设置完成后,服务器提供其证书。此证书必须由客户端本身或客户端信任的一方信任。在验证了证书并确定该服务器确实是他声称的人(而不是中间人)之后,交换了一个密钥。这可以是一个公钥,“PreMasterSecret”,也可以是任何东西,具体取决于密码套件。

服务器和客户端现在都可以计算对称加密的密钥。握手完成,两台主机可以安全通信。通过完成来关闭连接。 TCP 连接双方都会知道连接被不当终止。连接不会因此而受到损害,只会中断。

9.Go 内置的安全服务函数

net/http 的设计者友好地提供了一个我们熟悉的 TLS 的变体:ListenAndServe 函数,我们已经很熟悉了。

func ListenAndServeTLS(addr, certFile, keyFile string, handler Handler) error

ListenAndServeTLS​ 的使用和感觉几乎与 ListenAnd Serve​ 完全一样,只是它有两个额外的参数:certFile​ 和 keyFile​。如果你碰巧有证书和私钥的 PEM 文件,那么服务 HTTPS 加密连接只是将这些文件的名称传递给 ListenAndServeTLS 的问题。

http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)

这看起来超级方便。为了能够本地测试,使用自签名证书通常非常有用。自签名证书是带有公共密钥 P 的某些实体 E 的证书,但该密钥不是由已知证书授权签名,而是由 P 本身签名。虽然自签名的证书还有一些其他合法用途,但我们将重点介绍它们在这里进行测试的用途。

GO 的标准库对与加密,TLS 和证书相关的所有内容都有很好的支持。让我们看看如何在 GO 中生成自签名的证书!

使用crypto/ecdsa,crypto/elliptic​ 和 crypto/rand 包生成一对密钥对:

privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
log.Fatalf("Failed to generate private key: %v", err)
}

创建 证书模板:

serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
log.Fatalf("Failed to generate serial number: %v", err)
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"My Corp"},
},
DNSNames: []string{"localhost"},
NotBefore: time.Now(),
NotAfter: time.Now().Add(3 * time.Hour),

KeyUsage: x509.KeyUsageDigitalSignature,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
}

每个证书都需要一个唯一的序列号;通常,证书局将将这些存储在某些数据库中,但是对于我们本地的需求,将有一个随机的 128 位编号可以做到。这是摘要的前几行所做的。

接下来是 x509.certificate 模板。有关字段含义的更多信息,请参见 Crypto/X509 包文档以及 RFC5280 。我们只需注意,证书有效期为 3 小时,仅对 localhost 域有效。

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
if err != nil {
log.Fatalf("Failed to create certificate: %v", err)
}

该证书是由模板创建的,并用我们之前生成的私钥签名。请注意,&template​ 都在 createCertificate​的 template​ 和 parent 参数中传递。后者是使该证书自我签名的原因。

我们拥有服务器及其证书的私钥(其中包含公共密钥等)。现在剩下的就是将它们序列化为文件。首先,证书:

pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if pemCert == nil {
log.Fatal("Failed to encode certificate to PEM")
}
if err := os.WriteFile("cert.pem", pemCert, 0644); err != nil {
log.Fatal(err)
}
log.Print("wrote cert.pem\n")

然后,生成私钥:

privBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
log.Fatalf("Unable to marshal private key: %v", err)
}
pemKey := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privBytes})
if pemKey == nil {
log.Fatal("Failed to encode key to PEM")
}
if err := os.WriteFile("key.pem", pemKey, 0600); err != nil {
log.Fatal(err)
}
log.Print("wrote key.pem\n")

我们将证书和密钥序列化为 PEM 文件,看起来像这样(用于证书):

-----BEGIN CERTIFICATE-----
MIIBbjCCARSgAwIBAgIRALBCBgLhD1I/4S0fRZv6yfcwCgYIKoZIzj0EAwIwEjEQ
MA4GA1UEChMHTXkgQ29ycDAeFw0yMTAzMjcxNDI1NDlaFw0yMTAzMjcxNzI1NDla
MBIxEDAOBgNVBAoTB015IENvcnAwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAASf
wNSifB2LWDeb6xUAWbwnBQ2raSQTqqpaR1C1eEiy6cgqUiiOlr4jUDDiFCly+AS9
pNNe8o63/Gab/98dwFNQo0swSTAOBgNVHQ8BAf8EBAMCB4AwEwYDVR0lBAwwCgYI
KwYBBQUHAwEwDAYDVR0TAQH/BAIwADAUBgNVHREEDTALgglsb2NhbGhvc3QwCgYI
KoZIzj0EAwIDSAAwRQIgYlJYGIwSvA+AmsHe8P34B5+hlfWEK4+kBmydJ65XJZMC
IQCzg5aihUXh7Rm0L1K3JrG7eRuTuFSkHoAhzk4cy6FqfQ==
-----END CERTIFICATE-----

10.HTTPS 服务器

例如:

package main

import (
"io"
"log"
"net/http"
)

func main() {
http.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
io.WriteString(w, "Hello, TLS!\n")
})

// One can use generate_cert.go in crypto/tls to generate cert.pem and key.pem.
log.Printf("About to listen on 8080. Go to https://127.0.0.1:8080/")
err := http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil)
log.Fatal(err)
}

参考链接:

Go HTTPS servers with TLS

责任编辑:武晓燕 来源: 宇宙之一粟
相关推荐

2016-10-10 22:48:16

2016-10-10 23:00:18

2011-02-21 11:15:12

2011-08-01 10:36:01

2022-03-25 21:57:49

汇编Go语言

2009-12-29 19:35:56

2021-07-07 12:36:10

HTTPSSSL通信

2016-11-29 15:22:47

协议应用层安全层

2021-01-07 14:17:31

Springboot数据安全加密

2021-01-29 08:19:50

HTTPS安全传输

2013-03-21 09:32:31

文件传输安全文件传输

2023-10-09 18:28:12

2010-07-06 15:43:04

UDP协议

2013-05-29 09:29:07

OSI传输层TCP协议

2011-03-08 16:50:35

2020-09-26 22:04:32

数据安全传输HTTPSHTTP 协议

2010-06-24 13:35:53

GRE协议

2015-03-17 09:43:29

2010-06-09 13:21:30

TCP传输层协议

2009-11-26 13:12:01

点赞
收藏

51CTO技术栈公众号