A payments gateway of the kind Razorpay operates tokenises customer mobile numbers before they reach the analytical estate, using deterministic tokens so analysts can still join orders to support tickets. Nine months later a red team reconstructs the mapping for a large share of customers without ever touching the token vault. What did the design assume, and which decision made the attack possible?
Show the full answer Hide the answer
The trigger
Determinism plus a small, enumerable input domain. An Indian mobile number is ten digits with a constrained leading digit, so the candidate space is on the order of a few billion and the realistically reachable space far smaller. The red team did not attack the vault. They found a path that would tokenise an input they chose: a signup or lookup flow that echoed a token, or an internal tokenise endpoint reachable without strong authentication. With that, building a dictionary is a loop.
Why it propagated
The token was stable across the whole estate and across time, which is precisely the property that made joins work. One dictionary therefore resolves every table, every export and every historical snapshot, permanently. The blast radius of the compromise is the entire retained history, not the window in which the attack ran.
Why detection lagged
The vault's audit log is clean, because the vault was never called. Access reviews on the analytical tables are clean, because the attacker held no analytical access. The only signal that existed was volume and caller distribution on the tokenise path, and nobody was watching it, because tokenisation was classified as a data-preparation utility rather than as a privileged cryptographic operation.
The structural fix versus the tempting local fix
The tempting fix is to rotate the token key. It breaks every historical join in the warehouse and every downstream model keyed on the token, and it does not prevent the same attack against the new key. Do it only if you must invalidate a known-compromised dictionary.
The structural fixes, in order of value:
- Treat tokenise as a privileged operation. Authenticate it, rate-limit it per caller, and alert on a caller whose tokenise-to-detokenise ratio or absolute volume changes. A chosen-input oracle is the precondition for the whole attack.
- Scope determinism to where the join is actually needed. A keyed tokenisation with a per-dataset key gives joins inside a dataset and no cross-dataset dictionary. Most analytical joins turn out to be within one domain.
- For the analytical estate, stop tokenising the identifier at all. Assign a surrogate customer id at first sight, with no functional relationship to the phone number, and keep the mapping in the operational system. Analysts join on the surrogate and there is nothing to enumerate.
The general lesson
A deterministic token over an enumerable domain is a pseudonym, not anonymisation. The GDPR says as much directly: Article 4(5) defines pseudonymisation as processing after which data can still be attributed to a person using separately held information, and Recital 26 keeps such data within scope as personal data. A salted hash of a phone number is the same mistake with extra steps, because the salt is not secret from anyone who can compute hashes.
When this is the wrong answer
If the input domain is genuinely high-entropy and not chooseable by an attacker, a long random account reference for instance, deterministic tokenisation is fine and the surrogate-id rewrite is wasted effort. The decision rule is about the input, not about the algorithm: enumerable domain means the token leaks; unguessable domain means it does not.