Skip to main content
โšก Calmops

TLS/SSL: Transport Layer Security Explained

TLS (Transport Layer Security) is the backbone of secure communication on the internet. It encrypts data in transit, authenticates servers (and optionally clients), and ensures data integrity. Whether you’re browsing the web or building distributed systems, understanding TLS is essential.

In this guide, we’ll explore TLS fundamentals, cryptographic primitives, certificate management, and security best practices.

Understanding TLS

What is TLS?

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                    TLS Purpose                                โ”‚
โ”‚                                                             โ”‚
โ”‚   Without TLS:            With TLS:                          โ”‚
โ”‚                                                             โ”‚
โ”‚   Client โ”€โ”€โ”€โ”€โ”€โ”€โ–บ Server    Client โ•โ•โ•โ•โ•โ•โ•โ–บ Server          โ”‚
โ”‚    Hello                  (Encrypted tunnel)                โ”‚
โ”‚    Data  (plain text)      Data  (encrypted)                โ”‚
โ”‚    More Data               More Data                        โ”‚
โ”‚                           Authenticated                      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

TLS Provides

# Three pillars of TLS security

tls_guarantees = {
    "confidentiality": {
        "description": "Only authorized parties can read data",
        "method": "Encryption (AES, ChaCha20)"
    },
    
    "integrity": {
        "description": "Data cannot be modified without detection",
        "method": "Message Authentication Codes (HMAC)"
    },
    
    "authentication": {
        "description": "Verify identity of parties",
        "method": "Digital Certificates (X.509)"
    }
}

Cryptographic Fundamentals

Symmetric Encryption

# Symmetric: Same key for encrypt/decrypt
# Fast, used for bulk data encryption

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.backends import default_backend
import os

def encrypt_aes_gcm(plaintext, key):
    """AES-GCM: Authenticated encryption"""
    iv = os.urandom(12)  # 96-bit IV
    
    cipher = Cipher(
        algorithms.AES(key),
        modes.GCM(iv),
        backend=default_backend()
    )
    encryptor = cipher.encryptor()
    
    ciphertext = encryptor.update(plaintext) + encryptor.finalize()
    
    return {
        'ciphertext': ciphertext,
        'iv': iv,
        'tag': encryptor.tag  # Authentication tag
    }

# Key sizes
key_sizes = {
    "AES-128": 16,  # 128 bits = 16 bytes
    "AES-256": 32,  # 256 bits = 32 bytes
    "ChaCha20": 32
}

Asymmetric Encryption

# Asymmetric: Public key encrypts, private key decrypts
# Used for key exchange and signatures

from cryptography.hazmat.primitives.asymmetric import rsa, padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend

# Generate key pair
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)

public_key = private_key.public_key()

