A pluggable Go library to protect Personal Identifiable Information at the struct field level.
TLDR; PII simplifies encryption and cryptographic erasure.
PII may considerably help if you are:
- Following Privacy By Design principles.
- Looking for a solution to comply with privacy standards (ex: GDPR) while using Event Sourcing or an immutable store.
The library is experimental; breaking changes may occur based on developer experience.
v1.0.0
aims to be the first stable version.
$ go get github.com/ln80/pii
import "github.com/ln80/pii"
type Person struct {
UserID string `pii:"subjectID,prefix=account-"`
Fullname string `pii:"data,replace=forgotten user"`
Gender string `pii:"data"`
Country string
}
- Tag the field representing the
Subject ID
(ex: UserID) - Tag
Personal data
fields to encrypt (only string fields are supported at the moment)
prefix
option is added to the field value to define the subject ID.
replace
option is used to replace the crypto-erased field value. Otherwise, the field value will be empty.
Initiate the Factory
service:
func main() {
ctx := context.Background()
// newProt func used by factory service to instantiate protector service per namespace
newProt := func(namespace string) pii.Protector {
// engine handles encryption keys storage and lifecycle
engine := memory.NewKeyEngine()
return pii.NewProtector(namespace, enigne, func(pc *pii.ProtectorConfig) {
pc.CacheEnabled = true
pc.CacheTTL = 10 * time.Minute
pc.GracefulMode = true
})
}
// Factory must be injected as dependency in the functional code (ex: HTTP handlers)
f := pii.NewFactory(newProt)
// In a separated Goroutine, supervise and regularly clear resources
f.Monitor(ctx)
}
- Instantiate
Protector
service by passing the namespace (ex: tenant ID) - Use the
Protector
toEncrypt/Decrypt
structs that containPersonal data
:
func MakeSignupHandler(f pii.Factory, store UserStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
var per Person
err := json.NewDecoder(r.Body).Decode(&per)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Get the protector service for the given namespace
nspace := r.Header.Get("Tenant-ID")
prot, clear := f.Instance(nspace)
// Optional, Force clearing cache of encryption materials (related to namespace)
defer clear()
// Encrypt Person struct which contains PII.
if err := prot.Encrypt(ctx, &per); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
...
if err := store.Save(ctx, per); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
...
}
}
Note that Factory service maintains a single thread-safe Protector
service per namespace.
Under the hood, the Protector
service generates a single encryption key per Subject ID
and securely saves it in a Database
.
Allows to Forget
a subject's Personal data
by first disabling, then deleting the associated encryption materials.
...
if err := prot.Forget(ctx, subjectID); err != nil {
return err
}
...
if err := prot.Recover(ctx, subjectID); err != nil {
if errors.Is(err, pii.ErrCannotRecoverSubject) {
fmt.Print("Sorry it's late. Good bye forever")
}
return err
}
Forgetting a subject means we can't decrypt nor encrypt any of its old or new Personal data
.
Forgetting the encryption key and not being able to decrypt personal is likely accepted by most Privacy Standards
as deletion of PII.
Nonetheless, you may need to seek legal advice related to your specific context.
Depending on Graceful Mode
config, a subject encryption materials can be recovered within a grace period (to define) or not.
Protector
service uses plugins to manage encryption keys and the encryption algorithm.
You can use your own implementation for each pluggin:
b := func(namespace string) pii.Protector {
return pii.NewProtector(namespace, nil, func(pc *pii.ProtectorConfig) {
pc.Encrypter = MyCustomAlgorithm()
pc.Engine = MyCustomWrapper(
MyCustomKeyEngine(),
)
})
}
f := NewFactory(b)
By default, PII uses AES 256 GCM
for encryption.
By implementing core.Encrypter
interface, you take responsibility, and you use your favorite algorithm (likely to respond to security standards requirements).
Responsible for storing encryption keys and managing their life cycle.
PII comes with two basic implementations:
-
Dynamodb: keys are saved in plain text in an AWS Dynamodb table. (server-side encryption can be applied).
-
In-memory: used for test purposes.
and two wrappers:
KMS Wrapper: uses AWS KMS service to allow Envelope Encryption; client-side encryption of subjects' keys using a KMS Key as a Master Key
.
Memory Cache: saves keys in memory for a limited period to enhance performance and reduce costs.
Use your custom logic by implementing core.KeyEngine
, core.KeyEngineWrapper
or core.KeyEngineCache
.
// TODO
Please see https://pkg.go.dev/github.com/ln80/pii for detailed usage docs.
Distributed under MIT License.