Python for Security Engineering
This directory contains everything you need to be interview-ready in Python: hands-on exercises across 10 topic areas, security-specific code patterns, a quick cheatsheet, and common interview questions with hints.
New to the algorithm patterns?Start with patterns-primer.md — the canonical template for each of the 14 core patterns (sliding window, two pointers, binary search, BFS/DFS, heap, backtracking, DP, etc.) with comments explaining why each works and how to pick the right one in an interview.
Exercise Ladder
12 topic areas, 10 levels each. Each file is self-contained and self-testing.
python/ ├── cheatsheet.py — one-file reference for syntax, builtins, comprehensions │ ├── strings/ — manipulation, regex, ciphers, sliding substring ├── lists/ — in-place ops, two-sum, rotate, merge sorted, intervals ├── dicts/ — frequency counting, grouping, inversion, Top K, anagrams ├── sets/ — uniqueness, intersection, Jaccard, consecutive sequences ├── iteration/ — enumerate, zip, comprehensions, matrix ops, Pascal's triangle ├── recursion/ — factorial, Fibonacci, GCD, flatten, permutations ├── classes/ — OOP, properties, dunders (__eq__, __repr__), context managers ├── tuples_and_unpacking/ — structured records, namedtuple, variadic unpacking, zip/unzip ├── sorting_and_lambdas/ — key functions, multi-key sort, sort by frequency/priority ├── sliding_window/ — fixed window, variable window, rate limiter, anagram search ├── log_parsing/ — split/regex field extraction, brute-force-by-IP, access logs, JSON logs └── time_windows/ — windowing over timestamped events: bursts, scans, sessions, rate alerts
Practice format
log_parsing/andtime_windows/ship levels 01–02 as solved reference templates (read these first to learn the pattern) and levels 03–10 as stubs (pass) for you to implement. Run a stub before you've filled it in and it raisesAssertionError— that's the practice loop working.
How to Run Exercises
Each file runs independently. A passing solution prints OK; a failing one raises AssertionError with a helpful message.
# Run a single exercise
python strings/level_01_reverse_string.py
# Run all exercises in a topic
for f in strings/level_*.py; do python "$f" && echo "PASS: $f" || echo "FAIL: $f"; done
# Run everything
for f in */level_*.py; do python "$f" && echo "PASS: $f" || echo "FAIL: $f"; doneDifficulty guide
| Level | Effort | What it tests |
|---|---|---|
| 01–02 | ~2 min | Single built-in or operation |
| 03–05 | ~5 min | Combining 2 concepts; light logic |
| 06–08 | ~10 min | Algorithm design, edge cases |
| 09–10 | ~15 min | Full patterns: OT, dunders, rate limiter, deduplication |
Also check cheatsheet.py for a fast syntax refresher before an interview.
Topic Breakdown
strings/
Core string manipulation. By level 10 you can implement decode-string (LeetCode 394) and longest palindromic substring from scratch.
| Level | Problem |
|---|---|
| 01 | Reverse a string |
| 02 | Count vowels / palindrome check |
| 03 | Anagram check, reverse words, first non-repeating char |
| 04 | Capitalize words |
| 05 | Caesar cipher (useful for security interviews) |
| 06 | Run-length encoding / longest unique substring (WIP) |
| 07 | Longest common prefix |
| 08 | Is rotation (string contains rotated version) |
| 09 | Longest palindromic substring |
| 10 | Decode string (bracket expansion) |
lists/
In-place operations, classic two-pointer and interval problems.
| Level | Problem |
|---|---|
| 01 | Sum of list / find largest number |
| 02 | Find min and max |
| 03 | Running sum |
| 04 | Remove duplicates, preserve order |
| 05 | Rotate list by k positions |
| 06 | Two Sum (hash map approach) |
| 07 | Move zeroes in-place |
| 08 | Merge two sorted lists |
| 09 | Merge overlapping intervals |
| 10 | Product of array except self |
dicts/
Counting, grouping, and classic hash map patterns that come up constantly in security log analysis.
| Level | Problem |
|---|---|
| 01 | Word frequency count |
| 02 | Dict from two lists (zip) |
| 03 | Invert a dictionary |
| 04 | Group words by first letter |
| 05 | Most common element |
| 06 | Merge dicts by summing values |
| 07 | First unique character |
| 08 | Top K frequent elements |
| 09 | Isomorphic strings |
| 10 | Group anagrams |
sets/
Set operations and uniqueness problems. Bloom filter intuition lives here.
| Level | Problem |
|---|---|
| 01 | Unique elements |
| 02 | Intersection of two lists |
| 03 | Symmetric difference |
| 04 | Anagram check (Counter) |
| 05 | Contains duplicate within distance k |
| 06 | Jaccard similarity |
| 07 | Elements common to all lists |
| 08 | Find all duplicates |
| 09 | Longest consecutive sequence |
| 10 | Smallest missing positive |
sliding_window/
The pattern behind rate limiters, anomaly detection windows, and substring search.
| Level | Problem |
|---|---|
| 01 | Count increasing adjacent pairs |
| 02 | Max pair sum |
| 03 | Window averages (fixed size) |
| 04 | Max sum of k consecutive / longest subarray sum ≤ k |
| 05 | Max average fixed window |
| 06 | Count anagram substrings |
| 07 | Longest substring with k distinct chars |
| 08 | Min window with sum |
| 09 | Max in each window (deque) |
| 10 | Rate limiter with deduplication |
log_parsing/
Turning raw log text into structured data — the first step of almost every security task. Levels 01–02 are solved templates; 03–10 are practice stubs.
| Level | Problem |
|---|---|
| 01 | Parse a pipe-delimited log line (maxsplit) — solved |
| 02 | Extract all IPv4 addresses with regex — solved |
| 03 | Count log lines by severity level |
| 04 | Filter to ERROR lines only |
| 05 | Parse key=value (logfmt) structured logs |
| 06 | Brute-force detection: failed logins by IP |
| 07 | Count HTTP status codes from an access log |
| 08 | Top N requested paths |
| 09 | Total response bytes per client IP |
| 10 | Extract messages from JSON-lines logs (skip malformed) |
time_windows/
Windowing over timestamped event streams — the two-pointer-over-time pattern
that powers burst detection, scan detection, and rate alerts. The natural
follow-on to log_parsing/ (parse events, then window them). Levels 01–02 solved.
| Level | Problem |
|---|---|
| 01 | Count events in a time window — solved |
| 02 | Bucket timestamps into fixed time buckets — solved |
| 03 | Most events in any sliding time window (burst size) |
| 04 | Time of the first burst (alert trigger) |
| 05 | Most distinct keys in any time window |
| 06 | Port-scan detection (distinct ports per source) |
| 07 | Sessionize an event stream by idle gap |
| 08 | Rolling per-key count (live sliding window) |
| 09 | Maximum value sum in any time window |
| 10 | Brute-force alert (window + per-IP threshold) — capstone |
classes/
OOP patterns. Levels 8–10 cover the dunder methods and context managers that come up in "write a thread-safe cache" style questions.
| Level | Problem |
|---|---|
| 01 | Cookie class / Counter class |
| 02 | Point class with distance |
| 03 | Bank account (balance, transactions) |
| 04 | Rectangle with area and perimeter |
| 05 | Stack with push/pop/peek |
| 06 | Animal inheritance (polymorphism) |
| 07 | Temperature with property getter/setter |
| 08 | Dunder methods (eq, repr, len, add) |
| 09 | Countdown iterator (iter, next) |
| 10 | Context manager (enter, exit, resource cleanup) |
Security Code Patterns
Log Parsing & Brute Force Detection
import re
from collections import Counter
LOG_LINE = r'Failed password for (\w+) from ([\d.]+) port (\d+)'
def parse_auth_failures(log_text: str) -> list[dict]:
return [
{"user": m.group(1), "ip": m.group(2), "port": int(m.group(3))}
for m in re.finditer(LOG_LINE, log_text)
]
def find_brute_force(failures: list[dict], threshold: int = 5) -> list[str]:
counts = Counter(f["ip"] for f in failures)
return [ip for ip, count in counts.items() if count >= threshold]Port Scanner
import socket
from concurrent.futures import ThreadPoolExecutor
def scan_port(host: str, port: int, timeout: float = 0.5) -> int | None:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(timeout)
return port if s.connect_ex((host, port)) == 0 else None
def scan_ports(host: str, ports: range) -> list[int]:
with ThreadPoolExecutor(max_workers=100) as pool:
results = pool.map(lambda p: scan_port(host, p), ports)
return [p for p in results if p is not None]HMAC Signature Verification
import hmac, hashlib
def sign_payload(payload: bytes, key: bytes) -> str:
return hmac.new(key, payload, hashlib.sha256).hexdigest()
def verify_payload(payload: bytes, key: bytes, signature: str) -> bool:
expected = sign_payload(payload, key)
return hmac.compare_digest(expected, signature) # constant-time — prevents timing attacksTiming-Safe Comparison
import hmac
def safe_compare(a: str, b: str) -> bool:
return hmac.compare_digest(a.encode(), b.encode())
# Never use == for secrets: early exit leaks information via timingDNS Exfiltration Detector
import math
from collections import Counter
def shannon_entropy(s: str) -> float:
counts = Counter(s)
length = len(s)
return -sum((c / length) * math.log2(c / length) for c in counts.values())
def detect_dns_exfil(queries: list[str], threshold: float = 4.0) -> list[str]:
suspicious = []
for query in queries:
subdomain = query.split('.')[0]
if len(subdomain) > 20 and shannon_entropy(subdomain) > threshold:
suspicious.append(query)
return suspicious
# High entropy + long subdomain = likely base64/hex encoded data in DNS queryYARA-like Malware Scanner
import re
from dataclasses import dataclass
@dataclass
class Rule:
name: str
patterns: list[bytes]
def matches(self, content: bytes) -> bool:
return all(re.search(p, content) for p in self.patterns)
RULES = [
Rule("crypto_miner", [rb"stratum\+tcp", rb"xmrig"]),
Rule("reverse_shell", [rb"/bin/sh", rb"connect\(", rb"socket\("]),
Rule("credential_dump", [rb"lsass", rb"mimikatz", rb"sekurlsa"]),
]
def scan_binary(path: str) -> list[str]:
with open(path, 'rb') as f:
content = f.read()
return [rule.name for rule in RULES if rule.matches(content)]Rate Limiter (Sliding Window)
This is level 10 in sliding_window/ — and a genuine interview question.
from collections import deque
import time
class RateLimiter:
"""Sliding window rate limiter. O(1) amortised allow() calls."""
def __init__(self, max_requests: int, window_seconds: float):
self.max_requests = max_requests
self.window = window_seconds
self.timestamps: deque[float] = deque()
def allow(self) -> bool:
now = time.monotonic()
while self.timestamps and now - self.timestamps[0] > self.window:
self.timestamps.popleft()
if len(self.timestamps) < self.max_requests:
self.timestamps.append(now)
return True
return False
# Usage: limiter = RateLimiter(max_requests=100, window_seconds=60)Common Security Interview Questions
| Problem | Key Topics | Hint |
|---|---|---|
| Parse Apache/nginx logs, count 4xx errors by IP | Regex, Counter | re.findall, dict comprehension |
| Detect port scan: same src IP, many distinct dst ports in 60s | Sliding window, Counter | Count unique dsts per src in time window |
| Implement Caesar cipher / ROT-13 | Strings, modular arithmetic | ord, chr, (x - base + shift) % 26 |
| Implement HMAC-SHA256 request signing | Crypto | hmac.new(key, payload, hashlib.sha256) |
| Find duplicate files by content hash | Hashing, dict grouping | hashlib.sha256, defaultdict(list) |
| Sliding window rate limiter | Sliding window, deque | collections.deque, time.monotonic |
| Binary search in sorted list of timestamps | Binary search | bisect.bisect_left |
| Detect high-entropy DNS subdomains (exfil) | Shannon entropy, strings | -sum(p * log2(p) for p in probabilities) |
| Build a Trie for IP prefix matching | Classes, recursion | children: dict[str, TrieNode] |
| Recursive directory listing with depth limit | Recursion, pathlib | Path.iterdir(), track current depth |
| Parse CIDR notation, check if IP is in range | Bitwise ops | ipaddress.ip_network, ip_address in network |
| Decode a base64-encoded payload, extract fields | base64, json | base64.b64decode, json.loads |
Security Anti-Patterns
| Anti-pattern | Risk | Correct approach |
|---|---|---|
eval(user_input) | RCE — executes arbitrary Python | ast.literal_eval for safe data parsing only |
subprocess(cmd, shell=True) with user data | Command injection | shell=False, pass args as list |
if token == expected: | Timing side-channel leaks secret length | hmac.compare_digest(token, expected) |
pickle.loads(untrusted_bytes) | RCE — pickle executes __reduce__ on load | Use JSON or msgpack for untrusted data |
yaml.load(data) | RCE — PyYAML executes Python constructors | yaml.safe_load(data) always |
| Hard-coded secrets in source | Credential exposure in repos, logs | os.environ["SECRET"] or secrets manager |
requests.get(url, verify=False) | Disables TLS cert verification; MITM risk | Keep verify=True (default); pin certs if needed |
Logging request.body or auth headers | Sensitive data leaks to log aggregators | Scrub secrets before logging; use structured logging |
random.randint() for tokens/keys | Predictable output; not cryptographically secure | secrets.token_hex(32) or secrets.token_urlsafe() |
| String formatting in SQL queries | SQL injection | Parameterised queries; ORM |
Standard Library Quick Reference
# Cryptography & integrity
import hashlib # hashlib.sha256(data).hexdigest()
import hmac # hmac.new(key, msg, hashlib.sha256).hexdigest()
import secrets # secrets.token_hex(32), secrets.token_urlsafe(32)
# Encoding
import base64 # base64.b64encode/b64decode
import binascii # hexlify / unhexlify
# Network
import socket # TCP/UDP, connect_ex for port scanning
import ssl # TLS wrapping, ssl.create_default_context()
import ipaddress # ip_address, ip_network, CIDR membership
# Process & OS
import subprocess # run(args, shell=False, capture_output=True)
import os # os.environ, os.walk, os.stat
import pathlib # Path traversal, iterdir, glob
# Parsing
import re # findall, finditer, sub, compile
import json # loads / dumps
import csv # DictReader for structured log files
import ast # ast.literal_eval — safe eval for Python literals
# Data structures
from collections import Counter, defaultdict, deque, OrderedDict
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
# Time
import time # time.monotonic() for rate limiters (no clock drift)
import datetime # datetime.utcnow(), timedelta