# Encrypt with public key
def encrypt_rsa(plaintext, public_key):
    ciphertext = public_key.encrypt(
        plaintext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return ciphertext

# Decrypt with private key
def decrypt_rsa(ciphertext, private_key):
    plaintext = private_key.decrypt(
        ciphertext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    return plaintext

Digital Signatures

# Sign data with private key, verify with public key

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding

def sign_data(data, private_key):
    signature = private_key.sign(
        data,
        padding.PSS(
            mgf=padding.MGF1(hashes.SHA256()),
            salt_length=padding.PSS.MAX_LENGTH
        ),
        hashes.SHA256()
    )
    return signature

def verify_signature(data, signature, public_key):
    try:
        public_key.verify(
            signature,
            data,
            padding.PSS(
                mgf=padding.MGF1(hashes.SHA256()),
                salt_length=padding.PSS.MAX_LENGTH
            ),
            hashes.SHA256()
        )
        return True
    except:
        return False

# Hash-based signatures (faster, for certificates)
# Ed25519 (recommended)
# ECDSA (widely supported)

Hash Functions

# Hash functions: Fixed-size output from variable input

import hashlib

# Common hash algorithms
hashes = {
    "MD5": {
        "output": "128 bits (16 bytes)",
        "status": "BROKEN - Do not use",
        "example": hashlib.md5(b"data").hexdigest()
    },
    
    "SHA-1": {
        "output": "160 bits (20 bytes)",
        "status": "WEAK - Deprecated",
        "example": hashlib.sha1(b"data").hexdigest()
    },
    
    "SHA-256": {
        "output": "256 bits (32 bytes)",
        "status": "SECURE - Recommended",
        "example": hashlib.sha256(b"data").hexdigest()
    },
    
    "SHA-3": {
        "output": "256/384/512 bits",
        "status": "SECURE - Latest standard",
        "example": hashlib.sha3_256(b"data").hexdigest()
    }
}

# HMAC (Hash-based Message Authentication Code)
import hmac

def hmac_sha256(key, message):
    return hmac.new(key, message, hashlib.sha256).digest()

TLS Handshake

Full TLS 1.3 Handshake

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚                  TLS 1.3 Handshake                            โ”‚
โ”‚                                                             โ”‚
โ”‚   Client                                    Server           โ”‚
โ”‚   โ”€โ”€โ”€โ”€โ”€โ”€                                  โ”€โ”€โ”€โ”€โ”€โ”€โ”€           โ”‚
โ”‚   ClientHello                                            โ”‚
โ”‚   + supported_versions                                    โ”‚
โ”‚   + key_share (client)         โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ  โ”‚
โ”‚                                                        โ”‚    โ”‚
โ”‚                                              ServerHello โ”‚
โ”‚                                         + key_share     โ”‚
โ”‚                                         + certificate   โ”‚
โ”‚                                         + verify         โ”‚
โ”‚   Finished โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€  (complete)        โ”‚
โ”‚   (key derivation complete)                              โ”‚
โ”‚                                                         โ”‚
โ”‚   Application Data โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ–บ       โ”‚
โ”‚   (encrypted)                                            โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Handshake Details

# TLS 1.3 is faster than 1.2

handshake_comparison = {
    "tls_1_2": {
        "round_trips": "2-3 RTT",
        "steps": [
            "ClientHello",
            "ServerHello + Certificate + Key Exchange",
            "Client Key Exchange + ChangeCipherSpec",
            "Finished"
        ]
    },
    
    "tls_1_3": {
        "round_trips": "1 RTT",
        "steps": [
            "ClientHello + Key Share",
            "ServerHello + Key Share + Certificate + Verify",
            "Finished"
        ]
    }
}

# 0-RTT Mode (for returning clients)
# Client can send encrypted data in first message
# Trade-off: Replay attack risk
zero_rtt_warning = """
0-RTT is faster but vulnerable to replay attacks.
Use with caution for idempotent requests only.
"""

Key Exchange Methods

# TLS 1.3 Key Exchange

key_exchange = {
    "ecdhe": {
        "full_name": "Elliptic Curve Diffie-Hellman Ephemeral",
        "security": "Strong",
        "performance": "Fast",
        "recommended": True
    },
    
    "rsa": {
        "full_name": "RSA Key Exchange (deprecated)",
        "security": "Weak (no forward secrecy)",
        "performance": "Slower",
        "recommended": False
    },
    
    "psk": {
        "full_name": "Pre-Shared Key",
        "security": "Depends on PSK",
        "performance": "Very Fast",
        "recommended": "For IoT or resumption"
    }
}

Certificates

X.509 Certificates

# Certificate structure

Certificate:
  Version: v3
  Serial Number: 04:A5:B2:C3:D4:E5...
  Signature Algorithm: ecdsa-with-SHA256
  Issuer: 
    Country: US
    Organization: Let's Encrypt
    Common Name: R3
  Validity:
    Not Before: 2025-01-01
    Not After: 2025-04-01
  Subject:
    Common Name: example.com
    Subject Alternative Names:
      - example.com
      - www.example.com
      - *.example.com
  Subject Public Key:
    Algorithm: ECDSA
    Curve: prime256v1
  Extensions:
    Key Usage: Digital Signature
    Extended Key Usage: Server Auth
    CA: False (End-entity certificate)

Certificate Types

# Domain Validation (DV)
- Validates domain ownership
- Quick issuance (minutes)
- Example: Let's Encrypt

# Organization Validation (OV)
- Validates domain + organization
- Shows organization name
- Example: DigiCert Business

# Extended Validation (EV)
- Thorough validation
- Green address bar (historically)
- More expensive
- Example: DigiCert EV

# Wildcard Certificates
- *.example.com covers all subdomains
- Security concern: one key for all

# Self-signed Certificates
- Not trusted by browsers
- OK for development/testing
- Never use in production

Managing Certificates with Let’s Encrypt

# Using Certbot

# Install
# sudo apt install certbot python3-certbot-nginx

# Generate certificate
# certbot certonly --webroot -w /var/www/html -d example.com

# With Nginx
# certbot --nginx -d example.com -d www.example.com

# Auto-renewal
# certbot renew --dry-run

# In Python (using acme library)
from acme import client
from cryptography import x509
from cryptography.hazmat.primitives import serialization

def get_letsencrypt_cert(domain, email):
    """Get certificate using ACME protocol"""
    
    # 1. Create ACME client
    directory = client.Directory("https://acme-v02.api.letsencrypt.org/directory")
    acme_client = client.Client(directory)
    
    # 2. Register
    registration = acme_client.new_registration(email=email)
    registration.send_check_challenge()
    
    # 3. Authorize domain
    auth = acme_client.new_authz(domain=domain)
    
    # 4. Complete challenge
    # (HTTP-01 challenge typically)
    
    # 5. Request certificate
    # csr = ... (create CSR)
    # cert = acme_client.request_issuance(csr)
    
    return cert

Certificate Pinning

# Pin certificate to prevent MITM

# Pin the certificate itself
certificate_pins = [
    "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=",  # Leaf
    "sha256/CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC=",  # Backup
]

# Or pin the public key
public_key_pins = [
    "sha256/DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD=",
]

# Implementation in Python (Flask)
@app.after_request
def add_pin(response):
    response.headers['Public-Key-Pins'] = (
        'pin-sha256="BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="; '
        'pin-sha256="CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC="; '
        'max-age=2592000; includeSubDomains'
    )
    return response

TLS Configurations

Nginx Configuration

server {
    listen 443 ssl http2;
    
    # Certificate
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Modern TLS configuration
    ssl_protocols TLSv1.3;  # Only TLS 1.3 (or TLSv1.2 for compatibility)
    
    # Ciphers - prefer AEAD ciphers
    ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    
    # Prefer server cipher order
    ssl_prefer_server_ciphers on;
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;
    resolver_timeout 5s;
    
    # Session caching
    ssl_session_timeout 1d;
    ssl_session_cache shared:SSL:50m;
    ssl_session_tickets off;
    
    # HSTS (HTTP Strict Transport Security)
    add_header Strict-Transport-Security "max-age=63072000" always;
    
    # Additional security headers
    add_header X-Frame-Options DENI;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
}

Apache Configuration

<VirtualHost *:443>
    SSLEngine on
    
    SSLCertificateFile /etc/letsencrypt/live/example.com/cert.pem
    SSLCertificateKeyFile /etc/letsencrypt/live/example.com/privkey.pem
    SSLCertificateChainFile /etc/letsencrypt/live/example.com/chain.pem
    
    # TLS versions
    SSLProtocol all -SSLv3 -TLSv1 -TLSv1.1
    
    # Ciphers
    SSLCipherSuite ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256
    SSLHonorCipherOrder on
    
    # HSTS
    Header always set Strict-Transport-Security "max-age=63072000"
    
    # OCSP Stapling
    SSLUseStapling on
    SSLStaplingCache "shmcb:logs/ssl_stapling(32768)"
</VirtualHost>

Python TLS (requests)

import requests
import ssl

# Custom SSL context
def create_secure_context():
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.minimum_version = ssl.TLSVersion.TLSv1_3
    
    # Load default certs (system CA)
    context.load_default_certs()
    
    # Or specify custom CA
    context.load_verify_locations('custom-ca.pem')
    
    # Load client certificate
    context.load_cert_chain(
        certfile='client.crt',
        keyfile='client.key'
    )
    
    return context

# Use with requests
session = requests.Session()
session.ssl_context = create_secure_context()

response = session.get('https://api.example.com')

# Verify certificate
# requests verifies by default using system CA store
# Disable only for testing!
response = requests.get('https://example.com', verify=False)  # DANGER!

TLS Security Best Practices

Do’s and Don’ts

# DO:
- Use TLS 1.3 only (or TLS 1.2 minimum)
- Use strong cipher suites
- Enable HSTS
- Use OCSP Stapling
- Implement certificate pinning for mobile apps
- Keep certificates up to date
- Use forward secrecy

# DON'T:
- Use SSLv3 or earlier
- Use RC4, 3DES, or MD5
- Use RSA key exchange
- Disable certificate verification
- Use self-signed certificates in production
- Ignore certificate expiration

Forward Secrecy

# Forward secrecy: Even if long-term key is compromised,
// past sessions remain secure

# Good: Ephemeral key exchange (ECDHE, DHE)
forward_secrecy_ciphers = [
    "ECDHE-RSA-AES256-GCM-SHA384",
    "ECDHE-ECDSA-AES256-GCM-SHA384",
    "DHE-RSA-AES256-GCM-SHA384"
]

# Bad: No forward secrecy
no_forward_secrecy = [
    "RSA-AES256-GCM-SHA384"  # Uses static RSA key
]

Testing TLS Configuration

# SSL Labs test
# https://www.ssllabs.com/ssltest/

# Test from command line
openssl s_client -connect example.com:443 -tls1_3
openssl s_client -connect example.com:443 -showcerts

# Check certificate
openssl x509 -in certificate.pem -text -noout

# Check cipher suites
openssl ciphers -v 'HIGH:!aNULL:!MD5'

# Test with specific cipher
openssl s_client -connect example.com:443 -cipher ECDHE-RSA-AES256-GCM-SHA384
# Python test for TLS
import ssl
import socket

def check_tls_version(hostname, port=443):
    """Check which TLS versions are supported"""
    
    for version in [ssl.TLSVersion.TLSv1, 
                    ssl.TLSVersion.TLSv1_1,
                    ssl.TLSVersion.TLSv1_2,
                    ssl.TLSVersion.TLSv1_3]:
        
        context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
        context.minimum_version = version
        context.maximum_version = version
        
        try:
            with socket.create_connection((hostname, port)) as sock:
                with context.wrap_socket(sock, 
                                        server_hostname=hostname) as ssock:
                    print(f"{version.name}: Supported")
        except ssl.SSLError:
            print(f"{version.name}: Not supported")
        except Exception as e:
            print(f"{version.name}: Error - {e}")

TLS in Microservices

mTLS (Mutual TLS)

# Client and server authenticate each other

# Used in:
# - Service mesh (Istio, Linkerd)
# - Microservices with strict security
# - gRPC with TLS
# Python mTLS example

# Server-side
def create_mtls_server_context():
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
    context.load_cert_chain('server.crt', 'server.key')
    context.load_verify_locations('ca.crt')  # Client CA
    context.verify_mode = ssl.CERT_REQUIRED
    context.check_hostname = True
    return context

# Client-side
def create_mtls_client_context():
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.load_cert_chain('client.crt', 'client.key')
    context.load_verify_locations('ca.crt')  # Server CA
    context.verify_mode = ssl.CERT_REQUIRED
    context.check_hostname = True
    return context

Conclusion

TLS is essential for securing network communications:

  • Encryption: AES-GCM and ChaCha20 provide confidentiality
  • Authentication: X.509 certificates verify identity
  • Integrity: HMAC ensures data wasn’t tampered with
  • TLS 1.3: Faster handshake with 1-RTT, optional 0-RTT

Always use the latest TLS version, enable forward secrecy, and properly manage certificates to maintain security.


Comments