Consent Docs

Identifier hashing

How identifiers are stored as keyed hashes, what the pepper and the subject fingerprint are for, and where a readable identifier can still appear.

Concept4 min readFor architects, security reviewers and auditors

The product is built to hold identifiers only as hashes. A consent record is kept under a keyed hash of the identifier you choose, not the value. A copy of the database alone cannot turn those hashes back into identities.

How an identifier is stored#

subject hash = hex( HMAC-SHA256( pepper, identifier ) )
  • The pepper is a secret of the deployment, held outside the database. Without it, the stored hashes cannot be matched against a list of known identifiers.
  • Spaces at either end of the identifier are removed first.
  • An email address is hashed in lower case. Every other type keeps its exact case, so aB3x and Ab3X are two people.
  • Phone numbers are not changed. Send them in one form, such as E.164.

The same hash is used for identifiers linked to a record later, for the reference used to verify a guardian, and for the identifier a rights request is filed under.

What else is hashed#

Value How it is stored
IP address A SHA-256 hash salted with the pepper. The address itself is not stored with the record
Secret key, sk_… A SHA-512 hash salted with a deployment secret
Email confirmation token Hashed
Browser string and page address In clear, cut to 500 characters
Cookie names, storage key names and third-party hosts In clear. The runtime never reports values

The subject fingerprint#

The hash above is keyed with a secret you do not hold, so you cannot recompute it. The subject fingerprint is a second HMAC-SHA256 of the same identifier, under a key that belongs to your project.

fingerprint = hex( HMAC-SHA256( fingerprint key, identifier ) )

You read the key in Project settings, and each reveal is written to the audit log. The key is created on first use and never changes, so old receipts always match. Webhooks, receipts and the find call carry the fingerprint. You recompute it from your own identifier to show whom a receipt or webhook is about.

A fingerprint is made from the identifier a record was recorded under, not from identifiers linked to it. It is removed when the record is erased.

An example#

Kavya signs up as kavya.menon@example.in, and your server records her consent with the type EMAIL. The service lowers the case, computes the keyed hash and stores only that.

A year later she asks for access and gives Kavya.Menon@example.in. The lookup lowers the case and hashes it in the same way, so it finds the record. Had the consent been recorded under a user id, the request would find it only if her email address had been linked to the record.

Where a readable identifier can appear#

There are four places where a readable identifier can exist:

  1. The encrypted copy, when Store a reversible subject identifier is on. Webhooks then include the identifier as subject.reference, and the delivery log holds it for 30 days.
  2. A rights request filed on the hosted form. The identifier the person typed and the reply address go to you in the subject_request.received webhook. They sit in the delivery log until it is cleared, after 30 days.
  3. Whatever your integration puts in metadata.
  4. The reference you give each person in a notice campaign, encrypted, when you choose to send one. It appears in the links your sender reads and in campaign.withdrawn.

You control all four. A person search in Records sends the identifier in the request body, which is not logged, and the audit log keeps only the first 12 characters of its hash.

A notice campaign stores each person's identifiers as keyed hashes only, so a campaign can find them later without holding the identifiers. An erased person's hashes are kept, so an import or a campaign does not add them back.

Limits#

  • The keyed hash stays after erasure. Every seal in the chain covers it. It cannot be reversed without the pepper.
  • The pepper is never rotated. Every stored hash depends on it. With a new value, no record can be found by identifier.
  • A keyed hash is pseudonymous, not anonymous. Whoever holds the pepper and a list of identifiers can test each one.
  • Type matters. An email address recorded without the type EMAIL keeps its capital letters, and a later lookup by EMAIL does not find it.
  • Older records. Records written before identifiers kept their case hold a lower-case hash. It is sealed, so it is never rewritten, and lookups match both forms.
  • Moving a property. Identifier hashes are copied as they are. A returning visitor matches an imported record only if both deployments use the same pepper.

↑↓ to move, Enter to open