Understanding TLS: A Comprehensive Guide to Secure Communication

Published on September 20, 2025

In 2017, Equifax suffered one of the largest data breaches in history, exposing 147 million people’s personal information. One contributing factor? Outdated encryption protocols and security practices, compounded by an expired certificate in their internal monitoring system that left the breach undetected for 76 days. This is where TLS (Transport Layer Security) comes in: it’s the cryptographic protocol that stands between your sensitive data and potential attackers.

If you’ve ever noticed the padlock icon 🔒 in your browser’s address bar, you’ve seen TLS in action.

What is TLS?

TLS (Transport Layer Security) is a cryptographic protocol that provides secure communication between applications over a network, most commonly the internet. It’s the encryption protocol used by HTTPS to encrypt communication between web browsers and servers.

Without TLS, sensitive information such as login credentials, credit card numbers, and personal data can be easily intercepted by attackers through techniques like packet sniffing or man-in-the-middle attacks.

A Brief History

TLS evolved from SSL (Secure Sockets Layer):

Excerpted from https://en.wikipedia.org/wiki/Transport_Layer_Security#History_and_development

  • SSL 1.0 - Never publicly released due to security flaws
  • SSL 2.0 (1995) - Deprecated in 2011
  • SSL 3.0 (1996) - Deprecated in 2015 (POODLE attack)
  • TLS 1.0 (1999) - Deprecated in 2020
  • TLS 1.1 (2006) - Deprecated in 2020
  • TLS 1.2 (2008) - Currently widely used
  • TLS 1.3 (2018) - Latest version, recommended for new implementations

Important: Only TLS 1.2 and TLS 1.3 should be used in production today. TLS 1.0 and 1.1 were officially deprecated by major browsers and standards organisations in March 2020.

The Three Pillars of TLS Security

TLS provides three critical security features that protect your data:

1. Encryption

A mechanism to obfuscate (scramble) what is sent from one host to another, making it unreadable to anyone intercepting the communication.

Example: When you submit a password, TLS encrypts it so that network observers see random data instead of password123 🫩 .

2. Authentication

A mechanism to verify the validity of the server’s identity using digital certificates, ensuring you’re communicating with the intended server and not an impostor.

Example: When you visit https://bank.com, TLS verifies the server’s certificate to confirm you’re actually connected to your bank and not a phishing site.

3. Data Integrity

A mechanism to detect message tampering and forgery, ensuring that data hasn’t been modified during transmission.

Example: If an attacker tries to change your $100 transfer to $10,000, TLS detects the modification and rejects the message.

How TLS Works: The Handshake

A TLS connection begins with the TLS handshake, a sequence of messages exchanged between the client and the server. This process establishes a secure connection before any sensitive data is transmitted.

tls-handshake

What Happens During the Handshake:

  1. Version negotiation - Client and server agree on which TLS version to use (1.2 or 1.3)
  2. Cipher suite selection - They choose encryption algorithms from a list of supported options
  3. Authentication - Server proves its identity using a digital certificate signed by a trusted Certificate Authority
  4. Key exchange - Both parties securely exchange cryptographic keys
  5. Session establishment - They generate session keys for encrypting all subsequent communication

Cipher suites are combinations of cryptographic algorithms that define how encryption, authentication, and message integrity are performed. Examples include:

  • TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (TLS 1.2)
    • ECDHE: Elliptic Curve Diffie-Hellman for key exchange (provides forward secrecy)
    • RSA: RSA for authentication
    • AES_128_GCM: AES encryption in GCM mode (128-bit key)
    • SHA256: SHA-256 for message integrity
  • TLS_AES_256_GCM_SHA384 (TLS 1.3)
    • Simpler naming in TLS 1.3 (key exchange and authentication are separate)
    • AES_256_GCM: AES encryption (256-bit key)
    • SHA384: SHA-384 for integrity

Why TLS 1.3 is Better

