正在加载,请稍候…

How HTTPS and TLS Work: A Visual Deep Dive for Developers

Understand exactly how HTTPS protects your data: TLS handshake, certificates, certificate authorities, cipher suites, and common vulnerabilities. With practical examples.

What HTTPS Actually Does

Every time your browser shows a padlock icon, a cryptographic handshake has already happened. HTTPS (HTTP Secure) is just HTTP running over TLS (Transport Layer Security). But "running over TLS" involves several distinct protections that are easy to confuse:

  • Encryption: Data is scrambled so eavesdroppers can't read it
  • Integrity: Data cannot be modified in transit without detection
  • Authentication: You're talking to the server you think you are (not an impersonator)

All three are needed. Encryption without authentication is useless — an attacker could just intercept and re-encrypt your connection.

The TLS Handshake: Step by Step

Modern TLS 1.3 (2018) simplified the handshake significantly. Here's what happens when you visit https://example.com:

Client                                    Server
  |                                         |
  |──── ClientHello ──────────────────────→ |
  |     (TLS version, cipher suites,        |
  |      random nonce, key share)           |
  |                                         |
  |←─── ServerHello ─────────────────────── |
  |     (chosen cipher, key share)          |
  |←─── {Certificate} ────────────────────── |
  |     (server's public key + chain)       |
  |←─── {CertificateVerify} ──────────────── |
  |     (signature proving server has key)  |
  |←─── {Finished} ───────────────────────── |
  |                                         |
  |──── {Finished} ────────────────────────→ |
  |                                         |
  |←══════ Encrypted HTTP traffic ════════→ |

In TLS 1.3, the handshake completes in 1 round trip (1-RTT). There's even a 0-RTT mode for session resumption (though it has replay attack tradeoffs).

Key Exchange: Diffie-Hellman

The clever part is how client and server establish a shared secret without ever transmitting it:

# Simplified Diffie-Hellman (ECDHE in practice)
# Public info: prime p, base g
# Server generates: private_s, sends public_S = g^private_s mod p
# Client generates: private_c, sends public_C = g^private_c mod p
# 
# Shared secret = g^(private_s × private_c) mod p
# Server computes: public_C ^ private_s = same value
# Client computes: public_S ^ private_c = same value
# An eavesdropper who sees public_S and public_C cannot compute the secret

This is called Perfect Forward Secrecy (PFS): even if the server's private key is later compromised, past session keys cannot be decrypted.

TLS Certificates Explained

A TLS certificate binds a public key to a domain name, and is signed by a Certificate Authority (CA) that your browser trusts.

# Inspect a real certificate
openssl s_client -connect example.com:443 -showcerts 2>/dev/null |   openssl x509 -text -noout

# Key fields:
# Subject: CN=example.com (the domain this cert is for)
# Issuer: CN=Let's Encrypt R3 (who signed it)
# Validity: Not Before / Not After
# Subject Alternative Names: DNS:example.com, DNS:www.example.com
# Public Key Algorithm: id-ecPublicKey (curve: prime256v1)
# Signature Algorithm: ecdsa-with-SHA256

Certificate Chain of Trust

Your browser doesn't directly trust every website's certificate. It trusts a set of Root CAs (bundled with the OS/browser). Intermediate CAs bridge the gap:

Root CA (DigiCert Global Root)          → In browser's trust store
  └── Intermediate CA (DigiCert TLS RSA) → Signed by Root
        └── Your certificate (example.com) → Signed by Intermediate

When a browser connects, it verifies the entire chain up to a trusted root.

Let's Encrypt: Free Automated Certificates

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

# Get certificate for your domain
sudo certbot --nginx -d yourdomain.com -d www.yourdomain.com

# Auto-renewal (runs twice daily via cron/systemd)
sudo certbot renew --dry-run

# Certificate stored at:
# /etc/letsencrypt/live/yourdomain.com/fullchain.pem
# /etc/letsencrypt/live/yourdomain.com/privkey.pem

Configuring TLS in Nginx

server {
    listen 443 ssl http2;
    server_name example.com;

    # Certificate and private key
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Modern TLS only
    ssl_protocols TLSv1.2 TLSv1.3;
    
    # Strong cipher suites (TLS 1.3 auto-selects)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;

    # Session resumption (performance)
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off; # Disable tickets to preserve PFS

    # OCSP Stapling (faster cert validation)
    ssl_stapling on;
    ssl_stapling_verify on;
    resolver 8.8.8.8 8.8.4.4 valid=300s;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header Content-Security-Policy "default-src 'self'" always;
}

# Redirect HTTP to HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$host$request_uri;
}

