chatbot1-10 Exercise 2: Verifying Slack Signatures ===================================================== ex2.py (save in the project root): import hashlib, hmac, json from bot.channels.slack import sign, verify_request SECRET = "my-signing-secret" NOW = 1_700_000_000 body = b'{"type":"event_callback","event_id":"Ev1","event":{"type":"message"}}' ts = str(NOW) good = sign(SECRET, ts, body) print("signature:", good[:18] + "...") print("independent calculation matches:", good == "v0=" + hmac.new(SECRET.encode(), b"v0:" + ts.encode() + b":" + body, hashlib.sha256).hexdigest()) cases = { "untouched request": (SECRET, ts, body, good, NOW), "wrong signing secret": ("other-secret", ts, body, good, NOW), "body changed by one byte": (SECRET, ts, body + b" ", good, NOW), "body re-serialised (same data)": (SECRET, ts, json.dumps(json.loads(body)).encode(), good, NOW), "timestamp edited": (SECRET, str(NOW + 1), body, good, NOW), "request is 4 minutes old": (SECRET, str(NOW - 240), body, sign(SECRET, str(NOW - 240), body), NOW), "request is 6 minutes old (replay)": (SECRET, str(NOW - 360), body, sign(SECRET, str(NOW - 360), body), NOW), "signature header missing": (SECRET, ts, body, None, NOW), } for label, (secret, t, b, sig, now) in cases.items(): print(f"{label:<36} -> {'accepted' if verify_request(secret, t, b, sig, now=now) else 'REFUSED'}") Run with: py ex2.py Output: signature: v0=7416ec16f749537... independent calculation matches: True untouched request -> accepted wrong signing secret -> REFUSED body changed by one byte -> REFUSED body re-serialised (same data) -> REFUSED timestamp edited -> REFUSED request is 4 minutes old -> accepted request is 6 minutes old (replay) -> REFUSED signature header missing -> REFUSED Explaining each result: untouched request - accepted: the signature is HMAC-SHA256 of "v0::" keyed with the signing secret, and it matches. wrong signing secret - refused: a different key gives a different HMAC. body changed by one byte - refused: the signature covers every byte of the body. body re-serialised (same data) - refused: json.loads then json.dumps produced different bytes (spaces after separators), so the HMAC differs. This is why the route reads the raw body before parsing it. timestamp edited - refused: the timestamp is part of the signed text, so changing it breaks the signature. 4 minutes old - accepted; 6 minutes old - refused (even with a valid signature): the five-minute window stops someone replaying a captured request later. signature header missing - refused: no signature means no proof. WHY THIS WORKS AS AN ANSWER --------------------------- verify_request recomputes the signature from the secret, the timestamp header and the raw body, compares it to the header with hmac.compare_digest (a comparison that does not stop at the first different character, so timing does not leak how much matched), and separately refuses timestamps more than 300 seconds from the receiver's clock. The independent calculation at the top uses hmac and hashlib directly, written out separately, and agreed with sign(), which guards against a mistake that both the code and its own test would share.