TLS 1.3 addresses many limitations of earlier versions:

  • Faster handshake - 1 round trip instead of 2 (50% faster)
  • Better security - Removes obsolete and insecure cryptographic algorithms
  • Mandatory forward secrecy - Even if private keys are compromised later, past communications remain secure
  • Simplified cipher suites - Easier to configure securely
  • 0-RTT resumption - Even faster reconnection for repeat visitors

Remaining Considerations

1. Certificate Management -> Automate certificate renewal 2. Performance Impact -> Modern hardware with AES-NI support and TLS 1.3’s faster algorithms 3. Configuration Complexity -> Configuration testing tools like SSL Labs 4. Mixed Content Issues -> Content Security Policy headers and serve all resources over HTTPS 5. Certificate Trust -> Use well-known CAs, implement Certificate Transparency

Implementations

Java: Making HTTPS Requests with TLS

import java.io.IOException;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.util.Optional;
import javax.net.ssl.*;

public class TlsHttpClient {

    private static final String URL = "https://api.example.com";

    // ❌ BAD: Disables all certificate verification (NEVER do this in production!)
    static void bad() throws Exception {
        // This trust manager accepts ANY certificate without validation
        X509TrustManager trustAll = new X509TrustManager() {
            public void checkClientTrusted(java.security.cert.X509Certificate[] c, String a) {}
            public void checkServerTrusted(java.security.cert.X509Certificate[] c, String a) {}
            public java.security.cert.X509Certificate[] getAcceptedIssuers() { 
                return new java.security.cert.X509Certificate[0]; 
            }
        };
        
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, new TrustManager[]{trustAll}, new SecureRandom());

        HttpClient client = HttpClient.newBuilder()
                .sslContext(ctx)
                // WARNING: No hostname verification! Vulnerable to MITM attacks!
                .build();

