Frontend Labs
⋯

26. Quantitative Security

Level: Expert You'll use this for: turning "this feels risky" into a defensible, numeric argument.

Password/key strength, connected end-to-end

Combining Ch. 15 (keyspace), Ch. 18 (brute-force time), and Ch. 23 (entropy) into one practical workflow:

import math

def crack_time_estimate(entropy_bits, guesses_per_second):
    total_guesses = 2 ** entropy_bits
    avg_guesses = total_guesses / 2       # attacker expected to find it halfway through
    seconds = avg_guesses / guesses_per_second
    return seconds / (60*60*24*365)       # in years

# Offline attack against a fast, unsalted-hash-cracking rig (10^11 guesses/sec)
print(crack_time_estimate(40, 1e11), "years")   # weak: cracked in seconds
print(crack_time_estimate(80, 1e11), "years")   # strong: computationally infeasible

# Online attack, rate-limited to 10 guesses/sec (a login form with lockout)
print(crack_time_estimate(40, 10), "years")     # even "weak" 40-bit entropy holds up when rate-limited

The takeaway that changes real security architecture decisions: entropy alone doesn't determine practical security — entropy combined with attacker guess rate does. This is why rate-limiting a login endpoint is often more impactful than forcing users into unmemorable passwords, and why offline-crackable hash leaks (fast hash, no rate limit possible) are categorically worse than online-only exposure.

Detection system quality: precision, recall, ROC/AUC

Building directly on Bayes' theorem (Ch. 16):

Precision = TP / (TP + FP)     "of things I flagged, how many were real?"
Recall    = TP / (TP + FN)     "of real things, how many did I catch?"

A tiny worked example, by hand. Say a detector runs against 20 events: 8 real attacks, 12 benign. It flags 10 events, and of those 10, 6 turn out to be real attacks (and it missed 2 real attacks entirely):

TP (flagged AND real)        = 6
FP (flagged but benign)      = 10 - 6 = 4
FN (real but NOT flagged)    = 8 - 6  = 2

Precision = TP / (TP+FP) = 6 / (6+4) = 6/10  = 0.60   "60% of alerts were real"
Recall    = TP / (TP+FN) = 6 / (6+2) = 6/8   = 0.75   "caught 75% of real attacks"

If the threshold were loosened to flag more events, more of those missed 2 attacks would likely get caught (recall rises) — but some of the 12 benign events would likely get swept in too (precision falls). That push-pull, made concrete with real counts, is what the ROC curve below plots across every possible threshold at once.

These trade off against each other — lowering your alert threshold increases recall (catch more real attacks) but decreases precision (more false alarms), and vice versa. An ROC curve plots this tradeoff across every possible threshold; AUC (area under the curve) summarizes overall detector quality independent of any one threshold choice.

from sklearn.metrics import roc_curve, roc_auc_score

y_true = [0,0,0,0,1,0,0,1,0,1]                       # 1 = actual attack
y_scores = [0.1,0.4,0.2,0.3,0.8,0.05,0.6,0.9,0.15,0.7]  # detector's risk score
auc = roc_auc_score(y_true, y_scores)
fpr, tpr, thresholds = roc_curve(y_true, y_scores)
print("AUC:", auc)

Choosing the operating threshold on that curve is a business decision, not a math one — but the math tells you exactly what tradeoff you're accepting at each point.

Risk = Likelihood × Impact (and why this needs distributions, not point estimates)

The classic security risk formula is often applied with single-number guesses, which hides massive uncertainty. A more rigorous approach (used in quantitative risk frameworks like FAIR) treats likelihood and impact as probability distributions and runs a Monte Carlo simulation:

import numpy as np
np.random.seed(0)

# Simulate 100,000 possible "years" of risk for a given threat scenario
n_sims = 100_000
# Likelihood: how many incidents per year (Poisson-distributed, Ch. 14)
incidents_per_year = np.random.poisson(lam=0.3, size=n_sims)
# Impact per incident: log-normally distributed (heavy-tailed cost, common for breach costs)
cost_per_incident = np.random.lognormal(mean=11, sigma=1.2, size=n_sims)  # ~$60k median

annual_loss = incidents_per_year * cost_per_incident
print("Median annual loss estimate: $", np.median(annual_loss))
print("95th percentile (tail risk): $", np.percentile(annual_loss, 95))

