Field Encryption
Tayra's field encryption engine scans your entity types for [PersonalData], [DeepPersonalData], and [SerializedPersonalData] annotations, then encrypts and decrypts those fields in-place using AES-256-GCM with per-subject keys.
ITayra — The Public API
All encryption operations go through ITayra:
// DI
var tayra = provider.GetRequiredService<ITayra>();
// Non-DI
using var tayra = TayraHost.Create(opts => opts.LicenseKey = key);
await tayra.EncryptAsync(customer); // Encrypts all PII fields in-place
await tayra.DecryptAsync(customer); // Decrypts back to plaintext| Method | Purpose |
|---|---|
EncryptAsync<T> | Encrypts all [PersonalData] fields on the object in-place. Keys are auto-created if needed. Blind indexes are computed automatically. |
DecryptAsync<T> | Decrypts all [PersonalData] fields in-place. If the key is missing (shredded), replacement values are used. |
HasPersonalData<T> | Returns true if the type has any PII annotations. Useful for conditional encryption. |
Metadata-Driven Reflection
Internal implementation — provided for clarity
The field encryption engine does not hard-code field names or types. Instead, it relies on a metadata cache to discover annotated properties:
- Type scanning — On first use, the metadata cache scans the type's public instance properties using reflection.
- Field classification — Each annotated property is classified as
Text,Deep,Serialized,TextCollection, orDeepCollection. - Subject resolution —
[DataSubjectId]properties are recorded with their group and prefix. - Caching — The metadata is cached in a
ConcurrentDictionaryand reused for all subsequent calls.
After the first call, there is zero reflection overhead.
EncryptAsync Flow
Internal implementation — provided for clarity
When EncryptAsync<T> is called:
EncryptAsync(customer)
│
├─ Get metadata for typeof(Customer)
│ └─ Fields: Name (Text), Email (Text)
│ └─ Subjects: Id (default group)
│
├─ Resolve keys for encryption
│ └─ Get or create AES key for the data subject
│ └─ Returns 32-byte AES key (created or existing)
│
├─ For each field:
│ ├─ Text: AesGcm.Encrypt → Base64 → set property
│ ├─ Deep: Recurse into nested object
│ ├─ Serialized: Serialize → AesGcm.Encrypt → set companion byte[]
│ ├─ TextCollection: Encrypt each element
│ └─ DeepCollection: Recurse into each element
│
├─ Emit telemetry (activity, metrics, audit event)
└─ ReturnKey points:
- Keys are created automatically on first use.
- The operation is in-place — properties are modified directly on the object.
- If a
[DataSubjectId]value isnull, fields in that group are skipped. - If a masking strategy is set (via the
Maskingproperty), the masked value is embedded in the ciphertext string.
DecryptAsync Flow
Internal implementation — provided for clarity
When DecryptAsync<T> is called:
DecryptAsync(customer)
│
├─ Get metadata for typeof(Customer)
│
├─ Resolve keys for decryption
│ └─ Retrieve key for the data subject
│ ├─ Key exists → byte[] returned
│ └─ Key shredded → null returned
│
├─ For each field:
│ ├─ Key exists:
│ │ └─ Base64 decode → AesGcm.Decrypt → set property
│ │
│ └─ Key is null (shredded):
│ ├─ Check for embedded redacted value (TAYRA_M: prefix)
│ │ └─ Found → use redacted value
│ └─ Not found → use MaskValue from attribute (or default)
│
├─ Emit telemetry
└─ ReturnKey points:
- Never creates keys during decryption.
- A missing key triggers the replacement value logic.
- Decryption errors (wrong key, corrupted data) are caught and logged, leaving the property unchanged.
Masking Embedding
When a [PersonalData] field has a masking strategy configured (via the Masking property):
During encryption:
- The plaintext value is redacted according to the mode (e.g.,
"Jane Doe"becomes"Ja******"withMaskAfter(2)). - The redacted value is prepended to the ciphertext:
TAYRA_M:Ja******\n{base64_ciphertext}. - The combined string is stored in the property.
During decryption with key:
- The
TAYRA_M:prefix is detected and stripped. - The remaining Base64 ciphertext is decrypted normally.
- The original plaintext is restored.
During decryption without key (shredded):
- The
TAYRA_M:prefix is detected. - The redacted value (
Ja******) is extracted and returned. - The
MaskValuefrom the attribute is ignored.
Thread Safety
ITayra is registered as a singleton and is thread-safe. Concurrent calls on different objects are safe. Concurrent calls on the same object instance are not safe — use external synchronization if needed.
See Also
- Crypto Engine — Key management layer (internal)
- Key Store — Key persistence (public extension point)
- Encryption — AES-256-GCM wire format
- Attributes Overview — The annotations that drive the encrypter
- Getting Started — End-to-end usage example
