Documentation
¶
Overview ¶
Package deidentify removes personally identifiable information (PII) from text and structured data while preserving the shape and utility of the original data.
Replacements are deterministic: the same input, deidentified with the same secret key, always yields the same output. This preserves referential integrity across records and runs, so joins and groupings still work on anonymized data. Different secret keys produce different outputs, so the mapping cannot be reproduced without the key.
Getting started ¶
Create a Deidentifier with a secret key and call Deidentifier.Text to redact free-form text:
secretKey, err := deidentify.GenerateSecretKey()
if err != nil {
log.Fatal(err)
}
d := deidentify.NewDeidentifier(secretKey)
redacted, err := d.Text("Contact Frodo Baggins at frodo@shire.me or (555) 123-4567.")
if err != nil {
log.Fatal(err)
}
fmt.Println(redacted)
Supported PII types ¶
The package detects and replaces names, email addresses, phone numbers, Social Security numbers, credit card numbers, and street addresses. Each has a corresponding DataType constant and a convenience method on Deidentifier for deidentifying a single value of a known type.
Replacements preserve the format of the original value. Phone numbers keep their punctuation and area code, credit card numbers keep a valid Luhn checksum, and SSNs keep a structurally valid area-group-serial layout.
Structured data ¶
Deidentifier.Table processes column-oriented data where the type of each column is known, and Deidentifier.Slices processes CSV-like [][]string data, inferring the type of each column when it is not supplied. Both use the column name as part of the replacement context, so the same value appearing in two different columns maps to two different replacements. This prevents correlation across columns.
Values of type TypeGeneric are returned unchanged, which is how columns with no detected PII pass through the pipeline intact.
Concurrency ¶
A Deidentifier is safe for concurrent use by multiple goroutines. Its internal mapping tables are guarded by a mutex.
Limitations ¶
Detection is pattern-based. No automated system can guarantee that it finds every piece of PII, particularly in unstructured text, so results should be verified in sensitive applications. By default, phone area codes are preserved because they usually indicate a geographic region rather than an individual; consider whether that tradeoff suits your threat model.
Index ¶
- func GenerateSecretKey() (string, error)
- type Column
- type DataType
- type Deidentifier
- func (d *Deidentifier) Address(address string) (string, error)
- func (d *Deidentifier) ClearMappings()
- func (d *Deidentifier) CreditCard(cc string) (string, error)
- func (d *Deidentifier) Email(email string) (string, error)
- func (d *Deidentifier) Name(name string) (string, error)
- func (d *Deidentifier) Phone(phone string) (string, error)
- func (d *Deidentifier) SSN(ssn string) (string, error)
- func (d *Deidentifier) Slices(data [][]string, optional ...interface{}) ([][]string, error)
- func (d *Deidentifier) Table(table *Table) (*Table, error)
- func (d *Deidentifier) Text(text string) (string, error)
- type Table
Examples ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func GenerateSecretKey ¶
GenerateSecretKey returns a cryptographically secure random 256-bit key, hex-encoded, suitable for passing to NewDeidentifier.
Store the key if you need replacements to stay consistent across runs: deidentifying the same data with a different key produces different output.
Example ¶
package main
import (
"fmt"
"log"
"github.com/aliengiraffe/deidentify"
)
func main() {
secretKey, err := deidentify.GenerateSecretKey()
if err != nil {
log.Fatal(err)
}
// The key is a hex-encoded 256-bit value.
fmt.Println(len(secretKey))
}
Output: 64
Types ¶
type Column ¶
type Column struct {
// Name identifies the column and scopes its replacement mappings.
Name string
// DataType is the kind of PII stored in this column.
DataType DataType
// Values holds the column's cells. A nil element is passed through as nil.
Values []interface{}
}
Column represents a single column in a Table: its name, the type of PII it holds, and its values. The name is used as replacement context, so identical values in differently named columns receive different replacements.
type DataType ¶
type DataType int
DataType represents the type of personally identifiable information held by a value, and determines which replacement strategy is applied to it.
const ( // TypeName is a personal name, replaced with a generated first and last name. TypeName DataType = iota // TypeEmail is an email address, replaced with a generated address at a fictional domain. TypeEmail // TypePhone is a phone number, replaced while preserving punctuation and area code. TypePhone // TypeSSN is a US Social Security number, replaced with a structurally valid number. TypeSSN // TypeCreditCard is a credit card number, replaced with a number that passes the Luhn check. TypeCreditCard // TypeAddress is a street address, replaced with a generated street number and name. TypeAddress // TypeGeneric marks a value with no detected PII. Such values are returned unchanged. TypeGeneric )
The PII types recognized by this package. Each type has a replacement strategy that preserves the format of the original value.
type Deidentifier ¶
type Deidentifier struct {
// contains filtered or unexported fields
}
Deidentifier replaces PII with deterministic, format-preserving substitutes. Create one with NewDeidentifier. A Deidentifier is safe for concurrent use by multiple goroutines.
func NewDeidentifier ¶
func NewDeidentifier(secretKey string) *Deidentifier
NewDeidentifier returns a Deidentifier that derives its replacements from secretKey. Use GenerateSecretKey to create one, and reuse the same key whenever deidentified values must match across runs or datasets.
Example ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/aliengiraffe/deidentify"
)
func main() {
// In production, generate a key once with GenerateSecretKey and store it.
// Reusing the same key keeps replacements consistent across runs.
d := deidentify.NewDeidentifier("example-secret-key")
redacted, err := d.Text("Please email Frodo Baggins at frodo.baggins@shire.me or call (555) 123-4567.")
if err != nil {
log.Fatal(err)
}
// redacted reads, for example:
// "Please email Marlo Cole at undefined6840@private.com or call (555) 777-3471."
fmt.Println(strings.Contains(redacted, "Frodo Baggins"))
fmt.Println(strings.Contains(redacted, "frodo.baggins@shire.me"))
fmt.Println(strings.Contains(redacted, "(555) 123-4567"))
}
Output: false false false
func (*Deidentifier) Address ¶ added in v0.9.2
func (d *Deidentifier) Address(address string) (string, error)
Address deidentifies a single street address, replacing it with a generated street number and name. It recognizes international address formats and preserves a leading label, so "Europe HQ: 10 Rue de Rivoli, Paris, France" keeps its "Europe HQ:" prefix.
func (*Deidentifier) ClearMappings ¶
func (d *Deidentifier) ClearMappings()
ClearMappings discards every stored value-to-replacement mapping. Because replacements are derived from the secret key, clearing does not change what a given input maps to; it only frees the accumulated mapping tables.
func (*Deidentifier) CreditCard ¶ added in v0.9.2
func (d *Deidentifier) CreditCard(cc string) (string, error)
CreditCard deidentifies a single credit card number. The replacement is a 16-digit test number that passes the Luhn checksum.
func (*Deidentifier) Email ¶ added in v0.9.2
func (d *Deidentifier) Email(email string) (string, error)
Email deidentifies a single email address, replacing it with a generated address at a fictional domain.
Example ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/aliengiraffe/deidentify"
)
func main() {
d := deidentify.NewDeidentifier("example-secret-key")
first, err := d.Email("bilbo@bag-end.shire")
if err != nil {
log.Fatal(err)
}
second, err := d.Email("bilbo@bag-end.shire")
if err != nil {
log.Fatal(err)
}
other, err := d.Email("frodo@shire.me")
if err != nil {
log.Fatal(err)
}
// The same address always maps to the same replacement, so records can
// still be joined on the anonymized value.
fmt.Println(first == second)
fmt.Println(first == other)
fmt.Println(strings.Contains(first, "@"))
}
Output: true false true
func (*Deidentifier) Name ¶ added in v0.9.2
func (d *Deidentifier) Name(name string) (string, error)
Name deidentifies a single personal name, replacing it with a generated first and last name.
func (*Deidentifier) Phone ¶ added in v0.9.2
func (d *Deidentifier) Phone(phone string) (string, error)
Phone deidentifies a single phone number, preserving its punctuation and area code. Input that cannot be parsed as a phone number is replaced with an opaque generic value.
Example ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/aliengiraffe/deidentify"
)
func main() {
d := deidentify.NewDeidentifier("example-secret-key")
redacted, err := d.Phone("(555) 123-4567")
if err != nil {
log.Fatal(err)
}
// Punctuation and area code are preserved; only the subscriber digits change.
fmt.Println(strings.HasPrefix(redacted, "(555) "))
fmt.Println(len(redacted) == len("(555) 123-4567"))
fmt.Println(redacted == "(555) 123-4567")
}
Output: true true false
func (*Deidentifier) SSN ¶ added in v0.9.2
func (d *Deidentifier) SSN(ssn string) (string, error)
SSN deidentifies a single US Social Security number, replacing it with a structurally valid number in AAA-GG-SSSS form.
func (*Deidentifier) Slices ¶ added in v0.9.2
func (d *Deidentifier) Slices(data [][]string, optional ...interface{}) ([][]string, error)
Slices deidentifies CSV-like data, where each inner slice is a row. It returns a new [][]string of the same shape; empty cells are left empty.
Two optional arguments may follow data, in order:
- columnTypes ([]DataType): the type of each column. When omitted, the type of every column is inferred by sampling its values.
- columnNames ([]string): the name of each column, used as replacement context. When omitted, names of the form "column_0" are generated.
Both must have one entry per column, or Slices returns an error. Passing an argument of any other type is also an error.
d.Slices(data) d.Slices(data, columnTypes) d.Slices(data, columnTypes, columnNames)
Example ¶
package main
import (
"fmt"
"log"
"strings"
"github.com/aliengiraffe/deidentify"
)
func main() {
d := deidentify.NewDeidentifier("example-secret-key")
data := [][]string{
{"Gandalf Grey", "mithrandir@wizard.com", "555-123-4567"},
{"Aragorn Strider", "ranger@gondor.me", "(555) 987-6543"},
}
// With no column types supplied, each column's type is inferred.
result, err := d.Slices(data)
if err != nil {
log.Fatal(err)
}
fmt.Println(len(result), len(result[0]))
fmt.Println(result[0][1] == "mithrandir@wizard.com")
fmt.Println(strings.Contains(result[0][1], "@"))
}
Output: 2 3 false true
Example (ExplicitTypes) ¶
package main
import (
"fmt"
"log"
"github.com/aliengiraffe/deidentify"
)
func main() {
d := deidentify.NewDeidentifier("example-secret-key")
data := [][]string{
{"Gandalf Grey", "mithrandir@wizard.com"},
{"Aragorn Strider", "ranger@gondor.me"},
}
// Supplying types skips inference; supplying names controls the context
// used for replacement, so the same value in two columns differs.
columnTypes := []deidentify.DataType{deidentify.TypeName, deidentify.TypeEmail}
columnNames := []string{"customer_name", "customer_email"}
result, err := d.Slices(data, columnTypes, columnNames)
if err != nil {
log.Fatal(err)
}
fmt.Println(result[0][0] == "Gandalf Grey")
fmt.Println(len(result))
}
Output: false 2
func (*Deidentifier) Table ¶ added in v0.9.2
func (d *Deidentifier) Table(table *Table) (*Table, error)
Table deidentifies every column of a table according to its declared DataType, returning a new Table and leaving the input untouched. Nil values are preserved as nil.
Example ¶
package main
import (
"fmt"
"log"
"github.com/aliengiraffe/deidentify"
)
func main() {
d := deidentify.NewDeidentifier("example-secret-key")
table := &deidentify.Table{
Columns: []deidentify.Column{
{
Name: "customer_name",
DataType: deidentify.TypeName,
Values: []interface{}{"Gandalf Grey", nil},
},
{
Name: "email",
DataType: deidentify.TypeEmail,
Values: []interface{}{"mithrandir@wizard.com", ""},
},
},
}
result, err := d.Table(table)
if err != nil {
log.Fatal(err)
}
// Nil and empty values pass through untouched.
fmt.Println(result.Columns[0].Values[0] == "Gandalf Grey")
fmt.Println(result.Columns[0].Values[1] == nil)
fmt.Println(result.Columns[1].Values[1] == "")
}
Output: false true true
func (*Deidentifier) Text ¶ added in v0.9.2
func (d *Deidentifier) Text(text string) (string, error)
Text finds and replaces PII in free-form text, returning the redacted text. It detects email addresses, phone numbers, Social Security numbers, credit card numbers, names, and street addresses. Detection is pattern-based and is not guaranteed to be exhaustive.
Directories
¶
| Path | Synopsis |
|---|---|
|
examples
|
|
|
basic
command
|
|
|
international
command
|
|
|
slices
command
|
|
|
table
command
|
