Articles / Privacy Law

  • uk-law
  • uk-gdpr
  • pseudonymisation
  • analytics
  • data-protection
  • ico

Hashing User IDs Doesn't Make Your Analytics Anonymous

A common move in analytics pipelines is to run the user ID through SHA-256, ship the result to the warehouse, and declare the data "anonymised". It is not. Under UK GDPR that data is pseudonymised, which the law treats as personal data, and the hash is doing much less than people hope.

What the law actually says

The UK GDPR defines pseudonymisation in Article 4(5): processing personal data so it can no longer be attributed to a specific person without additional information, provided that information is kept separately and protected.

Read that again. The definition assumes the additional information exists. Pseudonymised data is, by definition, data that can be linked back to someone if you hold the key, the lookup table or the original database.

Recital 26 makes the consequence explicit: pseudonymised data which could be attributed to a person by the use of additional information should be considered information on an identifiable person. Only data that is properly anonymous, where the person is not identifiable by any means reasonably likely to be used, falls outside the regime.

Why a plain hash fails even as pseudonymisation

A hash of an email address is not a secret. Anyone who has a list of candidate emails can hash each one and compare. Email addresses, phone numbers and sequential user IDs are low-entropy inputs, so this is cheap.

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

func main() {
	// What your warehouse holds.
	stored := "..." // hex SHA-256 of an email

	// What anyone with a mailing list can do.
	candidates := []string{"[email protected]", "[email protected]"}
	for _, c := range candidates {
		sum := sha256.Sum256([]byte(c))
		if hex.EncodeToString(sum[:]) == stored {
			fmt.Println("re-identified:", c)
		}
	}
}

Nobody needs to break SHA-256. They only need a guess list, and the same hash appears in every system that used the same trick, so joining datasets across companies is trivial.

Detour: why a keyed hash is better but still personal data

Quick aside, because this is the interesting bit. Swapping to HMAC with a secret key fixes the guessing attack, since nobody without the key can test candidates.

func pseudonym(key []byte, userID string) string {
	m := hmac.New(sha256.New, key)
	m.Write([]byte(userID))
	return hex.EncodeToString(m.Sum(nil))
}

That is genuinely good pseudonymisation, and it is what Article 4(5) is describing. But you hold the key. You can recompute the pseudonym for any user, so you can find their rows. For you, it remains personal data, and you still owe a lawful basis, transparency, retention limits and the ability to answer a subject access request.

Singling out is enough

Identification does not require a name. The ICO's anonymisation guidance discusses singling out: if you can pick one person's records out of a crowd and follow them over time, they are identifiable in practice.

A stable pseudonym is exactly that. It lets you build a profile across visits, devices and sessions. Add a timestamp, a rough location, a user agent and a page history, and the combination is often unique even if you threw the hash away.

  • A stable per-user token tracks behaviour over time.
  • Quasi-identifiers (postcode district, device model, rare language setting) narrow the crowd.
  • Event streams with exact timestamps can be matched against server logs you also hold.

The wrinkle: identifiability depends on who holds the data

There is a real argument that identifiability is relative to the recipient. The EU's Court of Justice looked at this in the EDPS v SRB case in 2025, suggesting that pseudonymised data passed to a party with no realistic means of re-identification may not be personal data in that party's hands. That is an EU judgment, and how far UK courts and the ICO follow it is a question I would treat as open.

Even on the most generous reading, it does not help the party holding the key. It also does not make the original controller's disclosure free of duties: the controller still has to tell people the data is shared.

What pseudonymisation does buy you

It is not worthless; the law rewards it. Pseudonymisation is named in Article 32 as a security measure, and it counts in your favour when assessing risk, compatibility of purposes and data protection by design.

  • It reduces the damage if the analytics store leaks.
  • It limits which staff can see real identities.
  • It supports a legitimate interests balancing test.

It does not remove the data from scope, and it does not let you retain it forever.

If you actually want out of scope

Real anonymisation means you cannot, and nobody else reasonably can, link the record back. Practically that means dropping the stable identifier, rotating any session token daily and discarding the salt, aggregating before storage, and coarsening timestamps and locations.

Be honest about the trade-off: you lose per-user funnels and retention cohorts. If you need those, keep treating the data as personal data and design the paperwork accordingly. Whether a particular dataset clears the bar depends on the data and who can access what, so test it by trying to re-identify your own records.