learning-website-framework1-9 Exercise 2: Cookies and Password Storage ===================================================================== PART A: Decide how the login cookie is shared (or not) ------------------------------------------------------- Two cookie designs, built and checked by script: - SHARED: Domain=osztromok.com. One login for every subdomain, but the cookie is sent to EVERY subdomain, so one weak subdomain weakens all of them. - HOST-ONLY: a __Host- cookie. Browsers only accept it with Secure, Path=/ and NO Domain attribute, so it can never be shared, and each site logs in separately (or gets its own session through a central login). PART B: Store passwords safely ------------------------------ Never store a password, only a salted, deliberately slow hash, and compare in constant time. (The live Anime Vault uses PHP's password_hash with bcrypt; this example uses scrypt from Python's standard library.) Save as sessions.py: import hashlib, hmac, os, time DOMAIN = "osztromok.com" def session_cookie(value, shared): """Build a Set-Cookie header value. shared=True : one login for every subdomain (Domain attribute, name without prefix) shared=False: host-only cookie with the __Host- prefix (this one site only)""" if shared: return (f"sid={value}; Domain={DOMAIN}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400") return (f"__Host-sid={value}; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400") def check_cookie(header): """Return a list of problems with a Set-Cookie header value.""" parts = [p.strip() for p in header.split(";")] name = parts[0].split("=", 1)[0] attrs = {p.split("=", 1)[0].lower(): (p.split("=", 1)[1] if "=" in p else True) for p in parts[1:]} problems = [] if "secure" not in attrs: problems.append("missing Secure") if "httponly" not in attrs: problems.append("missing HttpOnly") if "samesite" not in attrs: problems.append("missing SameSite") if name.startswith("__Host-"): if "domain" in attrs: problems.append("__Host- cookies must not set Domain") if attrs.get("path") != "/": problems.append("__Host- cookies need Path=/") return problems def hash_password(password, salt=None): salt = salt or os.urandom(16) digest = hashlib.scrypt(password.encode(), salt=salt, n=2**14, r=8, p=1, dklen=32) return salt.hex() + "$" + digest.hex() def verify_password(password, stored): salt_hex, digest_hex = stored.split("$") candidate = hash_password(password, bytes.fromhex(salt_hex)).split("$")[1] return hmac.compare_digest(candidate, digest_hex) # constant-time comparison if __name__ == "__main__": shared = session_cookie("abc123", shared=True) host_only = session_cookie("abc123", shared=False) print("shared :", shared) print(" problems:", check_cookie(shared) or "none") print("host-only:", host_only) print(" problems:", check_cookie(host_only) or "none") for bad in ("sid=abc; Path=/", "__Host-sid=abc; Domain=osztromok.com; Secure; HttpOnly; SameSite=Lax; Path=/"): print("bad :", bad) print(" problems:", ", ".join(check_cookie(bad))) print() t = time.perf_counter() stored = hash_password("correct horse battery staple") ms = (time.perf_counter() - t) * 1000 print(f"stored format: <32 hex salt>$<64 hex hash> (length {len(stored)}), hashing took about {ms:.0f} ms") print("same password twice gives different stored values:", hash_password("pw") != hash_password("pw")) print("right password verifies:", verify_password("correct horse battery staple", stored)) print("wrong password verifies:", verify_password("wrong", stored)) Run it: python sessions.py Output (checked by running it): shared : sid=abc123; Domain=osztromok.com; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400 problems: none host-only: __Host-sid=abc123; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=86400 problems: none bad : sid=abc; Path=/ problems: missing Secure, missing HttpOnly, missing SameSite bad : __Host-sid=abc; Domain=osztromok.com; Secure; HttpOnly; SameSite=Lax; Path=/ problems: __Host- cookies must not set Domain stored format: <32 hex salt>$<64 hex hash> (length 97), hashing took about 57 ms same password twice gives different stored values: True right password verifies: True wrong password verifies: False Notes ----- - Secure sends the cookie only over HTTPS; HttpOnly hides it from page scripts (a stolen cookie via script injection is the usual attack); SameSite=Lax stops it being sent on most cross-site requests. - A shared cookie ties the security of every subdomain together. If you choose it, every site must be equally careful, and no subdomain may be handed to a third party. Otherwise prefer host-only cookies. - The same password gives a different stored value each time because the salt is random, which defeats pre-computed tables. - Hashing takes tens of milliseconds on purpose, which makes guessing slow. Tune the cost so that a login takes a noticeable fraction of a second. WHY THIS WORKS AS AN ANSWER --------------------------- Whether to share a login is a security trade-off, and writing the two cookie headers side by side, with a checker, makes the difference concrete. The password example shows the three habits that matter: random salt, slow hash and constant-time comparison.