Skip to main content

Encrypted fields

secure.Value[T] stores a value encrypted with AES-256-GCM, so a database dump or a stolen backup gives nothing away.

Storing a value​

type Patient struct {
ID int64
SSN secure.Value[string]
}

err := secure.SetKeys("2026", map[string][]byte{"2026": key}) // 32 bytes, at startup

patient := &Patient{SSN: secure.New("123-45-6789")}

err = db.Patients.Insert(ctx, patient)

loaded, err := db.Patients.Find(ctx, patient.ID)

fmt.Println(loaded.SSN.V)

The value can't leak by accident: fmt and JSON both print [encrypted], and you read .V when you actually need it. Add .Sensitive() too, so ant.LogValues keeps it out of logs.

Rotating keys​

Add a new key and make it the active one. Old rows still read with the old key, because each value records which key it used, and every new write uses the new one.

err := secure.SetKeys("2027", map[string][]byte{"2027": newKey, "2026": oldKey})

To finish, load and save the rows that still use the old key, then drop it. ChunkByKey suits this, and this SQL finds what is left:

SELECT count(*) FROM patients WHERE ssn NOT LIKE '2027:%';

Searching​

A value encrypts differently every time, so ant.Eq can't find it, and an index is no help. To look rows up, store a keyed hash beside it. Keep the hash as text, since ant.Eq needs a comparable type and []byte isn't one.

type Patient struct {
ID int64
SSN secure.Value[string]
SSNHash string `ant:"unique"`
}

func blindIndex(value string) string {
mac := hmac.New(sha256.New, hashKey)
mac.Write([]byte(value))

return hex.EncodeToString(mac.Sum(nil))
}

var P = ant.Fields[Patient]()

patient, err := db.Patients.Where(ant.Eq(&P.SSNHash, blindIndex(ssn))).First(ctx)

The hash needs its own key. It reveals when two rows hold the same value, so use it only where that is acceptable.

Limits​

  • A value isn't tied to its row, so someone who can write to the table can copy a ciphertext from one row to another.
  • Keys live in the process. Keep them in a secret store, and never in the repository.
  • GCM with random nonces is safe for about four billion values per key. Rotate before then.
  • It protects data at rest. It doesn't protect against an application that is itself compromised.

The ledger example stores IBANs this way.