1"""
2Authentication Mechanisms Demo
3==============================
4
5Educational demonstration of authentication concepts:
6- JWT (JSON Web Token) creation and verification (manual, no PyJWT)
7- TOTP (Time-based One-Time Password) from scratch
8- Password strength checker
9- Secure token generation for password resets
10- Session ID generation
11
12Uses only Python standard library (base64, hmac, hashlib, secrets, time).
13No external dependencies required.
14"""
15
16import base64
17import hashlib
18import hmac
19import json
20import math
21import os
22import re
23import secrets
24import struct
25import time
26import string
27from datetime import datetime, timezone, timedelta
28
29print("=" * 65)
30print(" Authentication Mechanisms Demo")
31print("=" * 65)
32print()
33
34
35# ============================================================
36# Section 1: JWT (JSON Web Token) - Manual Implementation
37# ============================================================
38
39print("-" * 65)
40print(" Section 1: JWT (JSON Web Token) Implementation")
41print("-" * 65)
42
43print("""
44 JWT Structure: header.payload.signature
45 - Header: {"alg": "HS256", "typ": "JWT"} (base64url)
46 - Payload: Claims (iss, sub, exp, iat, ...) (base64url)
47 - Signature: HMAC-SHA256(header.payload, secret)
48""")
49
50
51def base64url_encode(data: bytes) -> str:
52 """Base64url encoding without padding (JWT standard)."""
53 return base64.urlsafe_b64encode(data).rstrip(b"=").decode("ascii")
54
55
56def base64url_decode(s: str) -> bytes:
57 """Base64url decoding with padding restoration."""
58 padding = 4 - len(s) % 4
59 if padding != 4:
60 s += "=" * padding
61 return base64.urlsafe_b64decode(s)
62
63
64def jwt_create(payload: dict, secret: str, exp_minutes: int = 60) -> str:
65 """Create a JWT token with HS256 signature."""
66 # Header
67 header = {"alg": "HS256", "typ": "JWT"}
68 header_b64 = base64url_encode(json.dumps(header, separators=(",", ":")).encode())
69
70 # Add standard claims
71 now = int(time.time())
72 payload = {
73 **payload,
74 "iat": now,
75 "exp": now + exp_minutes * 60,
76 }
77 payload_b64 = base64url_encode(json.dumps(payload, separators=(",", ":")).encode())
78
79 # Signature
80 signing_input = f"{header_b64}.{payload_b64}"
81 signature = hmac.new(
82 secret.encode(), signing_input.encode(), hashlib.sha256
83 ).digest()
84 signature_b64 = base64url_encode(signature)
85
86 return f"{header_b64}.{payload_b64}.{signature_b64}"
87
88
89def jwt_verify(token: str, secret: str) -> dict:
90 """Verify and decode a JWT token."""
91 parts = token.split(".")
92 if len(parts) != 3:
93 raise ValueError("Invalid JWT format: expected 3 parts")
94
95 header_b64, payload_b64, signature_b64 = parts
96
97 # Verify signature
98 signing_input = f"{header_b64}.{payload_b64}"
99 expected_sig = hmac.new(
100 secret.encode(), signing_input.encode(), hashlib.sha256
101 ).digest()
102 actual_sig = base64url_decode(signature_b64)
103
104 if not hmac.compare_digest(expected_sig, actual_sig):
105 raise ValueError("Invalid signature")
106
107 # Decode header and verify algorithm
108 header = json.loads(base64url_decode(header_b64))
109 if header.get("alg") != "HS256":
110 raise ValueError(f"Unsupported algorithm: {header.get('alg')}")
111
112 # Decode payload
113 payload = json.loads(base64url_decode(payload_b64))
114
115 # Check expiration
116 if "exp" in payload and payload["exp"] < int(time.time()):
117 raise ValueError("Token has expired")
118
119 return payload
120
121
122# Demo
123jwt_secret = "super-secret-key-change-in-production"
124claims = {
125 "sub": "user_12345",
126 "name": "Alice",
127 "role": "admin",
128 "iss": "auth.example.com",
129}
130
131token = jwt_create(claims, jwt_secret, exp_minutes=30)
132print(f"\n JWT Token:")
133parts = token.split(".")
134print(f" Header: {parts[0][:40]}...")
135print(f" Payload: {parts[1][:40]}...")
136print(f" Signature: {parts[2]}")
137print(f" Full ({len(token)} chars): {token[:50]}...")
138print()
139
140# Decode and display
141decoded_header = json.loads(base64url_decode(parts[0]))
142decoded_payload = json.loads(base64url_decode(parts[1]))
143print(f" Decoded Header: {json.dumps(decoded_header)}")
144print(f" Decoded Payload: {json.dumps(decoded_payload, indent=2)}")
145print()
146
147# Verify valid token
148try:
149 verified = jwt_verify(token, jwt_secret)
150 print(f" Verification: VALID")
151 print(f" User: {verified['name']} ({verified['sub']})")
152 print(f" Role: {verified['role']}")
153except ValueError as e:
154 print(f" Verification: FAILED - {e}")
155
156# Verify with wrong secret
157try:
158 jwt_verify(token, "wrong-secret")
159 print(f" Wrong secret: VALID (should not happen!)")
160except ValueError as e:
161 print(f" Wrong secret: FAILED - {e}")
162
163# Verify expired token
164expired_token = jwt_create(claims, jwt_secret, exp_minutes=-1)
165try:
166 jwt_verify(expired_token, jwt_secret)
167 print(f" Expired token: VALID (should not happen!)")
168except ValueError as e:
169 print(f" Expired token: FAILED - {e}")
170
171print()
172
173
174# ============================================================
175# Section 2: TOTP (Time-based One-Time Password)
176# ============================================================
177
178print("-" * 65)
179print(" Section 2: TOTP (Time-based One-Time Password)")
180print("-" * 65)
181
182print("""
183 TOTP (RFC 6238) generates 6-digit codes that change every 30s.
184 Used by: Google Authenticator, Authy, 1Password, etc.
185
186 Algorithm:
187 1. shared_secret = random 20+ bytes (base32 encoded)
188 2. counter = floor(current_time / 30)
189 3. hmac = HMAC-SHA1(secret, counter_as_8_bytes)
190 4. offset = hmac[-1] & 0x0F
191 5. code = (hmac[offset:offset+4] & 0x7FFFFFFF) % 10^digits
192""")
193
194
195def generate_totp_secret(length: int = 20) -> bytes:
196 """Generate a random TOTP secret."""
197 return os.urandom(length)
198
199
200def totp_generate(secret: bytes, time_step: int = 30, digits: int = 6,
201 timestamp: float = None) -> str:
202 """Generate a TOTP code (RFC 6238)."""
203 if timestamp is None:
204 timestamp = time.time()
205
206 # Step 1: Calculate time counter
207 counter = int(timestamp) // time_step
208
209 # Step 2: HMAC-SHA1 of counter
210 counter_bytes = struct.pack(">Q", counter) # 8-byte big-endian
211 hmac_digest = hmac.new(secret, counter_bytes, hashlib.sha1).digest()
212
213 # Step 3: Dynamic truncation
214 offset = hmac_digest[-1] & 0x0F
215 binary_code = struct.unpack(">I", hmac_digest[offset:offset + 4])[0]
216 binary_code &= 0x7FFFFFFF # Remove sign bit
217
218 # Step 4: Modulo to get desired digits
219 otp = binary_code % (10 ** digits)
220 return str(otp).zfill(digits)
221
222
223def totp_verify(secret: bytes, code: str, time_step: int = 30,
224 window: int = 1) -> bool:
225 """Verify a TOTP code with a time window for clock skew."""
226 now = time.time()
227 for offset in range(-window, window + 1):
228 check_time = now + offset * time_step
229 expected = totp_generate(secret, time_step, len(code), check_time)
230 if hmac.compare_digest(code, expected):
231 return True
232 return False
233
234
235# Demo
236totp_secret = generate_totp_secret()
237secret_b32 = base64.b32encode(totp_secret).decode()
238
239print(f"\n Secret (base32): {secret_b32}")
240print(f" Secret (hex): {totp_secret.hex()}")
241print()
242
243# Generate current code
244current_time = time.time()
245current_code = totp_generate(totp_secret, timestamp=current_time)
246time_step = 30
247remaining = time_step - (int(current_time) % time_step)
248
249print(f" Current TOTP: {current_code}")
250print(f" Valid for: {remaining}s (of {time_step}s window)")
251print()
252
253# Show codes across time windows
254print(" TOTP codes across time windows:")
255for offset in range(-2, 3):
256 t = current_time + offset * 30
257 code = totp_generate(totp_secret, timestamp=t)
258 marker = " <-- current" if offset == 0 else ""
259 label = f"T{offset:+d}" if offset != 0 else "T=0"
260 print(f" {label}: {code}{marker}")
261print()
262
263# Verify
264is_valid = totp_verify(totp_secret, current_code)
265is_invalid = totp_verify(totp_secret, "000000")
266print(f" Verify '{current_code}': {is_valid}")
267print(f" Verify '000000': {is_invalid}")
268print()
269
270# QR code URL format (for authenticator apps)
271account = "user@example.com"
272issuer = "MyApp"
273otpauth_url = (
274 f"otpauth://totp/{issuer}:{account}"
275 f"?secret={secret_b32}&issuer={issuer}&algorithm=SHA1&digits=6&period=30"
276)
277print(f" QR Code URL (for authenticator apps):")
278print(f" {otpauth_url}")
279print()
280
281
282# ============================================================
283# Section 3: Password Strength Checker
284# ============================================================
285
286print("-" * 65)
287print(" Section 3: Password Strength Checker")
288print("-" * 65)
289
290
291def check_password_strength(password: str) -> dict:
292 """Evaluate password strength with detailed feedback."""
293 score = 0
294 feedback = []
295 checks = {}
296
297 # Length check
298 length = len(password)
299 checks["length"] = length
300 if length >= 16:
301 score += 3
302 elif length >= 12:
303 score += 2
304 elif length >= 8:
305 score += 1
306 else:
307 feedback.append("Use at least 8 characters (12+ recommended)")
308
309 # Character class checks
310 has_lower = bool(re.search(r"[a-z]", password))
311 has_upper = bool(re.search(r"[A-Z]", password))
312 has_digit = bool(re.search(r"\d", password))
313 has_special = bool(re.search(r"[!@#$%^&*()_+\-=\[\]{};':\"\\|,.<>\/?]", password))
314
315 checks["lowercase"] = has_lower
316 checks["uppercase"] = has_upper
317 checks["digits"] = has_digit
318 checks["special"] = has_special
319
320 char_classes = sum([has_lower, has_upper, has_digit, has_special])
321 score += char_classes
322 if not has_lower:
323 feedback.append("Add lowercase letters")
324 if not has_upper:
325 feedback.append("Add uppercase letters")
326 if not has_digit:
327 feedback.append("Add numbers")
328 if not has_special:
329 feedback.append("Add special characters (!@#$%^&*...)")
330
331 # Common pattern checks
332 common_patterns = [
333 (r"(.)\1{2,}", "Avoid repeated characters (aaa, 111)"),
334 (r"(012|123|234|345|456|567|678|789)", "Avoid sequential numbers"),
335 (r"(abc|bcd|cde|def|efg|fgh|ghi)", "Avoid sequential letters"),
336 (r"(?i)(password|qwerty|admin|login|welcome)", "Avoid common words"),
337 ]
338
339 for pattern, message in common_patterns:
340 if re.search(pattern, password):
341 score -= 1
342 feedback.append(message)
343
344 # Entropy estimation
345 pool_size = 0
346 if has_lower:
347 pool_size += 26
348 if has_upper:
349 pool_size += 26
350 if has_digit:
351 pool_size += 10
352 if has_special:
353 pool_size += 32
354 entropy = length * math.log2(pool_size) if pool_size > 0 else 0
355 checks["entropy_bits"] = round(entropy, 1)
356
357 # Determine strength level
358 score = max(0, min(score, 7))
359 if score >= 6:
360 strength = "STRONG"
361 elif score >= 4:
362 strength = "MODERATE"
363 elif score >= 2:
364 strength = "WEAK"
365 else:
366 strength = "VERY WEAK"
367
368 return {
369 "password": password[:2] + "*" * (len(password) - 2),
370 "score": score,
371 "max_score": 7,
372 "strength": strength,
373 "entropy_bits": checks["entropy_bits"],
374 "checks": checks,
375 "feedback": feedback if feedback else ["Good password!"],
376 }
377
378
379# Test various passwords
380test_passwords = [
381 "password",
382 "Admin123",
383 "MyD0g$N@me!",
384 "correct-horse-battery-staple",
385 "Tr0ub4dor&3",
386 "j&Hx9#mK2$pL@nQ!",
387]
388
389print()
390for pwd in test_passwords:
391 result = check_password_strength(pwd)
392 bar = "#" * result["score"] + "." * (result["max_score"] - result["score"])
393 print(f" Password: {result['password']:<22} [{bar}] {result['strength']}")
394 print(f" Entropy: {result['entropy_bits']} bits, Score: {result['score']}/{result['max_score']}")
395 if result["feedback"] and result["feedback"][0] != "Good password!":
396 for fb in result["feedback"][:2]:
397 print(f" - {fb}")
398 print()
399
400print(" Entropy benchmarks:")
401print(" < 28 bits: Trivially crackable")
402print(" 28-35 bits: Crackable with effort")
403print(" 36-59 bits: Reasonable for online attacks")
404print(" 60-127 bits: Strong against offline attacks")
405print(" 128+ bits: Computationally infeasible")
406print()
407
408
409# ============================================================
410# Section 4: Secure Token Generation
411# ============================================================
412
413print("-" * 65)
414print(" Section 4: Secure Token Generation")
415print("-" * 65)
416
417print("""
418 Tokens must be cryptographically random (not predictable).
419 Python's `secrets` module uses OS-level CSPRNG.
420""")
421
422# Password reset token
423reset_token = secrets.token_urlsafe(32)
424print(f" Password Reset Token: {reset_token}")
425print(f" Length: {len(reset_token)} chars")
426print(f" Entropy: 256 bits")
427print()
428
429# Email verification token
430email_token = secrets.token_hex(16)
431print(f" Email Verify Token: {email_token}")
432print(f" Length: {len(email_token)} chars")
433print()
434
435# API key generation
436def generate_api_key(prefix: str = "sk") -> str:
437 """Generate an API key with prefix (like Stripe sk_live_...)."""
438 random_part = secrets.token_urlsafe(32)
439 return f"{prefix}_{random_part}"
440
441api_key = generate_api_key("sk_live")
442print(f" API Key: {api_key}")
443print()
444
445# Token with expiration (stored in DB)
446def create_token_record(purpose: str, ttl_hours: int = 24) -> dict:
447 """Create a token record for database storage."""
448 token = secrets.token_urlsafe(32)
449 token_hash = hashlib.sha256(token.encode()).hexdigest()
450 now = datetime.now(timezone.utc)
451 return {
452 "token_plaintext": token, # Send to user (email, etc.)
453 "token_hash": token_hash, # Store in DB (never store plaintext!)
454 "purpose": purpose,
455 "created_at": now.isoformat(),
456 "expires_at": (now + timedelta(hours=ttl_hours)).isoformat(),
457 }
458
459record = create_token_record("password_reset", ttl_hours=1)
460print(f" Token Record (for DB storage):")
461print(f" Plaintext (sent): {record['token_plaintext'][:30]}...")
462print(f" Hash (stored): {record['token_hash'][:32]}...")
463print(f" Purpose: {record['purpose']}")
464print(f" Expires: {record['expires_at']}")
465print()
466print(" IMPORTANT: Store only the HASH in the database.")
467print(" When user presents token, hash it and compare to stored hash.")
468print()
469
470
471# ============================================================
472# Section 5: Session ID Generation
473# ============================================================
474
475print("-" * 65)
476print(" Section 5: Session ID Generation")
477print("-" * 65)
478
479print("""
480 Session IDs must be:
481 - Cryptographically random (unpredictable)
482 - Sufficiently long (128+ bits of entropy)
483 - Unique across all active sessions
484 - Regenerated after authentication changes
485""")
486
487
488def generate_session_id() -> str:
489 """Generate a secure session ID (128 bits of entropy)."""
490 return secrets.token_hex(16)
491
492
493def generate_session_with_metadata() -> dict:
494 """Create a session with metadata for server-side storage."""
495 session_id = secrets.token_hex(32)
496 return {
497 "session_id": session_id,
498 "created_at": datetime.now(timezone.utc).isoformat(),
499 "last_accessed": datetime.now(timezone.utc).isoformat(),
500 "ip_address": "192.168.1.100", # From request
501 "user_agent": "Mozilla/5.0...", # From request headers
502 "user_id": None, # Set after login
503 "is_authenticated": False,
504 }
505
506
507session = generate_session_with_metadata()
508print(f"\n Session ID: {session['session_id'][:32]}...")
509print(f" Created: {session['created_at']}")
510print(f" Authenticated: {session['is_authenticated']}")
511print()
512
513# Show multiple unique session IDs
514print(" Sample session IDs (all unique):")
515for i in range(5):
516 sid = generate_session_id()
517 print(f" {i+1}. {sid}")
518print()
519
520# Session cookie attributes
521print(" Secure session cookie attributes:")
522print(" Set-Cookie: session_id=<token>;")
523print(" HttpOnly; -- no JavaScript access")
524print(" Secure; -- HTTPS only")
525print(" SameSite=Lax; -- CSRF protection")
526print(" Path=/;")
527print(" Max-Age=3600; -- 1 hour expiry")
528print()
529
530
531# ============================================================
532# Section 6: Summary
533# ============================================================
534
535print("=" * 65)
536print(" Summary")
537print("=" * 65)
538print("""
539 Mechanism | Use Case | Key Points
540 -----------------+-----------------------+---------------------------
541 JWT (HS256) | Stateless auth | Short-lived, secret key
542 TOTP | 2FA / MFA | Time-based, 30s window
543 Password check | Registration/update | Entropy, patterns, length
544 Reset tokens | Password recovery | Hash before storing
545 Session IDs | Stateful auth | Random, HttpOnly, Secure
546
547 Authentication Best Practices:
548 - Always use MFA/2FA (TOTP or WebAuthn)
549 - Hash password reset tokens before DB storage
550 - Set short expiration for sensitive tokens
551 - Regenerate session IDs after login/logout
552 - Use HttpOnly + Secure + SameSite cookies
553 - Rate-limit authentication endpoints
554 - Log all authentication events
555""")