        try {
            var res = client.send(
                HttpRequest.newBuilder(URI.create(URL)).GET().build(),
                HttpResponse.BodyHandlers.ofString()
            );
            System.out.println("BAD status: " + res.statusCode());
            // This appears to work but provides NO security!
        } catch (IOException e) {
            System.err.println("Request failed: " + e.getMessage());
        }
    }

    // ✅ GOOD: Uses system trust store with default verification
    static void good() throws IOException, InterruptedException {
        HttpClient client = HttpClient.newHttpClient();
        
        try {
            var res = client.send(
                HttpRequest.newBuilder(URI.create(URL)).GET().build(),
                HttpResponse.BodyHandlers.ofString()
            );
            
            String tls = res.sslSession()
                .map(SSLSession::getProtocol)
                .orElse("N/A");
            
            System.out.println("GOOD status: " + res.statusCode());
            System.out.println("Connected using: " + tls);
            // Expected output:
            // GOOD status: 200
            // Connected using: TLSv1.3
            
        } catch (SSLHandshakeException e) {
            System.err.println("TLS handshake failed: " + e.getMessage());
            // Common causes: expired certificate, untrusted CA, hostname mismatch
        } catch (IOException e) {
            System.err.println("Connection failed: " + e.getMessage());
        }
    }

    // ✅✅ BEST: Explicitly enforces TLSv1.2+ with proper hostname verification
    static void best() throws Exception {
        // Initialize trust manager with system's trusted CAs
        TrustManagerFactory tmf = TrustManagerFactory.getInstance(
            TrustManagerFactory.getDefaultAlgorithm()
        );
        tmf.init((KeyStore) null); // Uses default system keystore
        
        SSLContext ctx = SSLContext.getInstance("TLS");
        ctx.init(null, tmf.getTrustManagers(), new SecureRandom());

        // Configure SSL parameters for maximum security
        SSLParameters params = new SSLParameters();
        params.setProtocols(new String[]{"TLSv1.3", "TLSv1.2"}); // Only modern TLS
        params.setEndpointIdentificationAlgorithm("HTTPS"); // Enables hostname verification

        HttpClient client = HttpClient.newBuilder()
                .sslContext(ctx)
                .sslParameters(params)
                .build();

        try {
            var res = client.send(
                HttpRequest.newBuilder(URI.create(URL)).GET().build(),
                HttpResponse.BodyHandlers.ofString()
            );

            Optional<SSLSession> session = res.sslSession();
            System.out.println("BEST status: " + res.statusCode());
            System.out.println("Protocol: " + session.map(SSLSession::getProtocol).orElse("N/A"));
            System.out.println("Cipher: " + session.map(SSLSession::getCipherSuite).orElse("N/A"));
            
            // Expected output:
            // BEST status: 200
            // Protocol: TLSv1.3
            // Cipher: TLS_AES_256_GCM_SHA384
            
        } catch (SSLHandshakeException e) {
            System.err.println("TLS handshake failed: " + e.getMessage());
            if (e.getMessage().contains("certificate")) {
                System.err.println("Possible causes: expired cert, untrusted CA, or hostname mismatch");
            }
        }
    }

    public static void main(String[] args) {
        try {
            System.out.println("=== Testing GOOD approach ===");
            good();
            System.out.println("\n=== Testing BEST approach ===");
            best();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

Generating Self-Signed Certificates for Development

# Generate private key and certificate (valid for 365 days)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes

# Generate with specific details (non-interactive)
openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes \
  -subj "/C=US/ST=State/L=City/O=Organization/CN=localhost"

Warning: Self-signed certificates should only be used for local development, never in production!

Best Practices for TLS Implementation

1. Always Use TLS 1.2 or Higher

import java.net.URI;
import java.net.http.*;
import javax.net.ssl.*;
import java.security.SecureRandom;

SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, null, new SecureRandom()); // Uses default system trust

SSLParameters params = new SSLParameters();
// Allow only TLSv1.3 and TLSv1.2
params.setProtocols(new String[] { "TLSv1.3", "TLSv1.2" });
params.setEndpointIdentificationAlgorithm("HTTPS"); // Hostname verification

HttpClient client = HttpClient.newBuilder()
        .sslContext(ctx)
        .sslParameters(params)
        .build();

HttpResponse<String> res = client.send(
        HttpRequest.newBuilder(URI.create("https://api.example.com")).GET().build(),
        HttpResponse.BodyHandlers.ofString()
);

System.out.println("Negotiated: " + 
    res.sslSession().map(SSLSession::getProtocol).orElse("N/A"));
// Output: Negotiated: TLSv1.3

2. Keep Certificates Up to Date

The Equifax breach highlighted the critical importance of certificate management. While the breach itself stemmed from an unpatched Apache Struts vulnerability, it went undetected for 76 days because an expired certificate in their security monitoring infrastructure prevented their scanning tools from working.

  • Monitor ALL certificates in your infrastructure (web servers AND internal tools)
  • Automate certificate renewal (Let’s Encrypt, cert-manager for Kubernetes)
  • Set up expiration alerts (30, 15, and 7 days before expiry)
  • Test your monitoring to ensure it’s actually working

3. Use Strong Cipher Suites

Prefer cipher suites with:

  • ECDHE for forward secrecy (protects past sessions if keys are compromised)
  • AES-GCM for encryption (fast and secure)
  • SHA256 or SHA384 for integrity (avoid SHA1)

Avoid weak cipher suites:

  • Anything with RC4, MD5, or DES
  • NULL cipher suites (no encryption!)
  • Export-grade cipher suites

4. Enable HSTS (HTTP Strict Transport Security)

HSTS tells browsers to ONLY connect via HTTPS, preventing downgrade attacks.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
class SecurityConfig {

    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            // Your auth rules here
            .authorizeHttpRequests(auth -> 
                auth.anyRequest().authenticated()
            )
            // Configure HSTS
            .headers(headers -> headers
                .httpStrictTransportSecurity(hsts -> hsts
                    .maxAgeInSeconds(31536000)    // 1 year
                    .includeSubDomains(true)      // Apply to all subdomains
                    .preload(true)                // Optional: submit to browser preload list
                )
            )
            // Force HTTPS for all requests
            .requiresChannel(ch -> 
                ch.anyRequest().requiresSecure()
            );

        return http.build();
    }
}

