HTTPS, TLS and Certificates
22 min · Intermediate
Need help? Ask a tutor on WhatsApp (opens in a new tab)Plain HTTP travels as readable text, so anyone on the network can read or change it. HTTPS wraps the same messages in TLS, which adds secrecy, tamper detection and proof of who the server is.
HTTPS is HTTP inside TLS
TLS (Transport Layer Security) is the protocol that encrypts a connection, detects changes to the data and lets the client check the server identity. HTTPS is HTTP running over TLS.
The messages do not change. The only difference is a secure tunnel built first. The URL scheme tells you which one is used, and each has its own default port.
from urllib.parse import urlsplit
for url in ("http://shop.example.com/", "https://shop.example.com/login"): parts = urlsplit(url) print(parts.scheme, parts.hostname, parts.path)Example 1 · URL parts and default ports
from urllib.parse import urlsplit
DEFAULT_PORT = {"http": 80, "https": 443}for url in ("http://shop.example.com/", "https://shop.example.com/login", "https://api.example.com:8443/v1"): u = urlsplit(url) print(u.scheme, u.hostname, u.port or DEFAULT_PORT[u.scheme])http shop.example.com 80 https shop.example.com 443 https api.example.com 8443
What this code does
urlsplitcuts a URL into scheme, host, port and path.- When the port is missing, the scheme decides the default.
The default port is 80 for http and 443 for https. A browser fills it in when the URL has no port.
Three promises of TLS
Integrity means that data was not changed on its way. TLS checks it with a message authentication code: a short fingerprint made from the data and a secret key that only the two sides know.
Privacy: outsiders cannot read the data. Integrity: any change on the way is detected. Identity: the client knows it reached the real server. Integrity works with a keyed fingerprint called a MAC.
import hashlib, hmac
key = b"shared-secret"message = b"pay 100 to account 7"tag = hmac.new(key, message, hashlib.sha256).hexdigest()print(len(tag))Example 2 · A changed message is detected
import hashlibimport hmac
key = b"shared-secret"
def sign(message): return hmac.new(key, message, hashlib.sha256).digest()
tag = sign(b"pay 100 to account 7")print(hmac.compare_digest(tag, sign(b"pay 100 to account 7")))print(hmac.compare_digest(tag, sign(b"pay 900 to account 7")))True False
What this code does
- Only someone with the key can make a matching tag.
compare_digestcompares in constant time, which stops timing leaks.
The sender attaches this tag. The receiver recomputes it, and a changed message gives a different tag.
The handshake builds a shared key
Key exchange is a way for two sides to agree on a secret key over an open network. An eavesdropper sees the messages but cannot work out the key.
Client and server first agree on settings. The server shows its certificate. They then make one shared secret key without ever sending it. After that, all traffic uses fast symmetric encryption with that key.
p, g = 23, 5a, b = 6, 15A = pow(g, a, p)B = pow(g, b, p)print(pow(B, a, p) == pow(A, b, p))Example 3 · A toy key exchange
p, g = 23, 5 # public numbers, tiny on purposeclient_secret, server_secret = 6, 15
client_public = pow(g, client_secret, p)server_public = pow(g, server_secret, p)print("sent in public:", client_public, server_public)
shared_client = pow(server_public, client_secret, p)shared_server = pow(client_public, server_secret, p)print("same secret:", shared_client == shared_server, shared_client)sent in public: 8 19 same secret: True 2
What this code does
- Only the two public values cross the network.
- Real TLS uses numbers with hundreds of digits or elliptic curves. This tiny version only shows the idea.
This toy Diffie-Hellman exchange uses tiny numbers. Each side sends only A or B in public, yet both reach the same secret. Real TLS uses huge numbers or elliptic curves.
Certificates prove identity
Certificate is a signed file that ties a domain name to a public key. A certificate authority signs it, and clients accept the site only if the signature, the dates and the name all check out.
A certificate links a server name to a public key. A trusted company, called a certificate authority, signs it. The client checks the signature, the dates, and that the name matches the site it asked for.
import ssl
ctx = ssl.create_default_context()print(ctx.check_hostname)print(ctx.verify_mode == ssl.CERT_REQUIRED)Example 4 · Safe defaults of an ssl context
import ssl
ctx = ssl.create_default_context()print("check_hostname:", ctx.check_hostname)print("verify required:", ctx.verify_mode == ssl.CERT_REQUIRED)print("minimum version:", ctx.minimum_version.name)
try: ctx.verify_mode = ssl.CERT_NONEexcept ValueError as err: print("blocked:", err)check_hostname: True verify required: True minimum version: TLSv1_2 blocked: Cannot set verify_mode to CERT_NONE when check_hostname is enabled.
What this code does
- The defaults verify the certificate chain and the host name.
- Python even refuses to turn verification off while the host name check is still on.
Example 5 · Matching a name to a certificate
def host_matches(pattern, host): """Simplified rule: a wildcard covers exactly one label.""" if pattern.startswith("*."): first, _, rest = host.partition(".") return bool(first) and rest == pattern[2:] return pattern == host
print(host_matches("*.example.com", "shop.example.com"))print(host_matches("*.example.com", "a.b.example.com"))print(host_matches("*.example.com", "example.com"))True False False
What this code does
- This is a simplified teaching version; the
sslmodule and your HTTP library do the real check. - A wildcard covers one label only, so the deeper name and the bare domain fail.
The default context verifies the chain and the host name. Python libraries such as httpx build on it. Never switch these checks off to "fix" an error.
Next step
Check your understanding
12 quick questions