The defence
Why it matters The last three lessons showed that names come back through fingerprints, quasi-identifiers and hashes. Here are the tools that actually help when you build something that stores or shares data about people. Each one is simple, and each has a cost you should know.
Four tools, one idea: share less detail
Think of a school publishing exam results. Listing every pupil's mark, even without names, lets classmates work out who is who. Saying "the class average was 64, and 3 pupils got top marks" tells parents what they need with far less risk. Every tool here is a version of that.
1. Keyed hashing (a secret salt)
Salt: extra text mixed into the input before hashing, so the same email gives a different hash. When the salt is a secret held by one organisation, it is often called a key or pepper.
HMAC: a standard way to hash with a secret key. Python has it built in: hmac.new(key, message, hashlib.sha256).hexdigest().
Last lesson's lookup table only works because anyone can compute sha256(email). With a secret key, an outsider cannot compute the hashes of candidate emails, so the table is useless. The cost: two companies can no longer match customers unless they share the key, which is often exactly the point. And it protects only against outsiders: whoever holds the key can still brute-force it.
2. Generalise, then suppress: reaching k
Generalisation: replacing exact values with coarser ones: zip 40113 becomes 4011*, birth date 1987-03-03 becomes 1980s.
Coarser values make bigger groups. But some groups stay tiny (in our data, people with gender X are rare). So the second step is suppression: leave out rows whose group is smaller than k. After both steps, every remaining combination is shared by at least k people. Choosing k (5 and 10 are common) is a judgement about risk.
Honest limits: if all k people in a group have the same diagnosis, the group still reveals it. And suppression removes the rarest people, whose data may matter most. k-anonymity lowers risk; it does not remove it.
3. Aggregation
Often you do not need rows at all. Publish counts: "212 people with asthma". Combine it with suppression ("do not publish any count below 10") and many attacks disappear, because there is no row to join with a public list.
4. Differential privacy, made gentle
Counts can still leak. If yesterday's count was 212 and today's is 213, and you know one person joined, you learn their diagnosis. Differential privacy adds a little random noise to every published number, so no single person changes the result in a way anyone can see.
Differential privacy: a promise that the published results would look almost the same whether or not any one person's data was included. It is achieved by adding carefully sized random noise.
Epsilon: the privacy setting. Smaller epsilon means more noise and more privacy; larger means more accuracy and less privacy.
The classic noise is the Laplace distribution with scale 1 / epsilon. A neat trick makes it from two ordinary random draws: rng.expovariate(epsilon) - rng.expovariate(epsilon). With epsilon 1, the published count is off by about 1 on average; with epsilon 0.25, by about 4. Averages over big groups stay useful, because the noise is small compared to them and cancels out.
The tests pass in a seeded random.Random object as rng. Using only that object makes your noise repeatable, so grading is fair. Leg 7 goes deeper: privacy budgets, and what happens when you answer many questions.
A common misconception
"Noise ruins the data." For one tiny group it does, which is the point: that is where individuals could be exposed. For the large totals people actually use, a few units of noise barely matter.
Try it When you pass, call noisy_count(212, epsilon, rng) ten times each for epsilon 2, 0.5 and 0.1. Write down the spread of answers. Which epsilon would you accept for a report on a town of 2,000 people?
In other languages
JavaScript (Node):
const { createHmac } = require("crypto");
const keyedHash = (email, key) =>
createHmac("sha256", key).update(email.trim().toLowerCase()).digest("hex");
// Laplace noise from two exponential draws (use a seeded generator for tests)
const expo = (rate, rand) => -Math.log(1 - rand()) / rate;
const noisyCount = (n, eps, rand) => n + expo(eps, rand) - expo(eps, rand);Go:
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(strings.ToLower(strings.TrimSpace(email))))
hash := hex.EncodeToString(mac.Sum(nil))
noisy := float64(count) + r.ExpFloat64()/eps - r.ExpFloat64()/epsRecap
- A secret key (HMAC) stops outsiders from brute-forcing hashed emails.
- Generalise, then suppress small groups, to reach k-anonymity; it lowers risk, not to zero.
- Publish counts instead of rows, and hide small counts.
- Differential privacy adds noise of scale 1/epsilon: tiny groups blur, big averages stay useful.
Write keyed_hash(email, key) (HMAC-SHA256 hex of the normalised email, key as UTF-8), generalise(row) (a new dict with zip = first 4 digits plus *, birth_decade such as "1980s", and gender and diagnosis unchanged), suppress_small(rows, columns, k) (keep, in order, only rows whose group of equal columns values has at least k rows) and noisy_count(true_count, epsilon, rng) (add Laplace noise of scale 1/epsilon drawn only from rng).
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.