Tokenization record
Last updated September 19, 2026
What is tokenized, what is hashed, with which algorithm, where the key lives, and what somebody who took the server would see. Written for a district security reviewer who would rather check a claim than believe one, so every line names the file that implements it.
Value by value
| Value | What happens to it | Algorithm | Where the key or salt lives |
|---|---|---|---|
| Student name | Stays on the device. A payload that is about to leave is walked string by string, every roster name and SIS identifier in it is removed, and the payload is then scanned again. If a name survived, nothing is sent. | Structural redaction, no hash involved | Not applicable |
| Classroom identifier or SIS identifier | Replaced by a random token in anything that travels. On the device it also gets a local fingerprint, so re-importing the same roster recognizes the same row and keeps its existing token. | SHA-256 over a 16 byte random device salt and the identifier, through Web Crypto | The salt is generated once per device and kept in the encrypted vault. It is never sent, so the fingerprint means nothing on any other machine. |
| The token itself | 32 bytes from the browser's cryptographic random source, base32 encoded to 52 characters behind a T prefix. It is not derived from the name, the identifier, a birth date, a counter, or a hash of any of them, so there is nothing in it to reverse. | crypto.getRandomValues, no hash and no cipher | Not applicable |
| The roster at rest on the device | Encrypted, always. There is no plaintext option. Identity and evidence are written under two different keys, and every envelope is bound to its store name and build so it cannot be moved into another slot. | AES-GCM-256, with the key stretched from the teacher's passphrase by PBKDF2 with SHA-256 at 310,000 iterations, then split by HKDF with SHA-256 | The passphrase is never stored. Key material exists in page memory while the vault is unlocked and is dropped after fifteen minutes without activity. A copy of the browser storage without the passphrase reads as nothing. |
| A record in transit | Carries a digest over a canonical serialization of the record, so the holder can tell whether a byte changed on the way. Field order cannot change the digest, and a missing field cannot be hidden by it, because the serialization refuses to encode a value JSON would silently drop. | SHA-256 through Web Crypto | None. Anybody holding the record can recompute this digest, which is what makes it verifiable and also what limits it. |
| A record whose digest must not be recomputable by whoever holds it | Carries a keyed digest instead. Somebody who holds the record but not the key cannot alter it and produce a matching seal. | HMAC with SHA-256 through Web Crypto | The district's. It is never written into a payload and never given to the server that holds the records. Putting it there would turn the keyed digest back into the unkeyed one. |
Every algorithm above comes from the browser's own Web Crypto implementation. Nothing here is a hash written by hand, and nothing reversible is presented as a hash.
What somebody holding the server sees
- A 52 character random token for each student, stable through the school year.
- Grade level and pronoun.
- Fluency measurements with their dates, and skill scores on the district's scale.
- The generated comment, with the student's own name taken out.
- The reporting term, the engine version, and a timestamp for each save.
- The teacher's email address and a scrambled version of their password.
What is not there to take
- Any student name.
- Any SIS identifier.
- The mapping from a token back to a person.
- The device fingerprint salt, or any fingerprint computed with it.
- The vault passphrase, or anything derived from it.
What they could still work out
The part a reviewer should weigh hardest, because it is the part a vendor is usually quiet about.
- A token is a pseudonym and not anonymity. Somebody holding the mapping can turn every record back into a child, and the mapping sits on the teacher's device by design.
- A class is small. Grade, pronoun, a reading rate and a term can single out one child in a room of twenty five, and somebody who knows the class may not need the mapping at all.
- A comment describes a child. Names are removed, but a sentence about the only student who reads aloud to the kindergarten class still points at one person.
- Tokens stay the same all year so trimesters line up. That is the feature, and it also means one token's records are linkable to each other across the year.
- An unkeyed digest confirms a guess. Somebody holding the server can check whether a record matches one they already suspect, so that digest proves a record was not altered and hides nothing from a good guesser. The keyed digest is the answer where that matters.
- None of this protects an unlocked device, a screenshot, a printed packet, or a passphrase written on a sticky note. Those stay the realistic way a record gets out.
Check it yourself
- lib/privacy/alias.ts mints the token. Read newToken and confirm it takes 32 bytes from crypto.getRandomValues and nothing from the student.
- lib/privacy/tokenization.ts is the egress boundary. Read sealForSync and the post condition at the end that refuses the whole push when a name survives.
- lib/privacy/integrity.ts holds the digests, the keyed digest, the salted fingerprint, and the mapping leak scan. The comments say why a salt belongs in one place and not the other.
- lib/crypto.ts and lib/privacy/vault.ts hold the on device encryption and the key derivation.
- tests/unit/integrity.test.ts draws a large sample of tokens for one identifier, changes one byte of a record and watches the digest move, and tries to forge a keyed digest without the key.
- tests/unit/tokenization-seal.test.ts tries to smuggle a name out through a field nobody scanned.
- tests/unit/privacy-modes.test.ts checks that the mapping stays on the device in all three modes.
These tests describe what the code does today, on September 19, 2026. They are not an outside audit, and no outside firm has tested this product.
Found something wrong here? Write to support@papalotledu.com. Reports are acknowledged within 72 hours and never met with legal threats for good faith research.