Skip to content

Encryption

Tayra uses AES-256-GCM (Galois/Counter Mode) for all field-level encryption. This is an authenticated encryption algorithm that provides both confidentiality and integrity - if the ciphertext or bound context is tampered with, decryption fails with a CryptographicException.

AES-256-GCM

AES-GCM is the recommended symmetric encryption mode for modern applications:

  • Authenticated - The authentication tag detects any modification to the ciphertext.
  • Non-deterministic - Each encryption produces different ciphertext because a fresh random nonce is used every time.
  • Performance - Hardware-accelerated on modern CPUs via AES-NI instructions.
  • Standard - NIST SP 800-38D, widely adopted in TLS 1.3, IPsec, and SSH.

Tayra uses the .NET System.Security.Cryptography.AesGcm class, which delegates to the OS cryptographic provider (OpenSSL on Linux, CNG on Windows, CommonCrypto on macOS).

Wire Format

Every encrypted value produced by Tayra follows a versioned binary format. The 0x03 and 0x04 payloads that field encryption actually writes carry a key version after the format byte:

┌───────────┬──────────────┬──────────┬──────────────┬──────────────┐
│ Version   │ Key version  │ Nonce    │ Ciphertext   │ Auth Tag     │
│ (1 byte)  │ (2 bytes BE) │ (12 B)   │ (N bytes)    │ (16 bytes)   │
└───────────┴──────────────┴──────────┴──────────────┴──────────────┘

The 0x01 and 0x02 primitives omit the key version field, so their ciphertext begins at offset 13 instead of 15.

For the byte-exact rules, including associated-data construction, envelope blobs and a language-independent reference decryptor, see the Wire Format Specification.

ComponentSizeDescription
Version1 byteFormat version. 0x01 is emitted only by the no-AAD AesGcmEncryptor.Encrypt primitive. 0x02 adds associated-data context binding for stronger misuse resistance. 0x03 additionally embeds the key version (so rotated keys stay decryptable) and is what field-level encryption writes. 0x04 is cryptographically identical to 0x03 and differs only in the plaintext it carries: JSON instead of the legacy binary encoding. See Serialized Payloads.
Nonce12 bytes (96 bits)Randomly generated per encryption operation using RandomNumberGenerator.
CiphertextN bytesThe encrypted data. Same length as the plaintext input.
Auth Tag16 bytes (128 bits)GCM authentication tag for tamper detection.

Total overhead: 31 bytes per encrypted value for the 0x03 and 0x04 formats (1 + 2 + 12 + 16), and 29 bytes for the 0x01 and 0x02 primitives (1 + 12 + 16).

For string fields, the binary output is Base64-encoded before being stored in the property, which adds approximately 33% to the encoded size.

Associated Data Context Binding

Version 0x02 payloads bind encryption to field context using AES-GCM associated data (AAD).

  • AAD is authenticated but not encrypted.
  • Tayra binds encrypted values to context components: entity type, field name, and field group.
  • Moving ciphertext between fields with the same key scope fails authentication at decrypt time.

This reduces substitution/mix-and-match attacks in scenarios where one subject/group key protects multiple fields.

Because 0x01 payloads carry no AAD, they cannot be context-verified. The AAD decrypt path (DecryptWithAssociatedData / DecryptStringWithAssociatedData) therefore rejects 0x01 payloads outright with a CryptographicException - there is no ciphertext-splice gap to configure around. The 0x01 format exists only for the no-AAD primitive API (AesGcmEncryptor.Encrypt / Decrypt), which keeps round-tripping it. All field-level encryption uses AAD-bound formats.

Nonce Generation

Each encryption operation generates a fresh 12-byte (96-bit) nonce using System.Security.Cryptography.RandomNumberGenerator:

  • The nonce is never reused for the same key. AES-GCM security depends on nonce uniqueness.
  • The nonce is stored alongside the ciphertext (in the wire format), so it does not need to be tracked separately.
  • With 96-bit random nonces and AES-256 keys, the probability of a collision is negligible for practical data volumes (birthday bound: ~2^48 encryptions per key).

Nonce Reuse

Reusing a nonce with the same key completely breaks AES-GCM security, allowing an attacker to recover plaintext and forge authentication tags. Tayra prevents this by generating a cryptographically random nonce for every encryption operation.

Key Sizes

Tayra supports three AES key sizes:

Key SizeBytesConfiguration
128 bits16 bytesopts.KeySizeInBits = 128
192 bits24 bytesopts.KeySizeInBits = 192
256 bits32 bytesopts.KeySizeInBits = 256 (default)

AES-256 is the default and recommended setting. All three sizes use the same wire format - the key size is determined by the key itself, not stored in the ciphertext.

String Encryption

For [PersonalData] string fields, Tayra:

  1. Encodes the string to UTF-8 bytes.
  2. Encrypts the bytes using AES-GCM (producing a versioned wire format, 0x03 for new writes).
  3. Encodes the result as a Base64 string.
  4. Stores the Base64 string back in the property.

Decryption reverses this: Base64 decode, AES-GCM decrypt, UTF-8 decode. A string member can hold both its plaintext and its ciphertext, so no companion member is needed.

Serialized Payloads

A value type cannot hold ciphertext in place - an int has nowhere to put a Base64 string - so for non-string [PersonalData] fields Tayra:

  1. Serializes the value to UTF-8 JSON.
  2. Encrypts the bytes using AES-GCM (version 0x04 for new writes).
  3. Stores the raw encrypted bytes (not Base64) in the companion byte[]? member, and zeroes the original member.

Tayra 2.3.x and earlier serialized step 1 with a hand-rolled binary encoder into a 0x03 payload, which supported only eleven types. Both encodings are always readable: the decoder is selected by the version byte on the data, so existing 0x03 ciphertext keeps decrypting, and a field migrates to 0x04 lazily the next time it is encrypted. TayraOptions.PayloadFormat controls which format new writes emit - see [PersonalData] for the staged-rollout and rollback implications.

Partial Redaction

When a redaction strategy is set on a [PersonalData] attribute (via the Redaction property), Tayra embeds the redacted value in the ciphertext string using a prefix format:

TAYRA_M:{redacted_value}\n{base64_ciphertext}

During decryption:

  • If the key exists, the prefix is stripped and the ciphertext is decrypted normally.
  • If the key has been deleted (crypto-shredding), the redacted value is extracted and returned.

Searchable Encrypted Fields

Since AES-256-GCM is non-deterministic, you cannot query encrypted fields directly. Tayra provides HMAC blind indexes as a companion feature - one-way hashes stored alongside encrypted fields that enable equality queries without exposing plaintext.

See Also