计算机网络课设——UDP/TCP/TLS Socket实验
2022年11月整的活儿。我看周围的人去网上去抄一篇C/C++/Java的Socket聊天实验室基本就能很好的把作业交差了,而我则选择用刚学的Go写一个用TLS加密Socket实验室。这几天整理了以下当时的代码,用GPT4生成使用TCP,UDP,TLS的Socker服务,重新温习一下Go语言的的操作。
TCP
tcp_server.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
| package main
import ( "bufio" "fmt" "net" "strings" )
func main() {
fmt.Println("Starting server...")
listener, _ := net.Listen("tcp", ":8080")
for { conn, _ := listener.Accept() go handleRequest(conn) } }
func handleRequest(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn) message, _ := reader.ReadString('\n') message = strings.TrimSpace(message)
fmt.Printf("Received: %s\n", message)
conn.Write([]byte("Message received.\n")) }
|
一个简单的TCP Socket Server,不涉及对conn的存储
聊天室需要利用对conn的存储进行实现
UDP
udp_server.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 48
| package main
import ( "fmt" "net" )
func main() {
p := make([]byte, 2048)
addr := net.UDPAddr{ Port: 12345, IP: net.ParseIP("127.0.0.1"), }
ser, err := net.ListenUDP("udp", &addr)
if err != nil { fmt.Printf("Some error %v", err) return }
for {
_, remoteaddr, err := ser.ReadFromUDP(p)
fmt.Printf("Read a message from %v %s \n", remoteaddr, p)
if err != nil { fmt.Printf("Some error %v", err) continue }
go sendResponse(ser, remoteaddr)
} }
func sendResponse(conn *net.UDPConn, addr *net.UDPAddr) {
_, err := conn.WriteToUDP([]byte("From server: Hello I got your mesage "), addr)
if err != nil { fmt.Printf("Couldn't send response %v", err) }
}
|
udp_client.go
1
| go run udp_client.go 127.0.0.1:12345
|
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
| package main
import ( "fmt" "net" "os" )
func main() { if len(os.Args) != 2 { fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0]) os.Exit(1) } service := os.Args[1] udpAddr, err := net.ResolveUDPAddr("udp4", service) CheckError(err) conn, err := net.DialUDP("udp", nil, udpAddr) CheckError(err) defer conn.Close() _, err = conn.Write([]byte("Hello from client")) CheckError(err) var buf [512]byte n, err := conn.Read(buf[0:]) CheckError(err) fmt.Println(string(buf[0:n])) os.Exit(0) }
func CheckError(err error) { if err != nil { fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error()) os.Exit(1) } }
|
TLS
先用OpenSSL生成公钥和私钥证书
1 2 3 4 5 6 7 8 9 10 11
| openssl genrsa -out private.key 2048
openssl req -new -key private.key -out cert.csr
openssl x509 -req -days 365 -in cert.csr -signkey private.key -out cert.crt
openssl x509 -in cert.crt -text -noout
|
转成pem文件
1 2 3 4 5
| openssl pkcs8 -topk8 -inform PEM -in private.key -out private.pem -nocrypt
openssl x509 -in cert.crt -out cert.pem -outform PEM
|
用Claude 2和GPT4都没有生成可用的代码,最后用ChatGPT生成了下面代码
tls_server.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 48 49 50 51 52 53 54 55 56 57 58 59 60
| package main
import ( "crypto/tls" "fmt" "io" "log" "net" )
func handleConnection(conn net.Conn) { defer conn.Close()
buf := make([]byte, 1024) for { n, err := conn.Read(buf) if err != nil { if err != io.EOF { fmt.Println("读取错误:", err) } break } fmt.Printf("收到消息:%s\n", string(buf[:n])) } }
func main() { cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") if err != nil { log.Fatal("加载证书失败:", err) }
config := &tls.Config{ Certificates: []tls.Certificate{cert}, }
listener, err := tls.Listen("tcp", ":443", config) if err != nil { log.Fatal("监听失败:", err) } defer listener.Close()
fmt.Println("等待客户端连接...")
for { conn, err := listener.Accept() if err != nil { log.Fatal("接受连接失败:", err) } fmt.Println("客户端已连接:", conn.RemoteAddr())
go handleConnection(conn) } }
|
tls_client.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 48 49 50 51 52 53 54 55 56
| package main
import ( "crypto/tls" "fmt" "log" "os" "os/signal" "syscall" )
func main() { cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") if err != nil { log.Fatal("加载证书失败:", err) }
config := &tls.Config{ Certificates: []tls.Certificate{cert}, InsecureSkipVerify: true, }
conn, err := tls.Dial("tcp", "127.0.0.1:443", config) if err != nil { log.Fatal("连接服务器失败:", err) } defer conn.Close()
fmt.Println("已连接到服务器。可以开始发送消息了。")
go func() { sigCh := make(chan os.Signal, 1) signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM) <-sigCh
fmt.Println("收到退出信号。关闭连接...") conn.Close() }()
buf := make([]byte, 1024) for { n, err := os.Stdin.Read(buf) if err != nil { log.Fatal("读取输入错误:", err) } _, err = conn.Write(buf[:n]) if err != nil { log.Fatal("发送消息失败:", err) } } }
|
抓出来的包不是TLS

但内容确实是加密的

与纯TCP的连接做个对照

但我手头上的最早的网络实验里,Wireshark可以找到TLS的握手包

然后第二天早上在跑一遍ChatGPT写的程序,TLS1.3就被识别出来了

所以这个乌龙应该是Wireshark在IPv6上抓包引起的
WireShark解密
常规办法:
通过设置WSSLKEYLOGFILE进行导出(仅对浏览器有效)
那就会引出下一个问题:我自己写的程序怎么办?
通过导入私钥的办法解决,这个方法用两个限制条件
- 必须要有完整的握手包(Client Hello还有Client Key Exchange,因此TLS版本必须小于1.3)
- 解密套件仅限于TLS_RSA_开头的Suite

对此,需要对服务端和客户端TLS的config做出修改,添加相关限制
1 2 3 4 5 6
| MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, CipherSuites: []uint16{ tls.TLS_RSA_WITH_AES_128_CBC_SHA, tls.TLS_RSA_WITH_AES_256_CBC_SHA, },
|
满足上述条件即可通过导入私钥抓包

由此可见,就算是服务器丢了私钥,使用TLS1.3的情况下也无法对数据包解密
总结
Sever最终进入死循环,在循环中接收到conn后转移到goroutine中解决
实现聊天室就需要建立连接池,建一个Map或和Array的全局变量把连接添加进去即可。