HSTS: HTTP Strict Transport Security

Once set, HSTS tells browsers to always use HTTPS, preventing downgrade attacks:

# Server response header
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

# max-age: how long to remember (2 years = 63072000 seconds)
# includeSubDomains: applies to all subdomains
# preload: submit to browser preload lists (hardcoded HTTPS)

The HSTS preload list means browsers use HTTPS even on the very first visit.

Certificate Transparency

Since 2018, all public CAs must log every certificate they issue to Certificate Transparency (CT) logs. This means:

  • You can monitor for unauthorized certificates for your domain
  • Browsers require SCTs (Signed Certificate Timestamps) in certificates
# Check CT logs for your domain
curl "https://crt.sh/?q=%.yourdomain.com&output=json" | jq '.[].name_value'

# Monitor for unexpected certificates (set up alerts with Facebook's Cert Transparency Monitor)

Common TLS Vulnerabilities

Mixed Content

<!-- ❌ Loading HTTP resource on HTTPS page → browser warning/block -->
<img src="http://cdn.example.com/image.png">

<!-- ✅ Always use relative URLs or HTTPS -->
<img src="https://cdn.example.com/image.png">
<img src="//cdn.example.com/image.png">  <!-- protocol-relative -->

Certificate Pinning (Mobile Apps)

// In React Native, pin expected certificate hash
// Prevents attacks even if a CA is compromised
import { fetch } from 'react-native-ssl-pinning';

fetch('https://api.yourapp.com/data', {
  sslPinning: {
    certs: ['sha256//EXPECTED_CERT_HASH=']
  }
});

Weak Cipher Suites

# Test your server's TLS configuration
nmap --script ssl-enum-ciphers -p 443 yourdomain.com

# Or use online tool:
# https://www.ssllabs.com/ssltest/

# Look for:
# ❌ SSLv3, TLSv1.0, TLSv1.1 (deprecated)
# ❌ RC4, DES, 3DES (broken)
# ❌ Export-grade ciphers (FREAK attack)
# ✅ TLSv1.2 with AEAD ciphers, TLSv1.3

TLS in Node.js

import https from 'https';
import fs from 'fs';
import express from 'express';

const app = express();

// Read certificate files
const options = {
  cert: fs.readFileSync('/path/to/fullchain.pem'),
  key: fs.readFileSync('/path/to/privkey.pem'),
  
  // Modern TLS settings
  secureOptions: crypto.constants.SSL_OP_NO_SSLv2 |
                  crypto.constants.SSL_OP_NO_SSLv3 |
                  crypto.constants.SSL_OP_NO_TLSv1 |
                  crypto.constants.SSL_OP_NO_TLSv1_1,
  minVersion: 'TLSv1.2',
};

https.createServer(options, app).listen(443);

// Verify remote certificate (for outbound requests)
const response = await fetch('https://api.example.com', {
  // Node.js verifies certificates by default
  // To debug (NEVER in production):
  // dispatcher: new Agent({ rejectUnauthorized: false }) 
});

HTTP/2 and HTTP/3

HTTPS is required for HTTP/2, which brings significant performance improvements:

HTTP/1.1:  One request per connection. Head-of-line blocking.
HTTP/2:    Multiplexed streams. Server push. Header compression.
HTTP/3:    Built on QUIC (UDP). Even better for mobile networks.
# Enable HTTP/2 in nginx (free with HTTPS)
listen 443 ssl http2;

# HTTP/3 (QUIC) - requires nginx 1.25+ or Cloudflare
listen 443 quic reuseport;
add_header Alt-Svc 'h3=":443"; ma=86400';

→ Use the Encryption Tool to explore symmetric and asymmetric encryption algorithms used in TLS.