This Monte Carlo approach (repeatedly sampling from the input distributions and tabulating outcomes) directly generalizes Ch. 14's distributions and Ch. 9's percentile thinking into a decision-support tool — instead of a single scary/reassuring number, you get a full distribution of plausible outcomes, including the tail risk that a single "expected value" calculation would hide.

k-anonymity — quantifying anonymization strength

A dataset satisfies k-anonymity if every record is indistinguishable from at least k-1 other records on the "quasi-identifying" fields (zip code, birth date, gender, etc.). This is a combinatorics/counting question (Ch. 15): how many records share each combination of quasi-identifiers?

import pandas as pd
df = pd.DataFrame({
    "zip": ["02138","02138","02139","02138","02139"],
    "age": [29, 29, 45, 29, 45],
    "disease": ["flu","cold","flu","flu","cold"],
})
group_sizes = df.groupby(["zip","age"]).size()
k = group_sizes.min()
print("k-anonymity level:", k)   # smallest group size = the anonymity guarantee

A famous real-world result of this exact math: 87% of the US population is uniquely identifiable from just {zip code, birth date, gender} alone (Sweeney, 2000) — a striking demonstration that "anonymized" data with insufficient k-anonymity often isn't anonymous at all.

The confusion matrix, every rate named

Actually malicious Actually benign
Alerted TP (true positive) FP (false positive)
Not alerted FN (false negative) TN (true negative)
Metric Formula Question it answers Also called
TPR TP / (TP + FN) of real attacks, what fraction did we catch? recall, sensitivity, detection rate
FPR FP / (FP + TN) of benign events, what fraction did we wrongly flag? fall-out, false-alarm rate
Precision TP / (TP + FP) of alerts, what fraction were real? positive predictive value
F1 2·P·R / (P + R) one number balancing precision P and recall R harmonic mean

For the 20-event example above (P = 0.6, R = 0.75), F1 = 2·0.6·0.75 / 1.35 ≈ 0.67. F1 uses the harmonic mean so that a detector can't hide a terrible precision behind a great recall. If either one goes to 0, so does F1.

Precision depends on the base rate, not just the detector

TPR and FPR are properties of the detector. Precision also depends on the environment, through the base rate π = the fraction of events that are malicious. Bayes' theorem (Ch. 16) gives:

Precision = TPR · π / ( TPR · π  +  FPR · (1 - π) )
Symbol Meaning
π base rate: P(event is malicious), before any detection
TPR · π the fraction of all events that are caught attacks
FPR · (1 - π) the fraction of all events that are false alarms

With TPR = 0.99, FPR = 0.01, π = 10⁻⁴, precision is 0.0098. That's Ch. 16's "under 1% of alerts are real", now as a formula. Solve it the other way to get the design target: for half of all alerts to be real, you need FPR ≈ TPR · π / (1 - π) ≈ 10⁻⁴. The false-positive rate has to be about as small as the base rate. That's a 100× improvement over "99% accurate", and it's why production detection stacks layer cheap high-recall filters in front of expensive high-precision ones. Each stage raises the base rate seen by the next stage.

Choosing the alert threshold from costs

If a false alarm costs C_FP (analyst time) and a missed attack costs C_FN (breach losses), alerting on an event is worth it when the expected cost of staying silent exceeds the expected cost of alerting:

alert  if   P(attack | evidence) · C_FN  >  (1 - P(attack | evidence)) · C_FP

  <=>       P(attack | evidence)  >  C_FP / (C_FP + C_FN)

Example: triaging an alert costs about $50 of analyst time, and a missed intrusion costs about $100,000. The threshold is 50 / (50 + 100,000) ≈ 0.0005. You should investigate anything with more than a 0.05% chance of being real. That shows why "only page on high-confidence alerts" can be the wrong policy when misses are expensive, and the formula lets you defend the threshold with numbers in a design review. The same derivation sets the decision threshold of any classifier, from fraud to medical screening.

What AUC actually measures

AUC has a clean probabilistic meaning (the Mann–Whitney U statistic):

AUC = P( score(random attack) > score(random benign event) )

Count it for the sklearn example above. There are 3 attacks, scored 0.8, 0.9, 0.7, and 7 benign events, the highest of which scores 0.6. All 3 × 7 = 21 attack/benign pairs are ranked correctly, so AUC = 21/21 = 1.0. Random guessing gives 0.5. AUC is threshold-free and base-rate-free, which makes it good for comparing detectors and bad for predicting alert volume. Use precision at your real π for that (above).

Password hashing: buying bits with work

