Chapter 14 — Cryptography and trust services: methods I
Lesson map
Lesson 14 published. Map, example, EX040.
flowchart TD
N0["Learning outcomes"]
N1["14.1 Signed webhooks"]
N2["Lab — Webhook verification"]
N3["14.2 Reading the webhook run"]
N4["14.3 Combining checks"]
N5["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5
N5 --> EX["Worked example"]
EX --> Q["Exercise EX040"]
Logical drawing
flowchart LR
N0["Learning outcomes"]
N1["14.1 Signed webhooks"]
N2["Lab — Webhook verification"]
N3["14.2 Reading the webhook run"]
N4["14.3 Combining checks"]
N5["Synthesis"]
N0 --> N1 --> N2 --> N3 --> N4 --> N5 --> Q["EX040"]
[Learning outcomes]
|
v
[14.1 Signed webhooks]
|
v
[Lab — Webhook verification]
|
v
[14.2 Reading the webhook run]
|
v
[14.3 Combining checks]
|
v
[Synthesis]
|
v
[Worked example] --> [Exercise EX040]
Learning outcomes
You will (1) verify signed webhooks with HMAC; (2) limit replay with timestamps; (3) combine checks correctly in code.
14.1 Signed webhooks
The payment provider notifies the council when a permit is paid by sending an HTTP request containing a JSON body, a timestamp header and a signature header. The signature is an HMAC-SHA256, as in RFC 2104, computed over the timestamp and body with a secret shared between the provider and the council. The council recomputes the HMAC and compares. Rosa learns that the comparison must use a constant-time function, hmac.compare_digest in Python, so attackers cannot learn the correct signature byte by byte from timing.
Lab — Webhook verification
"""Verify a signed webhook with HMAC-SHA256 plus a 300-second timestamp window (synthetic payment notice)."""
import hmac, hashlib
SECRET = b"demo-webhook-secret-not-for-production"
WINDOW = 300
def sign(ts, body): return hmac.new(SECRET, f"{ts}.".encode() + body, hashlib.sha256).hexdigest()
def verify(ts, body, sig, now):
return abs(now - ts) <= WINDOW and hmac.compare_digest(sig, sign(ts, body))
def main():
body, ts = b'{"permit":"PK-2291","paid":45.00}', 1767225600
sig = sign(ts, body)
print("fresh, intact:", verify(ts, body, sig, now=ts + 20))
print("amount edited:", verify(ts, body.replace(b"45.00", b"4.50"), sig, now=ts + 20))
print("replayed after 1 hour:", verify(ts, body, sig, now=ts + 3600))
if __name__ == "__main__":
main()
Run with python3 lab_webhook.py. Verified output:
fresh, intact: True
amount edited: False
replayed after 1 hour: False
14.2 Reading the webhook run
A fresh, intact notification verifies as True. When the paid amount is edited from 45.00 to 4.50, verification fails because the HMAC no longer matches. When the same valid notification is replayed an hour later, verification fails because the timestamp falls outside the 300-second window. Rosa notes that the window limits replay to five minutes but does not prevent it entirely; the council also records processed notification identifiers and rejects duplicates. The demonstration secret is fixed only for reproducible output; the real secret lives in the secret store.
14.3 Combining checks
Rosa writes the verification order: read the raw body exactly as received, before any JSON parsing that might change spacing; check the timestamp window; recompute and compare the HMAC; check the notification identifier has not been processed; and only then act on the content. She also logs failures with reasons for operations, but returns a generic error to the caller. Owen reviews the code and asks her to add tests for each failure path, mirroring the lab cases. Each test sends a crafted request and asserts both the response code and the log line, so silent acceptance cannot slip through.
Worked example
Rosa verifies webhooks with HMAC and a 300-second window, adds duplicate-identifier checks, and orders the checks so raw bytes are verified before parsing.
Synthetic case. The secret and notification are invented; RFC 2104 is real.
Errors and limits
Verifying after JSON parsing can fail on harmless formatting changes. A timestamp window alone does not stop all replays.
Exercises
- EX040 — Signature input. What does the provider's HMAC cover? Rubric: 3 = the timestamp and the body;; 2 = both;; 1 = body only;; 0 = none.
- EX041 — Lab outcomes. What did the lab print for edited and replayed notifications? Rubric: 3 = False for edited amount and False for replay after one hour;; 2 = both;; 1 = one;; 0 = none.
- EX042 — Raw body. Why verify the raw body before parsing JSON? Rubric: 3 = parsing can change bytes such as spacing, breaking verification;; 2 = bytes;; 1 = speed;; 0 = none.
Synthesis
Webhook security combines HMAC over raw bytes, constant-time comparison, a timestamp window and duplicate detection.
Continue studying — chapter 14 of 30
← Chapter 13 (draft) · Course chapter index · Chapter 15 (draft) →