Rajeshwari Vakharia

Rajeshwari Vakharia

Cloud Engineer • Systems, Go & AWS • Tech Educator
← Back to Articles

Demonstrating TLS using GoLang

Originally published on DevOps.dev on Medium ↗.

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.
Client Local Socket
<!-- 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>
Figure 1: Simplified client-server session key exchange flow.

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:

  1. It encrypts the message contents.
  2. 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.