GDPR Erasure
Crypto-erasure — destroying per-subject encryption keys to make data irrecoverable.
Chronicle implements GDPR Article 17 (right to erasure) through crypto-erasure: each data subject's sensitive fields are encrypted with a per-subject AES-256-GCM key. On erasure request, the key is destroyed — making the encrypted payload irrecoverable — while the hash chain remains structurally valid.
Enable crypto-erasure
c, err := chronicle.New(
chronicle.WithStore(adapter),
chronicle.WithCryptoErasure(true),
)Or via the extension:
ext := extension.New(
extension.WithStore(s),
extension.WithCryptoErasure(true),
)Record events with a subject ID
When recording events, set SubjectID to associate the event with a data subject:
c.Info(ctx, "profile_update", "user", "user-42").
Category("account").
SubjectID("user-42").
Meta("email", "alice@example.com").
Record()Perform erasure
Via the erasure service
import "github.com/xraph/chronicle/erasure"
svc := erasure.NewService(store, keyStore)
result, err := svc.Erase(ctx, &erasure.Input{
SubjectID: "user-42",
Reason: "GDPR Article 17",
RequestedBy: "dpo@company.com",
}, appID, tenantID)
fmt.Printf("keyDestroyed=%v eventsAffected=%d\n",
result.KeyDestroyed, result.EventsAffected)Via the HTTP API
POST /v1/erasuresRequest body:
{
"subject_id": "user-42",
"reason": "GDPR Article 17",
"requested_by": "dpo@company.com"
}Response: erasure.Result JSON.
What erasure does
- Looks up all events with
SubjectID = "user-42"in the given app+tenant scope. - For each event, sets
Erased = true,ErasedAt, andErasureID. - Destroys the per-subject encryption key from the
KeyStore. - Saves an
erasure.Erasurerecord to the store. - Returns an
erasure.ResultwithKeyDestroyed = trueandEventsAffected.
After erasure, attempting ks.Get("user-42") returns chronicle.ErrErasureKeyNotFound. The ciphertext in the store cannot be decrypted.
Hash chain integrity after erasure
Hashes are computed over event metadata — not the encrypted payload. Destroying the key does not change any hash value, so VerifyChain still returns Valid = true for streams that contain erased events.
erasure types
erasure.Input
| Field | Type | Description |
|---|---|---|
SubjectID | string | The data subject to erase |
Reason | string | Erasure justification (e.g. "GDPR Article 17") |
RequestedBy | string | Identity of the requestor |
erasure.Result
| Field | Type | Description |
|---|---|---|
ID | id.ID | TypeID of the erasure record |
SubjectID | string | The erased subject |
EventsAffected | int64 | Number of events marked as erased |
KeyDestroyed | bool | Whether the encryption key was destroyed |
erasure.Erasure (stored record)
| Field | Type | Description |
|---|---|---|
ID | id.ID | erasure_ TypeID |
SubjectID | string | The erased subject |
Reason | string | Erasure reason |
RequestedBy | string | Requestor identity |
EventsAffected | int64 | Events affected |
KeyDestroyed | bool | Key destruction status |
AppID / TenantID | string | Scope |
Crypto primitives
The crypto package provides Encryptor (AES-256-GCM with random nonces) and KeyStore:
import "github.com/xraph/chronicle/crypto"
enc := crypto.NewEncryptor()
ks := crypto.NewInMemoryKeyStore()
// Get or create the key for a subject
key, keyID, err := ks.GetOrCreate("user-42")
// Encrypt
ciphertext, err := enc.Encrypt(key, []byte("sensitive data"))
// Decrypt
plaintext, err := enc.Decrypt(key, ciphertext)
// Erase: destroy the key
err = ks.Delete("user-42")
// Any subsequent Decrypt with this key is impossibleFor production, replace InMemoryKeyStore with a backend that delegates to AWS KMS, HashiCorp Vault, or similar.
HTTP endpoints
| Method | Path | Description |
|---|---|---|
POST | /v1/erasures | Request erasure for a subject |
GET | /v1/erasures | List erasure records |
GET | /v1/erasures/:id | Get a specific erasure record |