Getting Started
Tayra is a .NET library for protecting personal data at the field level. You annotate your model properties with attributes like [PersonalData] and [DataSubjectId], and Tayra handles encryption, decryption, and GDPR-compliant crypto-shredding automatically.
This guide walks you through a complete encrypt-decrypt-shred workflow in under five minutes.
Install Package
You only need one package to get started:
dotnet add package Tayra.CoreInstall-Package Tayra.CoreTayra.Core includes a built-in in-memory key store that is used by default during development, so no additional packages are needed to start encrypting.
Key Stores
For production, use a secrets manager: Tayra.KeyStore.Vault, Tayra.KeyStore.AzureKeyVault, or Tayra.KeyStore.AwsParameterStore. For local development with persistent keys across restarts, use Tayra.KeyStore.PostgreSql. See Key Stores for details.
Define Your Model
Annotate your entity class with [DataSubjectId] to identify the data owner and [PersonalData] to mark fields that contain personal information:
public class Customer
{
[DataSubjectId]
public Guid Id { get; set; }
[PersonalData]
public string Name { get; set; } = "";
[PersonalData(MaskValue = "redacted@example.com")]
public string Email { get; set; } = "";
/// <summary>
/// Not annotated — stored and retrieved as plaintext.
/// </summary>
public string AccountType { get; set; } = "";
}[DataSubjectId]onIdtells Tayra to derive the encryption key from this property's value.[PersonalData]onNameandEmailmarks them for encryption.- The
MaskValueproperty onEmailspecifies the value returned after crypto-shredding. If omitted, an empty string is used forstringfields. AccountTypehas no attribute and is never touched by Tayra.
Alternative: Fluent Configuration
If you prefer not to use attributes -- for example, when working with third-party types, shared DTOs, or when you want centralized configuration -- you can use the fluent API instead:
var fluentServices = new ServiceCollection();
fluentServices.AddTayra(opts =>
{
opts.LicenseKey = licenseKey;
opts.Entity<FluentCustomer>(e =>
{
e.DataSubjectId(c => c.CustomerId);
e.PersonalData(c => c.Name);
e.PersonalData(c => c.Email);
});
});When to use each approach
- Attributes: Convenient when you own the model class and want self-documenting code.
- Fluent API: Useful for third-party types, shared DTOs, or when you prefer centralized configuration.
See Fluent API for the complete builder reference.
Configure Services
Register Tayra in your dependency injection container with AddTayra(), set your license key, and chain a key store:
var services = new ServiceCollection();
// Register Tayra with your license key (request a trial key at https://tayra.dev)
services.AddTayra(opts =>
{
opts.LicenseKey = Environment.GetEnvironmentVariable("TAYRA_LICENSE_KEY")
?? licenseKey; // fallback for samples
});
var provider = services.BuildServiceProvider();
var tayra = provider.GetRequiredService<ITayra>();AddTayra() registers ITayra and all supporting services. Resolve ITayra from the container — it's the single entry point for encryption, decryption, crypto-shredding, key rotation, and blind index queries. A valid license key is required — request a free trial key at tayra.dev. By default, Tayra uses the built-in InMemoryKeyStore (keys lost on restart). For local development with persistent keys, chain .UseSqliteKeyStore(). For production, use a secrets manager.
Configure Services (without DI)
If you don't use dependency injection, create a standalone host with TayraHost.Create:
// Simple — in-memory key store, no builder needed
using var simpleTayra = TayraHost.Create(opts =>
{
opts.LicenseKey = licenseKey;
});
await simpleTayra.EncryptAsync(new FluentCustomer { CustomerId = "non-di-001", Name = "Test", Email = "test@example.com" });For key stores, compliance, or other configuration, use the builder overload — it supports the same extension methods as the DI path:
// With builder — same extension methods as the DI path
using var builderTayra = TayraHost.Create(
opts => opts.LicenseKey = licenseKey,
builder =>
{
builder.UseSqliteKeyStore();
builder.AddCompliance(complianceOpts =>
{
complianceOpts.AddEntityType<FluentCustomer>();
});
});
await builderTayra.EncryptAsync(new FluentCustomer { CustomerId = "non-di-002", Name = "Builder Test", Email = "builder@example.com" });Encrypt and Decrypt
Create an entity, encrypt it, and then decrypt it back:
// Create a customer with personal data
var customer = new Customer
{
Id = Guid.NewGuid(),
Name = "Jane Doe",
Email = "jane@example.com",
AccountType = "Premium",
};
Console.WriteLine("=== Original ===");
Console.WriteLine($" Name: {customer.Name}");
Console.WriteLine($" Email: {customer.Email}");
Console.WriteLine($" Type: {customer.AccountType}");
// Encrypt — [PersonalData] fields become ciphertext in-place
await tayra.EncryptAsync(customer);
Console.WriteLine("\n=== After Encryption ===");
Console.WriteLine($" Name: {customer.Name}"); // Base64 ciphertext
Console.WriteLine($" Email: {customer.Email}"); // Base64 ciphertext
Console.WriteLine($" Type: {customer.AccountType}"); // Unchanged
// Decrypt — fields are restored to their original plaintext values
await tayra.DecryptAsync(customer);
Console.WriteLine("\n=== After Decryption ===");
Console.WriteLine($" Name: {customer.Name}"); // "Jane Doe"
Console.WriteLine($" Email: {customer.Email}"); // "jane@example.com"After EncryptAsync, the Name and Email properties contain Base64-encoded AES-256-GCM ciphertext. AccountType is untouched. After DecryptAsync, the original plaintext values are restored.
Crypto-Shred
Crypto-shredding is GDPR-compliant data erasure. Instead of finding and deleting every copy of a person's data, you delete their encryption key. All their encrypted data becomes permanently unreadable:
// Crypto-shred: delete the encryption key for this data subject
await tayra.ShredAsync(customer.Id.ToString());
// Re-encrypt to simulate reading from a data store after shredding
customer.Name = "encrypted-blob";
customer.Email = "encrypted-blob";
// Decrypt after shredding — replacement values are returned
await tayra.DecryptAsync(customer);
Console.WriteLine("\n=== After Crypto-Shredding ===");
Console.WriteLine($" Name: \"{customer.Name}\""); // "" (default replacement)
Console.WriteLine($" Email: \"{customer.Email}\""); // "redacted@example.com" (custom replacement)
Console.WriteLine($" Type: {customer.AccountType}"); // UnchangedAfter the key is deleted, DecryptAsync returns replacement values instead of the original data:
Namebecomes""(the default replacement for strings)Emailbecomes"redacted@example.com"(the custom replacement specified in the attribute)
This is irreversible. The original data cannot be recovered.
Next Steps
- Attributes -- Learn about all four PII attributes:
[PersonalData],[DataSubjectId],[DeepPersonalData], and[SerializedPersonalData] - Configuration -- Customize cache duration, key size, and licensing
- Architecture -- Understand the AES-256-GCM wire format and key management
- Key Stores -- Configure PostgreSQL, Vault, Azure Key Vault, or AWS for production
- Installation -- Full package reference and target framework details