5. Test Your TLS Configuration

Use these tools to verify your implementation:

6. Monitor Certificate Expiration

Set up monitoring and alerts:

# Check certificate expiration date
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
  openssl x509 -noout -enddate

# Expected output:
# notAfter=Dec 31 23:59:59 2025 GMT

# Get days until expiration
openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | \
  openssl x509 -noout -checkend 604800

# Exit code 0: Certificate valid for at least 7 more days
# Exit code 1: Certificate expires within 7 days

Consider using automated tools:

  • cert-manager (Kubernetes): Automates certificate issuance and renewal
  • Certbot (Let’s Encrypt): Automated certificate management for web servers
  • AWS Certificate Manager: Fully managed certificates for AWS resources

Common TLS Pitfalls

1. Disabling Certificate Verification

// ❌ NEVER DO THIS
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(null, trustAllManager, new SecureRandom()); // Accepts ANY certificate!

2. Using Self-Signed Certificates in Production

Self-signed certificates bypass the trust chain and should never be used in production. Use Let’s Encrypt for free, automated certificates.

3. Forgetting to Redirect HTTP to HTTPS

Always redirect HTTP to HTTPS

@Configuration
class HttpsConfig {
    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.requiresChannel(channel -> 
            channel.anyRequest().requiresSecure()
        );
        return http.build();
    }
}

4. Mixed Content Errors

Loading resources over HTTP on HTTPS page

<!-- ❌ BAD: HTTP resource on HTTPS page -->
<script src="http://cdn.example.com/script.js"></script>
<img src="http://example.com/image.jpg" />

<!-- ✅ GOOD: Use HTTPS for all resources -->
<script src="https://cdn.example.com/script.js"></script>
<img src="https://example.com/image.jpg" />

<!-- ✅ BETTER: Use protocol-relative URLs (inherits page protocol) -->
<script src="//cdn.example.com/script.js"></script>

Enforce with Content Security Policy:

http.headers(headers -> headers
    .contentSecurityPolicy(csp -> csp
        .policyDirectives("upgrade-insecure-requests")
    )
);

5. Not Testing TLS Configuration

Always test after configuration changes:

curl -v --tlsv1.2 https://example.com

Useful Testing Commands

Testing specific TLS versions

openssl s_client -connect example.com:443 -tls1_2
openssl s_client -connect example.com:443 -tls1_3

Check hostname matches

openssl x509 -in certificate.crt -noout -text | grep DNS

Verify certificate chain

# Download and verify full certificate chain 
openssl s_client -connect example.com:443 -showcerts < /dev/null | \ openssl x509 -text -noout

# Verify certificate against CA bundle
openssl verify -CAfile ca-bundle.crt certificate.crt

Check certificate details

openssl s_client -connect example.com:443 -showcerts

Check which protocols are enabled

nmap --script ssl-enum-ciphers -p 443 example.com

Test TLS handshake

curl -v --tlsv1.2 https://example.com

Check certificate expiration

openssl x509 -in certificate.crt -noout -enddate

While TLS adds some complexity and overhead, the security benefits are non-negotiable in today’s threat landscape. With modern TLS 1.3, many historical performance concerns have been addressed, making it easier than ever to implement strong security without sacrificing speed.

Start by auditing your applications’ TLS configurations today. Check your certificate expiration dates, verify you’re using TLS 1.2 or higher, and test your configuration with SSL Labs. Your users’ security, and your organisation’s reputation, depend on it.


Further Resources