Go语言加密通信
一般的 HTTPS 是基于 SSL(Secure Sockets Layer)协议。SSL 是网景公司开发的位于 TCP 与 HTTP 之间的透明安全协议,通过 SSL,可以把 HTTP 包数据以非对称加密的形式往返于浏览器和站点之间,从而避免被第三方非法获取。
目前,伴随着电子商务的兴起,HTTPS 获得了广泛的应用。由 IETF(Internet Engineering Task Force)实现的 TLS(Transport Layer Security)是建立于 SSL v3.0 之上的兼容协议,它们主要的区别在于所支持的加密算法。
1) 在浏览器中输入 HTTPS 协议的网址,如下图所示。
2) 服务器向浏览器返回证书,浏览器检查该证书的合法性,如下图所示。
3) 验证合法性,如下图所示。
4) 浏览器使用证书中的公钥加密一个随机对称密钥,并将加密后的密钥和使用密钥加密后的请求 URL 一起发送到服务器。
5) 服务器用私钥解密随机对称密钥,并用获取的密钥解密加密的请求 URL。
6) 服务器把用户请求的网页用密钥加密,并返回给用户。
7) 用户浏览器用密钥解密服务器发来的网页数据,并将其显示出来。
上述过程都是依赖于 SSL/TLS 层实现的。在实际开发中,SSL/TLS 的实现和工作原理比较复杂,但基本流程与上面的过程一致。
SSL 协议由两层组成,上层协议包括 SSL 握手协议、更改密码规格协议、警报协议,下层协议包括 SSL 记录协议。
SSL 握手协议建立在 SSL 记录协议之上,在实际的数据传输开始前,用于在客户与服务器之间进行“握手”。“握手”是一个协商过程。这个协议使得客户和服务器能够互相鉴别身份,协商加密算法。在任何数据传输之前,必须先进行“握手”。
在“握手”完成之后,才能进行 SSL 记录协议,它的主要功能是为高层协议提供数据封装、压缩、添加MAC、加密等支持。
可以看到,我们使用了 http.ListenAndServerTLS() 这个方法,这表明它是执行在 TLS 层上的 HTTP 协议。如果我们并不需要支持 HTTPS,只需要把该方法替换为 http.ListenAndServeTLS(fmt.Sprintf(":%d", SERVER_PORT), nil) 即可。
下面我们首先实现这个超级 ECHO 程序的服务器端,代码如下所示。
服务器端的输出结果为:
目前,伴随着电子商务的兴起,HTTPS 获得了广泛的应用。由 IETF(Internet Engineering Task Force)实现的 TLS(Transport Layer Security)是建立于 SSL v3.0 之上的兼容协议,它们主要的区别在于所支持的加密算法。
加密通信流程
当用户在浏览器中输入一个以 https 开头的网址时,便开启了浏览器与被访问站点之间的加密通信。下面我们以百度 https://www.baidu.com/ 为例,为大家展现一下 SSL/TLS 的工作方式。1) 在浏览器中输入 HTTPS 协议的网址,如下图所示。
2) 服务器向浏览器返回证书,浏览器检查该证书的合法性,如下图所示。
3) 验证合法性,如下图所示。
4) 浏览器使用证书中的公钥加密一个随机对称密钥,并将加密后的密钥和使用密钥加密后的请求 URL 一起发送到服务器。
5) 服务器用私钥解密随机对称密钥,并用获取的密钥解密加密的请求 URL。
6) 服务器把用户请求的网页用密钥加密,并返回给用户。
7) 用户浏览器用密钥解密服务器发来的网页数据,并将其显示出来。
上述过程都是依赖于 SSL/TLS 层实现的。在实际开发中,SSL/TLS 的实现和工作原理比较复杂,但基本流程与上面的过程一致。
SSL 协议由两层组成,上层协议包括 SSL 握手协议、更改密码规格协议、警报协议,下层协议包括 SSL 记录协议。
SSL 握手协议建立在 SSL 记录协议之上,在实际的数据传输开始前,用于在客户与服务器之间进行“握手”。“握手”是一个协商过程。这个协议使得客户和服务器能够互相鉴别身份,协商加密算法。在任何数据传输之前,必须先进行“握手”。
在“握手”完成之后,才能进行 SSL 记录协议,它的主要功能是为高层协议提供数据封装、压缩、添加MAC、加密等支持。
支持 HTTPS 的 Web 服务器
Go语言目前实现了 TLS 协议的部分功能,已经可以提供最基础的安全层服务。下面我们来看一下如何实现支持 TLS 的 Web 服务器。下面的代码示范了如何使用 http.ListenAndServerTLS 实现一个支持 HTTPS 的 Web 服务器。package main import ( "fmt" "net/http" ) const RESPONSE_TEMPLATE = "新宝库" func rootHandler(w http.ResponseWriter, req *http.Request) { w.Header().Set("Content-Type", "text/plain") w.Header().Set("Content-Length", fmt.Sprint(len(RESPONSE_TEMPLATE))) w.Write([]byte(RESPONSE_TEMPLATE)) } func main() { http.HandleFunc("/", rootHandler) http.ListenAndServeTLS(":8080", "cert.pem", "key.pem", nil) }运行上面的程序需要用到 cert.pem 和 key.pem 这两个文件,可以使用 crypto/tls 包的 generate_cert.go 文件来生成,运行成功后我们可以在浏览器中通过 https://localhost:8080 查看访问效果,如下图所示。
可以看到,我们使用了 http.ListenAndServerTLS() 这个方法,这表明它是执行在 TLS 层上的 HTTP 协议。如果我们并不需要支持 HTTPS,只需要把该方法替换为 http.ListenAndServeTLS(fmt.Sprintf(":%d", SERVER_PORT), nil) 即可。
支持 HTTPS 的文件服务器
利用 Go语言标准库中提供的完备封装,我们也可以很容易实现一个支持 HTTPS 的文件服务器,代码如下所示。package main import ( "net/http" ) func main() { h := http.FileServer(http.Dir(".")) http.ListenAndServeTLS(":8001", "cert.pem", "key.pem", h) }运行效果如下图所示。
基于 SSL/TLS 的 ECHO 程序
在本章最后,我们用一个完整的安全版 ECHO 程序来演示如何让 Socket 通信也支持 HTTPS。当然,ECHO 程序支持 HTTPS 似乎没有什么必要,但这个程序可以比较容易地改造成有实际价值的程序,比如安全的聊天工具等。下面我们首先实现这个超级 ECHO 程序的服务器端,代码如下所示。
package main import ( "crypto/rand" "crypto/tls" "io" "log" "net" "time" ) func main() { cert, err := tls.LoadX509KeyPair("rui.crt", "rui.key") if err != nil { log.Fatalf("server: loadkeys: %s", err) } config := tls.Config{Certificates:[]tls.Certificate{cert}} config.Time = time.Now config.Rand = rand.Reader service := "127.0.0.1:8000" listener, err := tls.Listen("tcp", service, &config) if err != nil { log.Fatalf("server: listen: %s", err) } log.Print("server: listening") for { conn, err := listener.Accept() if err != nil { log.Printf("server: accept: %s", err) break } log.Printf("server: accepted from %s", conn.RemoteAddr()) go handleClient(conn) } } func handleClient(conn net.Conn) { defer conn.Close() buf := make([]byte, 512) for { log.Print("server: conn: waiting") n, err := conn.Read(buf) if err != nil { if err != io.EOF { log.Printf("server: conn: read: %s", err) } break } log.Printf("server: conn: echo %q\n", string(buf[:n])) n, err = conn.Write(buf[:n]) log.Printf("server: conn: wrote %d bytes", n) if err != nil { log.Printf("server: write: %s", err) break } } log.Println("server: conn: closed") }现在服务器端已经实现了。我们再实现超级 ECHO 的客户端,代码如下所示。
package main import ( "crypto/tls" "io" "log" ) func main() { conn, err := tls.Dial("tcp", "127.0.0.1:8000", nil) if err != nil { log.Fatalf("client: dial: %s", err) } defer conn.Close() log.Println("client: connected to: ", conn.RemoteAddr()) state := conn.ConnectionState() log.Println("client: handshake: ", state.HandshakeComplete) log.Println("client: mutual: ", state.NegotiatedProtocolIsMutual) message := "Hello\n" n, err := io.WriteString(conn, message) if err != nil { log.Fatalf("client: write: %s", err) } log.Printf("client: wrote %q (%d bytes)", message, n) reply := make([]byte, 256) n, err = conn.Read(reply) log.Printf("client: read %q (%d bytes)", string(reply[:n]), n) log.Print("client: exiting") }接下来我们分别编译和运行服务器端和客户端程序,可以看到类似以下的运行效果。
服务器端的输出结果为:
2012/04/06 13:48:24 server: listening
2012/04/06 13:50:41 server: accepted from 127.0.0.1:15056
2012/04/06 13:50:41 server: conn: waiting
2012/04/06 13:50:41 server: conn: echo "Hello\n"
2012/04/06 13:50:41 server: conn: wrote 6 bytes
2012/04/06 13:50:41 server: conn: waiting
2012/04/06 13:50:41 server: conn: closed
2012/04/06 13:50:41 client: connected to: 127.0.0.1:8000
2012/04/06 13:50:41 client: handshake: true
2012/04/06 13:50:41 client: mutual: true
2012/04/06 13:50:41 client: wrote "Hello\n" (6 bytes)
2012/04/06 13:50:41 client: read "Hello\n" (6 bytes)
2012/04/06 13:50:41 client: exiting
所有教程
- C语言入门
- C语言编译器
- C语言项目案例
- 数据结构
- C++
- STL
- C++11
- socket
- GCC
- GDB
- Makefile
- OpenCV
- Qt教程
- Unity 3D
- UE4
- 游戏引擎
- Python
- Python并发编程
- TensorFlow
- Django
- NumPy
- Linux
- Shell
- Java教程
- 设计模式
- Java Swing
- Servlet
- JSP教程
- Struts2
- Maven
- Spring
- Spring MVC
- Spring Boot
- Spring Cloud
- Hibernate
- Mybatis
- MySQL教程
- MySQL函数
- NoSQL
- Redis
- MongoDB
- HBase
- Go语言
- C#
- MATLAB
- JavaScript
- Bootstrap
- HTML
- CSS教程
- PHP
- 汇编语言
- TCP/IP
- vi命令
- Android教程
- 区块链
- Docker
- 大数据
- 云计算