A Complete Guide to TLS in Go: Full Process Explained
James Reed
Infrastructure Engineer ยท Leapcell

Explanation of the TLS Handshake Process
The TLS (Transport Layer Security) handshake is a vital procedure that enables secure communication between a client (such as a web browser) and a server (such as a web server). Below is a detailed breakdown of the entire TLS handshake process:
-
Client Hello
- The client starts the handshake by sending a "Client Hello" message to the server.
- This message contains:
- The TLS versions supported by the client.
- A list of cipher suites (encryption algorithms) it supports.
- A random byte string (referred to as Client Random).
-
Server Hello
- The server replies with a "Server Hello" message.
- This message includes:
- The selected TLS version.
- The chosen cipher suite.
- A random byte string (known as Server Random).
- The server's digital certificate (issued by a trusted Certificate Authority, CA).
-
Certificate Verification
- The client verifies the server's certificate through the certificate authority (CA) chain.
- It ensures that the certificate is valid, not expired, and issued to the correct domain.
-
Pre - Master Secret Generation
- The client generates a "Pre - Master Secret" using the server's public key (extracted from the certificate).
- This secret is encrypted and sent to the server.
-
Master Secret Derivation
- Both the client and the server generate the "Master Secret" using the following:
- The Client Random.
- The Server Random.
- The Pre - Master Secret.
- The Master Secret is used to derive session keys for encryption and integrity checks.
- Both the client and the server generate the "Master Secret" using the following:
-
Session Keys Creation
- Using the Master Secret, both parties create:
- Encryption keys for symmetric encryption.
- MAC (Message Authentication Code) keys for integrity checks.
- Using the Master Secret, both parties create:
-
Client Finished
- The client sends a "Finished" message, which is encrypted with the session keys.
- This confirms that the handshake was successful and that future messages will be encrypted.
-
Server Finished
- The server sends its own "Finished" message, encrypted with the session keys.
- This marks the end of the handshake and the start of encrypted communication.
-
Data Transfer
- All subsequent communication is encrypted using the derived session keys.
- Data is sent in encrypted packets with integrity checks.
Diagram of the TLS Handshake Process
+----------------------------------------+ +----------------------------------------+
| Client | | Server |
+----------------------------------------+ +----------------------------------------+
| | | |
| ClientHello |----->| |
| [TLS Version, Cipher Suites, Random] | | |
| | | |
| | | ServerHello |
| |<-----| [TLS Version, Cipher Suite, Random] |
| | | |
| |<-----| Certificate |
| | | [Server's Public Key] |
| | | |
| |<-----| ServerHelloDone |
| | | |
| CertificateVerify | | |
| [Verify Server's Certificate] | | |
| | | |
| ClientKeyExchange |----->| |
| [Encrypted Pre-Master Secret] | | |
| | | |
| ChangeCipherSpec |----->| |
| [Start Using Encryption] | | |
| | | |
| Finished |----->| |
| [Verifies Handshake Integrity] | | |
| | | |
| |<-----| ChangeCipherSpec |
| | | [Start Using Encryption] |
| | | |
| |<-----| Finished |
| | | [Verifies Handshake Integrity] |
| | | |
| Secure Communication |<--->| Secure Communication |
| [Encrypted Data Transfer] | | [Encrypted Data Transfer] |
+----------------------------------------+ +----------------------------------------+
Obtaining the TLS Client Hello Message with GoLang
Here's how to implement a server that captures all ClientHello messages using GoLang:
Certificate Generation
First, generate the necessary SSL certificates:
# Generate a private key openssl genrsa -out server.key 2048 # Generate a public key (certificate) openssl req -new -x509 -key server.key -out server.pem -days 3650
Server Implementation
The following is the complete server code for capturing ClientHello information:
package main import ( "bufio" "crypto/tls" "encoding/json" "fmt" "io/ioutil" "log" "net" "os" "sync" "time" ) type CollectInfos struct { ClientHellos []*tls.ClientHelloInfo sync.Mutex } var collectInfos CollectInfos var currentClientHello *tls.ClientHelloInfo func (c *CollectInfos) collectClientHello(clientHello *tls.ClientHelloInfo) { c.Lock() defer c.Unlock() c.ClientHellos = append(c.ClientHellos, clientHello) } func (c *CollectInfos) DumpInfo() { c.Lock() defer c.Unlock() data, err := json.Marshal(c.ClientHellos) if err != nil { log.Fatal(err) } ioutil.WriteFile("hello.json", data, os.ModePerm) } func getCert() *tls.Certificate { cert, err := tls.LoadX509KeyPair("server.pem", "server.key") if err != nil { log.Println(err) return nil } return &cert } func buildTlsConfig(cert *tls.Certificate) *tls.Config { cfg := &tls.Config{ Certificates: []tls.Certificate{*cert}, GetConfigForClient: func(clientHello *tls.ClientHelloInfo) (*tls.Config, error) { collectInfos.collectClientHello(clientHello) currentClientHello = clientHello return nil, nil }, } return cfg } func serve(cfg *tls.Config) { ln, err := tls.Listen("tcp", ":443", cfg) if err != nil { log.Println(err) return } defer ln.Close() for { conn, err := ln.Accept() if err != nil { log.Println(err) continue } go handler(conn) } } func handler(conn net.Conn) { defer conn.Close() r := bufio.NewReader(conn) for { msg, err := r.ReadString('\n') if err != nil { log.Println(err) return } fmt.Println(msg) data, err := json.Marshal(currentClientHello) if err != nil { log.Fatal(err) } _, err = conn.Write(data) if err != nil { log.Println(err) return } } } func main() { go func() { for { collectInfos.DumpInfo() time.Sleep(10 * time.Second) } }() cert := getCert() if cert != nil { serve(buildTlsConfig(cert)) } }
Client Implementation
The corresponding client code is as follows:
func main() { conn, err := tls.Dial("tcp", "localhost:443", &tls.Config{InsecureSkipVerify: true}) if err != nil { log.Fatal(err) } defer conn.Close() _, err = conn.Write([]byte("hello\n")) if err != nil { log.Fatal(err) } buf := make([]byte, 1000) n, err := conn.Read(buf) if err != nil { log.Fatal(err) } fmt.Println(string(buf[:n])) }
This implementation enables you to capture detailed information from ClientHello messages during the TLS handshake process. The server periodically exports this information to a JSON file for analysis.
Leapcell: The Best of Serverless Web Hosting
Finally, recommend a platform that is most suitable for deploying Go services: Leapcell
๐ Build with Your Favorite Language
Develop effortlessly in JavaScript, Python, Go, or Rust.
๐ Deploy Unlimited Projects for Free
Only pay for what you useโno charges when there are no requests.
โก Pay - as - You - Go, No Hidden Costs
No idle fees, just seamless scalability.
๐ Explore Our Documentation
๐น Follow us on Twitter: @LeapcellHQ