Transport Layer Security (TLS) is the backbone of modern internet communication, securing everything from HTTP browsing to microservice-to-microservice APIs. While standard libraries like Go’s crypto/tls handle this under the hood, understanding the exact cryptographic mechanisms makes debugging connection handshakes much more intuitive.
Understanding Symmetric vs Asymmetric Encryption
TLS achieves both security and high performance through a hybrid approach:
- Asymmetric Encryption: Used initially during the handshake (e.g. RSA or ECDHE) to securely agree upon a shared session secret without transmitting it in plaintext over the wire.
- Symmetric Encryption: Once the session secret is agreed upon, fast symmetric ciphers (such as AES-GCM) encrypt the bulk payload data with minimal CPU overhead.
<!-- Server -->
<rect x="415" y="40" width="100" height="100" rx="8" fill="#1e293b" stroke="#34d399" stroke-width="2"/>
<text x="442" y="80" fill="#34d399" font-family="sans-serif" font-weight="bold" font-size="13">Server</text>
<text x="425" y="105" fill="#94a3b8" font-family="monospace" font-size="10">TCP Listener</text>
<!-- Steps -->
<line x1="125" y1="65" x2="415" y2="65" stroke="#f59e0b" stroke-width="2" />
<text x="210" y="58" fill="#f59e0b" font-family="sans-serif" font-size="11">1. TCP Handshake & Connect</text>
<line x1="415" y1="95" x2="125" y2="95" stroke="#38bdf8" stroke-width="2"/>
<text x="200" y="88" fill="#38bdf8" font-family="sans-serif" font-size="11">2. Server sends Session Key</text>
<line x1="125" y1="125" x2="415" y2="125" stroke="#a7f3d0" stroke-width="2"/>
<text x="185" y="118" fill="#a7f3d0" font-family="sans-serif" font-size="11">3. Encrypted AES-GCM Exchange</text>
Why AES-GCM?
In modern TLS (including TLS 1.3), cipher suites exclusively use AEAD (Authenticated Encryption with Associated Data). AES-GCM (Galois/Counter Mode) is the gold standard because:
- It encrypts the message contents.
- It generates an authentication tag using a unique NONCE (number used once). If an attacker tampers with even a single byte in transit, the authentication tag verification fails immediately.
Go Implementation: AES-GCM Cipher
Here is how we initialize AES-GCM in Go using the standard crypto/aes and crypto/cipher packages:
package main
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"
"io"
)
// Encrypt payload using AES-GCM and a unique Nonce
func encryptPayload(key, plaintext []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, err
}
// Always use a unique Nonce for every transmission
nonce := make([]byte, gcm.NonceSize())
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return nil, err
}
// Seal appends the ciphertext and auth tag to the nonce
return gcm.Seal(nonce, nonce, plaintext, nil), nil
}
The Server & Client Socket Handshake
On the server side, we spin up a concurrent goroutine for each incoming client connection:
func handleConnection(conn net.Conn, sessionKey []byte) {
defer conn.Close()
// Send the negotiated session key to client
_, err := conn.Write(sessionKey)
if err != nil {
log.Printf("Failed to transfer session key: %v", err)
return
}
// Read and decrypt incoming packets...
}
Key Takeaways
Implementing this simplified model illustrates why TLS 1.3 reduced its cipher suite choices to only five secure AEAD constructions. By eliminating legacy ciphers (such as CBC mode with MAC-then-encrypt), modern TLS guarantees tamper-proof authentication and high performance.