A deliberately slow hash adds "free" entropy. If a hash is W times slower for the attacker, it adds log₂ W bits of effective strength:

effective bits = entropy_bits + log₂(slowdown factor)

A single high-end GPU computes fast unsalted hashes like MD5 at tens of billions per second, but only tens of thousands of bcrypt hashes per second at a moderate cost setting. That's a slowdown of roughly a million, so about log₂(10⁶) ≈ 20 extra bits. A 40-bit password behind bcrypt resists an offline attacker about as well as a 60-bit password behind MD5. Each +1 to bcrypt's cost parameter doubles the work (+1 bit). Memory-hard functions (scrypt, Argon2) also require a large block of RAM per guess, which removes the GPU's and ASIC's advantage from massive parallelism. That's why OWASP recommends Argon2id. Salts don't add bits per hash. They stop one computation from attacking every account at once, so precomputed rainbow tables become useless.

Differential privacy: a mathematical definition of "anonymous"

k-anonymity (above) can be broken by combining it with outside data. Differential privacy (Dwork et al., 2006) gives a guarantee that holds against any auxiliary information. A randomized query M is ε-differentially private if, for any two datasets D and D' that differ in one person's record, and any set of outputs S:

P[ M(D) ∈ S ]  ≤  e^ε · P[ M(D') ∈ S ]
Symbol Meaning
D, D' "neighbouring" datasets: identical except for one individual
M the randomized mechanism (the query plus noise)
ε "epsilon", the privacy budget. Smaller is more private. e^ε ≈ 1 + ε for small ε

In words: whether you are in the dataset changes the probability of any answer by at most a factor of e^ε, so nobody can learn much about you from the output.

The Laplace mechanism achieves this for numeric queries. Add noise drawn from a Laplace distribution with scale b = Δf / ε, where Δf (the sensitivity) is the most one person can change the true answer.

Example: "how many employees clicked the phishing link?" One person changes a count by at most 1, so Δf = 1. With ε = 0.5, the scale is b = 2, which gives a noise standard deviation of √2 · b ≈ 2.8. The published count is true count ± about 3, which is useful for a company of 5,000 and protective for any individual.

Privacy losses add up: answering k such queries costs k·ε in total (the composition theorem). That's why deployments track a budget. The 2020 US Census, Apple's keyboard and emoji telemetry, and Google's RAPPOR all use differential privacy, and DP-SGD (clip each example's gradient, add Gaussian noise) trains ML models with the same guarantee, so they can't memorize any single training record.

Real-life engineering ties

  • Security: this entire chapter is core Sr.-security-engineer vocabulary — precision/recall/AUC for evaluating any detection system, entropy+rate-limiting reasoning for authentication design, Monte Carlo risk quantification for prioritizing a remediation backlog defensibly instead of by gut feeling.
  • AI: the same precision/recall/ROC math applies to any binary classifier, security-related or not (fraud detection, content moderation, spam filtering).
  • SWE/Network: capacity/incident risk modeling (Monte Carlo over failure-rate distributions) generalizes directly to reliability engineering (Ch. 14's exponential/Poisson distributions feeding in here).

More real-life examples:

  • The "Have I Been Pwned" Pwned Passwords API (built by researcher Troy Hunt) lets you check whether a password appears in a breach corpus without ever sending the full password or its full hash — the client sends only the first 5 characters of a SHA-1 hash and receives back all matching suffixes, directly applying k-anonymity thinking in a widely deployed production tool.
  • The FAIR (Factor Analysis of Information Risk) model is an industry-standard quantitative risk framework that formalizes "risk = probability distribution of loss frequency x probability distribution of loss magnitude" — a standardized, auditable version of the Monte Carlo simulation in this chapter, used for real enterprise risk reporting to boards and regulators.
  • Credit scoring and insurance underwriting use the same precision/recall and cost-weighted tradeoff reasoning as fraud/intrusion detection: too permissive approves bad loans (false negatives), too strict rejects good customers (false positives).
  • Bug bounty programs and CVSS (Common Vulnerability Scoring System) scores are attempts to formalize "likelihood x impact" into a repeatable number for prioritizing a remediation backlog.

Common mistake

Optimizing a detection system purely for accuracy or purely for recall in isolation. In a Bayes-driven rare-event context (Ch. 16), a 100%-recall detector that flags everything is worthless (zero precision); the right metric is almost always a threshold chosen on a precision/recall or cost-weighted curve, not a single global metric.


Generated from real-life-maths-guide.md · 8 Oct 2026 16:36