Step 4: Hash Identifiers
Pseudonymize identifiers for cohort analytics. Hashing does not establish anonymity or prevent re-identification.
The Goal
- Customer ID → Salted hash (count unique customers without knowing who)
- Email → Retain the domain in this authored example
- IBAN → Retain the country prefix in this authored example
- IP Address → /16 subnet (regional analysis)
Why Hash vs. Delete?
Hashed identifiers enable valuable analytics:
- Count unique customers per merchant
- Track customer cohort behavior over time
- Detect patterns across pseudonymous IDs
A secret salt makes candidate matching harder; a known salt does not protect predictable identifiers.
Implementation
See the hashing processor in the canonical reference and its captured input and output in the Explorer. The fixed fallback salt is for the example; require a managed secret when adapting it.
Understanding the Code
| Expression | What It Does |
|---|---|
(value + $salt).hash("sha256") | Salted SHA-256 digest bytes |
.encode("hex") | Encode digest bytes as hexadecimal text |
.slice(0, 12) | Truncate hexadecimal text; collisions remain possible |
.split("@").index(1) | Get domain from email |
.slice(0, 2) | Get first 2 characters (country code from IBAN) |
Why Salting Matters
Without salt: Attacker with customer ID list can hash each and match.
With salt: Attacker needs both the ID list AND your secret salt.
customer_id = "CUST-DE-12345"
# Without salt (vulnerable):
hash("CUST-DE-12345") → "a1b2c3d4..." # Rainbow table attack possible
# With the demonstration salt:
hash("CUST-DE-12345" + "secret-salt-xyz") → "x9y8z7w6..." # Requires salt knowledge
Expected Output
Use the Explorer checkpoints for the sample input and resulting fields.
Analytics Value Preserved
| Original | Pseudonymous output | Analytics Possible |
|---|---|---|
| customer_id | anonymized_customer_id | Count unique customers, cohort tracking |
| customer_email | email_domain | B2B vs B2C ratio, company analysis |
| iban | bank_country | Geographic distribution by bank country |
| ip_address | ip_subnet | Regional traffic patterns |
Adaptation checks
Consistent Hashing Across Pipelines
Document your hash specification:
# HASH SPECIFICATION (document for all pipelines):
# Algorithm: SHA-256
# Salt: env("ANONYMIZATION_SALT")
# Encoding: hexadecimal before truncation
# Truncation: 12 characters
# Input format: Raw value as string
Handle IPv6
Support both IPv4 and IPv6:
root.ip_subnet = if this.ip_address.contains(":") {
# IPv6: keep first 4 groups
this.ip_address.split(":").slice(0, 4).join(":") + "::/64"
} else if this.ip_address.contains(".") {
# IPv4: keep first 2 octets
this.ip_address.split(".").slice(0, 2).join(".") + ".0.0/16"
} else {
"unknown"
}
Email Domain Categorization
Enhance domain extraction with categorization:
let domain = this.customer_email.split("@").index(1).lowercase()
let freemail = ["gmail.com", "yahoo.com", "hotmail.com", "outlook.com"]
root.email_domain = domain
root.email_type = if $freemail.contains(domain) { "consumer" } else { "business" }