CyTypes.EntityFramework
1.0.0
See the version list below for details.
dotnet add package CyTypes.EntityFramework --version 1.0.0
NuGet\Install-Package CyTypes.EntityFramework -Version 1.0.0
<PackageReference Include="CyTypes.EntityFramework" Version="1.0.0" />
<PackageVersion Include="CyTypes.EntityFramework" Version="1.0.0" />
<PackageReference Include="CyTypes.EntityFramework" />
paket add CyTypes.EntityFramework --version 1.0.0
#r "nuget: CyTypes.EntityFramework, 1.0.0"
#:package CyTypes.EntityFramework@1.0.0
#addin nuget:?package=CyTypes.EntityFramework&version=1.0.0
#tool nuget:?package=CyTypes.EntityFramework&version=1.0.0
cyTypes
Always-encrypted primitive types for .NET — AES-256-GCM encryption, taint tracking, security policies, and memory protection built into every value.
Copyright 2026 Matteo Sala (cysalazar@cysalazar.com)
Table of Contents
- Overview
- Why cyTypes?
- Installation
- Quick Start
- .NET Native vs cyTypes — Side-by-Side Comparison
- Supported Types
- API Reference
- CyTypeBase — Common Functionality
- Security Policies
- Taint Tracking
- Auto-Destroy
- Key Rotation
- Memory Protection
- Roslyn Analyzer (CY0001-CY0004)
- FHE — Fully Homomorphic Encryption
- Benchmarks
- Cryptographic Primitives
- Project Structure
- Building & Testing
- Roadmap
- Contributing
- License
Overview
cyTypes replaces standard .NET primitives (int, string, bool, etc.) with encrypted counterparts (CyInt, CyString, CyBool, etc.) that keep data encrypted in memory at all times using AES-256-GCM in pinned, locked buffers.
Key features:
- Always-encrypted: Values never exist as plaintext in managed memory
- Taint tracking: Automatic propagation of compromise/taint state through operations
- Security policies: Configurable security levels (Maximum, Balanced, Performance, HomomorphicBasic)
- Memory protection: Pinned buffers + OS-level memory locking (mlock/VirtualLock) + zeroing on dispose
- Auto-destroy: Automatic disposal after configurable decryption count threshold
- Drop-in operators: Standard arithmetic, comparison, and conversion operators
Why cyTypes?
Sensitive data in .NET lives as plaintext in memory. A memory dump, a debugger attach, or a GC heap inspection exposes every
int salary,string ssn, anddecimal balancein your process.SecureStringis deprecated and never covered numeric types.cyTypes is a drop-in replacement. Change
inttoCyInt,stringtoCyString— your existing operators, comparisons, and LINQ queries keep working. No API redesign required.Security is automatic, not opt-in. Taint tracking, auto-destroy, and memory zeroing happen without developer intervention. The Roslyn analyzer catches mistakes at compile time.
Installation
# Core primitives (CyInt, CyString, CyBool, CyBytes, etc.)
dotnet add package CyTypes.Primitives
# Core crypto engine (included as dependency of Primitives)
dotnet add package CyTypes.Core
# Encrypted collections (CyList, CyDictionary)
dotnet add package CyTypes.Collections
# Roslyn analyzer — compile-time security checks
dotnet add package CyTypes.Analyzer
# Auto-redacting logger
dotnet add package CyTypes.Logging
# Entity Framework Core value converters
dotnet add package CyTypes.EntityFramework
# ASP.NET Core dependency injection
dotnet add package CyTypes.DependencyInjection
# Fully Homomorphic Encryption (Microsoft SEAL)
dotnet add package CyTypes.Fhe
Quick Start
using CyTypes.Primitives;
// Implicit conversion — encrypts immediately
CyInt balance = 1000;
// Arithmetic stays encrypted
using var deposit = new CyInt(250);
using var newBalance = balance + deposit;
// Explicit decryption — marks instance as compromised
int plaintext = newBalance.ToInsecureInt();
Console.WriteLine(plaintext); // 1250
Console.WriteLine(newBalance.IsCompromised); // True
.NET Native vs cyTypes — Side-by-Side Comparison
Integers: int vs CyInt
// ── .NET native ──────────────────────────────────
int a = 100;
int b = 50;
int sum = a + b; // 150 — plaintext in memory
int product = a * b; // 5000
bool equal = (a == b); // false
string s = a.ToString(); // "100"
int parsed = int.Parse("42"); // 42
// ── cyTypes ──────────────────────────────────────
CyInt ca = 100; // implicit conversion, encrypted immediately
using var cb = new CyInt(50);
using var cSum = ca + cb; // encrypted arithmetic
using var cProd = ca * cb; // encrypted multiplication
bool cEqual = (ca == cb); // constant-time encrypted comparison
CyInt cParsed = CyInt.Parse("42");
// Decryption is explicit and tracked
int plainSum = cSum.ToInsecureInt(); // 150 — cSum.IsCompromised = true
int plainProd = cProd.ToInsecureInt(); // 5000
// Bitwise operators work too
using var and = ca & cb; // bitwise AND
using var or = ca | cb; // bitwise OR
using var xor = ca ^ cb; // bitwise XOR
using var not = ~ca; // bitwise NOT
using var shl = ca << 2; // shift left
using var shr = ca >> 1; // shift right
// Overflow detection (with Maximum policy)
using var safe = new CyInt(int.MaxValue, SecurityPolicy.Maximum);
// safe + new CyInt(1) → throws OverflowException
Strings: string vs CyString
// ── .NET native ──────────────────────────────────
string greeting = "Hello, World!";
int len = greeting.Length; // 13
string upper = greeting.ToUpper(); // "HELLO, WORLD!"
string sub = greeting.Substring(0, 5); // "Hello"
bool contains = greeting.Contains("World"); // true
string[] parts = greeting.Split(','); // ["Hello", " World!"]
string joined = string.Join("-", parts); // "Hello- World!"
// ── cyTypes ──────────────────────────────────────
using var cGreeting = new CyString("Hello, World!");
int cLen = cGreeting.Length; // 13 — metadata, no decryption
using var cUpper = cGreeting.ToUpper(); // encrypted result
using var cSub = cGreeting.Substring(0, 5); // encrypted result
bool cContains = cGreeting.Contains("World"); // true — no compromise flag
CyString[] cParts = cGreeting.Split(','); // encrypted array
using var cJoined = CyString.Join("-", cParts); // encrypted join
// Secure comparison — constant-time, no compromise
bool eq = cGreeting.SecureEquals(new CyString("Hello, World!")); // true
// Indexer — returns plaintext char, marks compromise
char c = cGreeting[0]; // 'H', cGreeting.IsCompromised = true
// Additional query methods — no compromise
bool starts = cGreeting.StartsWith("Hello"); // true
bool ends = cGreeting.EndsWith("!"); // true
int idx = cGreeting.IndexOf("World"); // 7
// Transformation methods — all return new encrypted CyString
using var trimmed = cGreeting.Trim();
using var replaced = cGreeting.Replace("World", ".NET");
using var padded = cGreeting.PadRight(20, '.');
using var inserted = cGreeting.Insert(5, "!!");
Booleans: bool vs CyBool
// ── .NET native ──────────────────────────────────
bool x = true;
bool y = false;
bool andResult = x & y; // false
bool orResult = x | y; // true
bool xorResult = x ^ y; // true
bool notResult = !x; // false
// ── cyTypes ──────────────────────────────────────
CyBool cx = true; // implicit conversion
using var cy = new CyBool(false);
using var cAnd = cx & cy; // encrypted AND
using var cOr = cx | cy; // encrypted OR
using var cXor = cx ^ cy; // encrypted XOR
using var cNot = !cx; // encrypted NOT
bool plain = cAnd.ToInsecureBool(); // false — cAnd.IsCompromised = true
Floating-Point: double/float vs CyDouble/CyFloat
// ── .NET native ──────────────────────────────────
double d = 3.14159;
double nan = double.NaN;
double inf = double.PositiveInfinity;
bool isNan = double.IsNaN(nan); // true
// ── cyTypes ──────────────────────────────────────
CyDouble cd = 3.14159; // implicit conversion
using var cNan = CyDouble.NaN; // encrypted NaN
using var cInf = CyDouble.PositiveInfinity; // encrypted Infinity
using var cEps = CyDouble.Epsilon; // encrypted Epsilon
// Arithmetic
using var result = cd + new CyDouble(1.0); // encrypted addition
using var div = cd / new CyDouble(2.0); // encrypted division
// CyFloat — same API
CyFloat cf = 2.71828f;
using var cfNan = CyFloat.NaN;
using var cfInf = CyFloat.PositiveInfinity;
using var cfEps = CyFloat.Epsilon;
// Parse
CyDouble parsed = CyDouble.Parse("3.14");
bool ok = CyFloat.TryParse("2.71", out CyFloat? fParsed);
Decimal: decimal vs CyDecimal
// ── .NET native ──────────────────────────────────
decimal price = 29.99m;
decimal tax = price * 0.21m;
decimal total = price + tax;
// ── cyTypes — ideal for financial calculations ──
using var cPrice = new CyDecimal(29.99m);
using var cTaxRate = new CyDecimal(0.21m);
using var cTax = cPrice * cTaxRate; // encrypted, never plaintext
using var cTotal = cPrice + cTax;
// Predefined constants
using var zero = CyDecimal.Zero; // 0m
using var one = CyDecimal.One; // 1m
using var neg = CyDecimal.MinusOne; // -1m
decimal plainTotal = cTotal.ToInsecureDecimal(); // 36.2879m
Byte Arrays, Guid, DateTime
// ── CyBytes ──────────────────────────────────────
byte[] raw = new byte[] { 0x01, 0x02, 0x03 };
using var cBytes = new CyBytes(raw);
int byteLen = cBytes.Length; // 3 — metadata
byte[] decrypted = cBytes.ToInsecureBytes(); // marks compromised
// Implicit/explicit conversions
CyBytes fromArr = (CyBytes)raw; // implicit
byte[] toArr = (byte[])fromArr; // explicit — compromises
// ── CyGuid ───────────────────────────────────────
Guid id = Guid.NewGuid();
CyGuid cId = id; // implicit conversion
Guid plainId = (Guid)cId; // explicit — compromises
bool same = (cId == new CyGuid(id)); // encrypted comparison
// ── CyDateTime ───────────────────────────────────
CyDateTime cNow = DateTime.UtcNow; // implicit conversion
DateTime plainNow = cNow.ToInsecureDateTime(); // explicit decryption
bool before = (cNow < new CyDateTime(DateTime.MaxValue)); // comparison
Collections: List vs CyList, Dictionary vs CyDictionary
// ── .NET native ──────────────────────────────────
var list = new List<int> { 1, 2, 3 };
list.Add(4);
int first = list[0];
var dict = new Dictionary<string, int> { ["key"] = 42 };
int val = dict["key"];
// ── cyTypes (thread-safe, auto-dispose) ──────────
using var cList = new CyList<CyInt>();
cList.Add(new CyInt(1));
cList.Add(new CyInt(2));
cList.Add(new CyInt(3));
CyInt cFirst = cList[0]; // encrypted value
int count = cList.Count; // 3
cList.RemoveAt(0); // disposes the removed element
using var cDict = new CyDictionary<string, CyString>();
cDict.Add("name", new CyString("Alice"));
cDict.Add("ssn", new CyString("123-45-6789"));
CyString name = cDict["name"]; // encrypted lookup
bool hasKey = cDict.ContainsKey("ssn"); // true
cDict.Remove("ssn"); // disposes the CyString value
// Enumeration
foreach (var item in cList)
{
// item is CyInt — still encrypted
}
// Clear disposes all elements
cList.Clear();
cDict.Clear();
EF Core With and Without cyTypes
// ── Without cyTypes — plaintext in memory ────────
public class User
{
public int Id { get; set; }
public string Name { get; set; } // plaintext
public string SocialSecurity { get; set; } // plaintext!
public decimal Salary { get; set; } // plaintext!
}
// ── With cyTypes — encrypted in memory ───────────
public class SecureUser
{
public int Id { get; set; }
public CyString Name { get; set; } // encrypted
public CyString SocialSecurity { get; set; } // encrypted
public CyDecimal Salary { get; set; } // encrypted
}
// DbContext configuration — one line
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.UseCyTypes(); // registers all 10 value converters
}
Value converters handle transparent encryption/decryption at the persistence boundary. Data is stored as native types in the database and re-encrypted on read.
Available converters: CyIntValueConverter, CyLongValueConverter, CyFloatValueConverter, CyDoubleValueConverter, CyDecimalValueConverter, CyBoolValueConverter, CyStringValueConverter, CyGuidValueConverter, CyDateTimeValueConverter, CyBytesValueConverter.
JSON Serialization
using CyTypes.Primitives.Serialization;
using System.Text.Json;
// Configure once
var options = new JsonSerializerOptions().AddCyTypesConverters();
// Serialize — calls ToInsecureValue() internally (marks compromised)
using var salary = new CyDecimal(85000m);
string json = JsonSerializer.Serialize(salary, options); // "85000"
// Deserialize — creates fresh encrypted instance
CyDecimal restored = JsonSerializer.Deserialize<CyDecimal>(json, options);
// restored is a new encrypted instance, not compromised
// Works with complex objects
var user = new { Name = new CyString("Alice"), Age = new CyInt(30) };
string userJson = JsonSerializer.Serialize(user, options);
// {"Name":"Alice","Age":30}
Supported converters: all 10 CyTypes (CyInt, CyLong, CyFloat, CyDouble, CyDecimal, CyBool, CyString, CyBytes as base64, CyGuid, CyDateTime).
Logging with Auto-Redaction
// ── Without cyTypes — sensitive data leaks to logs ──
logger.LogInformation("User salary: {Salary}", salary); // "User salary: 85000" !!
// ── With cyTypes — automatic redaction ───────────────
// CyType.ToString() never exposes plaintext:
logger.LogInformation("User salary: {Salary}", cySalary);
// Output: "User salary: [CyDecimal:Encrypted|Policy=Balanced|Compromised=False]"
// RedactingLogger adds an extra safety net:
using CyTypes.Logging;
var redactingLogger = new RedactingLogger(innerLogger);
redactingLogger.LogInformation("Data: {Data}", someString);
// Automatically redacts hex payloads, base64 payloads, and CyType metadata patterns
The RedactingLoggerProvider wraps any ILoggerProvider to apply redaction across your entire logging pipeline.
Dependency Injection Setup
using CyTypes.DependencyInjection;
// In Program.cs or Startup.cs
builder.Services.AddCyTypes(options =>
{
options.DefaultPolicy = SecurityPolicy.Balanced;
options.EnableRedactingLogger = true; // default: true
options.EnableAudit = true; // default: true
options.EnablePqcKeyEncapsulation = false; // PQC stub, not yet production
});
// Optional: register FHE engine
builder.Services.AddCyTypesFhe(sp =>
{
var keyManager = new SealKeyManager();
// Initialize with BFV scheme parameters...
return new SealBfvEngine(keyManager);
});
AddCyTypes registers: default SecurityPolicy, crypto engine, SecurityAuditor, LoggingAuditSink, and optionally the RedactingLoggerProvider and PQC MlKemKeyEncapsulation.
Supported Types
| CyType | .NET Equivalent | Operators | Interfaces |
|---|---|---|---|
CyInt |
int |
+ - * / % == != < > <= >= & \| ^ ~ << >> >>> |
IEquatable<CyInt>, IComparable<CyInt> |
CyLong |
long |
+ - * / % == != < > <= >= & \| ^ ~ << >> >>> |
IEquatable<CyLong>, IComparable<CyLong> |
CyDouble |
double |
+ - * / % == != < > <= >= |
IEquatable<CyDouble>, IComparable<CyDouble> |
CyFloat |
float |
+ - * / % == != < > <= >= |
IEquatable<CyFloat>, IComparable<CyFloat> |
CyDecimal |
decimal |
+ - * / % == != < > <= >= |
IEquatable<CyDecimal>, IComparable<CyDecimal> |
CyBool |
bool |
& \| ^ ! == != < > <= >= |
IEquatable<CyBool>, IComparable<CyBool> |
CyString |
string |
+ == != < > <= >= [] |
IEquatable<CyString>, IComparable<CyString> |
CyBytes |
byte[] |
== != < > <= >= + implicit/explicit conversions |
IEquatable<CyBytes>, IComparable<CyBytes> |
CyGuid |
Guid |
== != < > <= >= + implicit/explicit conversions |
IEquatable<CyGuid>, IComparable<CyGuid> |
CyDateTime |
DateTime |
== != < > <= >= |
IEquatable<CyDateTime>, IComparable<CyDateTime> |
API Reference
CyInt / CyLong
// Construction
CyInt a = 42; // implicit from int
var b = new CyInt(42); // explicit constructor
var c = new CyInt(42, SecurityPolicy.Maximum); // with policy
CyInt d = CyInt.Parse("42"); // from string
CyInt.TryParse("42", out CyInt? e); // safe parse
CyInt.Parse("42".AsSpan()); // from ReadOnlySpan<char>
// Constants
CyInt min = CyInt.MinValue; // int.MinValue (-2,147,483,648)
CyInt max = CyInt.MaxValue; // int.MaxValue (2,147,483,647)
// Decryption
int plain = a.ToInsecureInt(); // marks compromised
int cast = (int)a; // explicit cast — also compromises
// Conversions (widening — no data loss)
CyLong asLong = a; // implicit CyInt → CyLong
CyDouble asDouble = a; // implicit CyInt → CyDouble
// Arithmetic: +, -, *, /, %
// Comparison: ==, !=, <, >, <=, >=
// Bitwise: &, |, ^, ~, <<, >>, >>>
CyLong has the same API surface with long equivalents (ToInsecureLong(), CyLong.MinValue, etc.).
CyFloat / CyDouble
// Special IEEE 754 values
CyDouble nan = CyDouble.NaN;
CyDouble posInf = CyDouble.PositiveInfinity;
CyDouble negInf = CyDouble.NegativeInfinity;
CyDouble epsilon = CyDouble.Epsilon;
CyDouble min = CyDouble.MinValue;
CyDouble max = CyDouble.MaxValue;
// Same for CyFloat
CyFloat fNan = CyFloat.NaN;
CyFloat fPosInf = CyFloat.PositiveInfinity;
CyFloat fNegInf = CyFloat.NegativeInfinity;
CyFloat fEps = CyFloat.Epsilon;
// Parse
CyDouble d = CyDouble.Parse("3.14159");
CyFloat.TryParse("2.71", out CyFloat? f);
// Decryption
double plain = d.ToInsecureDouble();
float fPlain = f.ToInsecureFloat();
CyDecimal
// Predefined constants — useful for financial calculations
CyDecimal zero = CyDecimal.Zero; // 0m
CyDecimal one = CyDecimal.One; // 1m
CyDecimal minusOne = CyDecimal.MinusOne; // -1m
CyDecimal min = CyDecimal.MinValue;
CyDecimal max = CyDecimal.MaxValue;
// Parse
CyDecimal d = CyDecimal.Parse("29.99");
CyDecimal.TryParse("100.50", out CyDecimal? result);
// Decryption
decimal plain = d.ToInsecureDecimal();
CyBool
CyBool a = true;
using var b = new CyBool(false);
// Logical operators
using var and = a & b; // false
using var or = a | b; // true
using var xor = a ^ b; // true
using var not = !a; // false
bool plain = a.ToInsecureBool();
CyString
Metadata (no decryption needed):
Length— string lengthIsEmpty— true if length is 0IsNullOrEmpty(CyString?)/IsNullOrWhiteSpace(CyString?)— static checks
Transformation methods (decrypt, compute, re-encrypt — return new CyString):
ToUpper(),ToLower(),ToUpperInvariant(),ToLowerInvariant()Trim(),TrimStart(),TrimEnd()Substring(startIndex),Substring(startIndex, length)Replace(oldValue, newValue)Insert(startIndex, value)Remove(startIndex),Remove(startIndex, count)PadLeft(totalWidth),PadLeft(totalWidth, char),PadRight(totalWidth),PadRight(totalWidth, char)
Query methods (decrypt internally but do not mark compromise):
Contains(value),StartsWith(value),EndsWith(value)IndexOf(value),LastIndexOf(value)IsNullOrEmpty(),IsNullOrWhiteSpace()- All accept optional
StringComparisonparameter
Split/Join:
Split(char separator)/Split(char[] separators)— returnsCyString[]CyString.Concat(a, b)— static concatenationCyString.Join(separator, values)— static join
Secure methods (HMAC-based constant-time, never mark compromise):
SecureEquals(CyString other)SecureContains(string value)SecureStartsWith(string value)SecureEndsWith(string value)
Decryption:
ToInsecureString()— returns plaintext, marks compromised[int index]— returnschar, marks compromised
CyBytes
using var cb = new CyBytes(new byte[] { 0x01, 0x02 });
int len = cb.Length; // metadata — no decryption
byte[] plain = cb.ToInsecureBytes(); // marks compromised
// Implicit/explicit conversions
CyBytes from = (CyBytes)someByteArray;
byte[] to = (byte[])from;
CyGuid
CyGuid cg = Guid.NewGuid(); // implicit conversion
Guid plain = (Guid)cg; // explicit — compromises
bool eq = (cg == new CyGuid(Guid.Empty)); // encrypted comparison
CyDateTime
CyDateTime cdt = DateTime.UtcNow; // implicit conversion
DateTime plain = cdt.ToInsecureDateTime(); // marks compromised
bool before = (cdt < new CyDateTime(DateTime.MaxValue));
int cmp = cdt.CompareTo(otherCyDateTime);
CyTypeBase — Common Functionality
All CyTypes inherit from CyTypeBase<TNative>, which provides:
Properties
| Property | Type | Description |
|---|---|---|
Policy |
SecurityPolicy |
Active security policy |
InstanceId |
Guid |
Unique identifier per instance |
CreatedUtc |
DateTime |
UTC creation timestamp |
IsDisposed |
bool |
Whether Dispose() has been called |
IsCompromised |
bool |
Whether plaintext has been exposed |
IsTainted |
bool |
Whether taint has been propagated from another source |
Methods
| Method | Description |
|---|---|
RotateKeyAndReEncrypt() |
Atomically: decrypt with old key, derive new key via HKDF, re-encrypt |
ReEncryptWithCurrentKey() |
Re-encrypt without changing the key |
ToSecureBytes() |
Serialize to binary envelope (ciphertext + nonce + tag + HMAC-SHA512) |
ElevatePolicy(SecurityPolicy) |
Upgrade to a stricter policy (demotion not allowed) |
ApplyPolicy(SecurityPolicy) |
Change policy (demotion allowed only if AllowDemotion is set — marks tainted) |
MarkCompromised() |
Explicitly mark as compromised |
MarkTainted() |
Explicitly mark as tainted |
ClearTaint(string reason) |
Clear taint flag with documented reason |
Dispose() |
Zero secure buffers and release resources |
DisposeAsync() |
Async disposal |
Events
| Event | Raised When |
|---|---|
SecurityBreached |
Instance is marked compromised |
PolicyChanged |
Security policy changes |
TaintCleared |
Taint flag is cleared |
using var cy = new CyInt(42);
cy.SecurityBreached += (sender, e) =>
Console.WriteLine($"Compromised: {e}");
cy.PolicyChanged += (sender, e) =>
Console.WriteLine($"Policy changed: {e}");
_ = cy.ToInsecureInt(); // fires SecurityBreached
Security Policies
Four predefined policies control the security/performance tradeoff:
| Policy | Max Decryptions | Auto-Destroy | Taint Mode | Overflow Mode | Memory Protection |
|---|---|---|---|---|---|
Maximum |
10 | Yes | Strict | Checked | PinnedLocked |
Balanced |
100 | No | Standard | Unchecked | PinnedLocked |
Performance |
Unlimited | No | Relaxed | Unchecked | PinnedLocked |
HomomorphicBasic |
Reserved for FHE | — | — | — | — |
// Maximum security — 10 decryptions max, auto-destroy, strict taint
using var secret = new CyInt(42, SecurityPolicy.Maximum);
// Balanced (default) — 100 decryptions, standard taint tracking
using var normal = new CyInt(42, SecurityPolicy.Balanced);
// Performance — unlimited decryptions, relaxed taint
using var fast = new CyInt(42, SecurityPolicy.Performance);
// HomomorphicBasic — reserved for FHE operations (requires CyTypes.Fhe package)
// using var fhe = new CyInt(42, SecurityPolicy.HomomorphicBasic);
Custom Policies — SecurityPolicyBuilder
var policy = new SecurityPolicyBuilder()
.WithName("MyPolicy")
.WithMaxDecryptionCount(50)
.WithTaintMode(TaintMode.Strict)
.WithAuditLevel(AuditLevel.AllOperations)
.WithMemoryProtection(MemoryProtection.PinnedLocked)
.WithOverflowMode(OverflowMode.Checked) // throws on integer overflow
.WithAutoDestroy(true)
.WithAllowDemotion(false) // prevent policy downgrades
.WithArithmeticMode(ArithmeticMode.Standard)
.WithComparisonMode(ComparisonMode.Standard)
.WithStringOperationMode(StringOperationMode.Standard)
.WithKeyRotation(KeyRotationPolicy.Manual)
.WithDecryptionRateLimit(10) // max 10 decryptions/second
.WithKeyStoreMinimumCapability(KeyStoreCapability.InMemory)
.Build();
using var cy = new CyInt(42, policy);
Full SecurityPolicyBuilder API:
| Method | Description |
|---|---|
WithName(string) |
Display name for the policy |
WithMaxDecryptionCount(int) |
Max decryptions before auto-destroy (if enabled) |
WithTaintMode(TaintMode) |
Taint propagation mode: Strict, Standard, Relaxed |
WithAuditLevel(AuditLevel) |
Audit verbosity level |
WithMemoryProtection(MemoryProtection) |
Memory protection level |
WithOverflowMode(OverflowMode) |
Integer arithmetic: Checked or Unchecked |
WithAutoDestroy(bool) |
Enable auto-destroy on decryption limit |
WithAllowDemotion(bool) |
Allow policy demotion (marks tainted if true) |
WithArithmeticMode(ArithmeticMode) |
Arithmetic computation mode |
WithComparisonMode(ComparisonMode) |
Comparison mode for encrypted values |
WithStringOperationMode(StringOperationMode) |
String operation mode |
WithKeyRotation(KeyRotationPolicy) |
Key rotation policy |
WithDecryptionRateLimit(int) |
Max decryptions per second |
WithKeyStoreMinimumCapability(KeyStoreCapability) |
Minimum key store capability |
Build() |
Validate and build the policy |
Note: FHE-based arithmetic/comparison modes (
HomomorphicFull,HomomorphicBasic,HomomorphicCircuit,HomomorphicEquality) are reserved for Phase 3 and cannot be selected via the builder.
Taint Tracking
cyTypes tracks data compromise and taint propagation automatically:
using var a = new CyInt(10);
_ = a.ToInsecureInt(); // a.IsCompromised = true
using var b = new CyInt(20); // b is clean
using var c = a + b; // c.IsTainted = true (compromised operand)
// Clear taint with documented reason
c.ClearTaint("verified-clean-by-security-review");
Propagation rules:
| Operation | Source State | Result State |
|---|---|---|
ToInsecure*() |
any | IsCompromised = true |
a + b (either tainted) |
tainted | IsTainted = true |
a + b (both clean, diff policies) |
clean | higher policy, clean |
| Policy demotion | any | IsTainted = true |
ClearTaint(reason) |
tainted | clean |
Auto-Destroy
Instances can self-destruct after reaching a decryption threshold:
var policy = new SecurityPolicyBuilder()
.WithMaxDecryptionCount(3)
.WithAutoDestroy(true)
.Build();
var cy = new CyInt(42, policy);
_ = cy.ToInsecureInt(); // decryption 1
_ = cy.ToInsecureInt(); // decryption 2
_ = cy.ToInsecureInt(); // decryption 3 — triggers auto-destroy
cy.IsDisposed; // true — any further access throws ObjectDisposedException
Key Rotation
cyTypes supports atomic key rotation with automatic re-encryption:
using var cy = new CyInt(42);
// Atomically: decrypt → derive new key via HKDF → re-encrypt
cy.RotateKeyAndReEncrypt();
// Value is preserved with the new key
int value = cy.ToInsecureInt(); // 42
// Re-encrypt with the current key (no rotation)
cy.ReEncryptWithCurrentKey();
Important: Never call
KeyManager.RotateKey()directly — it destroys the old key, making existing ciphertext unreadable. Always useRotateKeyAndReEncrypt()which handles the full cycle atomically.
Memory Protection
All encrypted data is stored in SecureBuffer instances that provide:
- Pinned allocation:
GC.AllocateArray<byte>(size, pinned: true)— prevents GC relocation - OS-level locking:
mlock(Linux/macOS) /VirtualLock(Windows) — prevents paging to disk - Zeroing on dispose:
CryptographicOperations.ZeroMemory— no residual plaintext - Thread-safe dispose: Atomic
Interlocked.CompareExchangeensures dispose is safe under concurrent access
Roslyn Analyzer (CY0001-CY0004)
The CyTypes.Analyzer package provides compile-time security diagnostics:
| ID | Severity | Title | Description |
|---|---|---|---|
| CY0001 | Warning | ToInsecureValue() called outside [InsecureAccess] context |
Decryption calls should be wrapped in a method marked with [InsecureAccess] to document intentional exposure |
| CY0002 | Warning | CyType used in string interpolation | String interpolation may leak security metadata — use explicit formatting |
| CY0003 | Error | Explicit cast from CyType discards security tracking | Casting (int)myCyInt discards security state — value is compromised silently |
| CY0004 | Warning | CyType not disposed | CyType instances hold sensitive memory and should use using or explicit Dispose() |
Examples
// CY0001 — Warning: ToInsecureValue() outside [InsecureAccess]
int value = myCyInt.ToInsecureInt(); // ⚠️ CY0001
[InsecureAccess]
int GetValue(CyInt cy) => cy.ToInsecureInt(); // ✅ OK
// CY0002 — Warning: CyType in string interpolation
Console.WriteLine($"Value: {myCyInt}"); // ⚠️ CY0002 — leaks metadata
Console.WriteLine("Value: " + myCyInt.ToInsecureInt()); // ✅ Explicit
// CY0003 — Error: Explicit cast discards tracking
int bad = (int)myCyInt; // ❌ CY0003 — silent compromise
// CY0004 — Warning: CyType not disposed
var cy = new CyInt(42); // ⚠️ CY0004 — no using/Dispose
using var ok = new CyInt(42); // ✅ OK
FHE — Fully Homomorphic Encryption
The CyTypes.Fhe package provides an initial FHE implementation using Microsoft SEAL with the BFV scheme for integer arithmetic on ciphertexts.
Current Status
- BFV scheme: Integer addition, subtraction, multiplication, and negation on encrypted data — without decryption
- CKKS scheme: Not yet supported (approximate arithmetic for floating-point)
- Limitations: Homomorphic comparisons and string operations are not implemented. The Core
SecurityPolicyBuilderdoes not yet accept FHE modes — direct use of the Fhe API is required
API
using CyTypes.Fhe.Crypto;
using CyTypes.Fhe.KeyManagement;
using Microsoft.Research.SEAL;
// 1. Initialize key manager
using var keyManager = new SealKeyManager();
var parms = new EncryptionParameters(SchemeType.BFV);
// Configure poly_modulus_degree, coeff_modulus, plain_modulus...
keyManager.Initialize(FheScheme.BFV, parms);
// 2. Create engine
using var engine = new SealBfvEngine(keyManager);
// 3. Encrypt values
byte[] encA = engine.Encrypt(42);
byte[] encB = engine.Encrypt(17);
// 4. Arithmetic on ciphertexts — no decryption needed
byte[] encSum = engine.Add(encA, encB); // 42 + 17 = 59
byte[] encDiff = engine.Subtract(encA, encB); // 42 - 17 = 25
byte[] encProd = engine.Multiply(encA, encB); // 42 * 17 = 714
byte[] encNeg = engine.Negate(encA); // -42
// 5. Decrypt result
long sum = engine.Decrypt(encSum); // 59
// 6. Monitor noise budget (decreases with operations)
int budget = engine.GetNoiseBudget(encProd);
// When budget reaches 0, decryption will fail
Key Management
// Export keys for storage
SealKeyBundle bundle = keyManager.ExportKeyBundle();
// Properties
bool ready = keyManager.IsInitialized;
SEALContext ctx = keyManager.Context;
PublicKey pk = keyManager.PublicKey;
SecretKey sk = keyManager.SecretKey;
RelinKeys rlk = keyManager.RelinKeys;
Benchmarks
The tests/CyTypes.Benchmarks project measures the overhead of encrypted operations vs native .NET using BenchmarkDotNet.
What Is Measured
CyIntBenchmarks:
| Benchmark | Description |
|---|---|
Add |
Addition of two CyInt values (decrypt + add + re-encrypt) |
Multiply |
Multiplication of two CyInt values |
Roundtrip |
Create CyInt(123) → ToInsecureInt() → create new CyInt |
NativeAdd |
Baseline: native int addition (42 + 17) |
CyStringBenchmarks:
| Benchmark | Description |
|---|---|
Concat |
Concatenation of two CyString values |
Split |
Split a CyString by comma separator |
Roundtrip |
Create CyString → ToInsecureString() → create new CyString |
SecureEquals |
Constant-time HMAC-based string comparison |
Running Benchmarks
dotnet run --project tests/CyTypes.Benchmarks -c Release
BenchmarkDotNet outputs a table with Mean, Error, StdDev, and Allocated columns for each benchmark. The NativeAdd baseline shows the cost of native int arithmetic for comparison.
Performance Guidance
- Maximum policy: Highest overhead — checked arithmetic, strict taint propagation, low decryption limit. Use for secrets (API keys, SSNs, passwords).
- Balanced policy: Moderate overhead — standard taint, 100 decryptions. Good default for most sensitive data.
- Performance policy: Lowest overhead — relaxed taint, unlimited decryptions. Use for bulk data that needs encryption but not strict auditing.
Cryptographic Primitives
| Component | Algorithm | Details |
|---|---|---|
| Encryption | AES-256-GCM | 12-byte random nonce, 16-byte auth tag |
| Key Derivation | HKDF-SHA512 | Contextual info for key diversification |
| Secure Comparison | HMAC-SHA512 | FixedTimeEquals — immune to timing attacks |
| Nonce Generation | RandomNumberGenerator.Fill() |
CSPRNG per encryption |
Project Structure
src/
├── CyTypes.Core # Crypto engine, key management, memory, policy, audit
├── CyTypes.Primitives # CyInt, CyString, CyBool, CyBytes, etc.
├── CyTypes.Analyzer # Roslyn compile-time security checks (CY0001-CY0004)
├── CyTypes.Collections # Encrypted collections (CyList, CyDictionary)
├── CyTypes.Logging # Auto-redacting logger
├── CyTypes.EntityFramework # EF Core value converters
├── CyTypes.DependencyInjection # IServiceCollection extensions
└── CyTypes.Fhe # Fully Homomorphic Encryption (Microsoft SEAL)
tests/
├── CyTypes.Core.Tests
├── CyTypes.Primitives.Tests
├── CyTypes.Collections.Tests
├── CyTypes.Analyzer.Tests
├── CyTypes.Logging.Tests
├── CyTypes.EntityFramework.Tests
├── CyTypes.DependencyInjection.Tests
├── CyTypes.Fhe.Tests
└── CyTypes.Benchmarks
Building & Testing
# Restore and build
dotnet build cyTypes.sln
# Run all tests
dotnet test cyTypes.sln
# Run with coverage
dotnet test cyTypes.sln --collect:"XPlat Code Coverage"
# Run benchmarks (Release mode required for accurate results)
dotnet run --project tests/CyTypes.Benchmarks -c Release
Roadmap
| Phase | Feature | Status |
|---|---|---|
| 1 | Core crypto, primitives, taint, policies | Complete |
| 2 | Roslyn analyzer, secure collections, auto-redacting logging, EF Core | Complete |
| 3a | FHE — Microsoft SEAL BFV integration (CyTypes.Fhe package) |
In Progress (~60%) |
| 3b | FHE — CKKS support, comparison/string operations on ciphertexts | Planned |
| 3c | PQC — ML-KEM-1024 key encapsulation (stub present, integration pending) | In Progress (~90%) |
Phase 3 status: The
CyTypes.Fhepackage exists with a workingSealBfvEnginefor integer arithmetic on BFV ciphertexts, butSecurityPolicyBuilderstill rejects FHE modes — direct use of the Fhe package API is required. CKKS (approximate arithmetic) and homomorphic comparisons/string operations are not yet implemented. ML-KEM-1024 key encapsulation (MlKemKeyEncapsulation) is registered via DI but not yet wired into the encryption pipeline. Phase 3 focuses on completing these integrations for production use.
Contributing
Contributions are welcome. Please read SECURITY.md for the vulnerability disclosure policy before reporting security issues.
For general contributions:
- Fork the repository
- Create a feature branch
- Ensure all tests pass:
dotnet test cyTypes.sln - Submit a pull request
License
MIT — Copyright 2026 Matteo Sala (cysalazar@cysalazar.com)
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net9.0
- CyTypes.Primitives (>= 1.0.0)
- Microsoft.EntityFrameworkCore (>= 9.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on CyTypes.EntityFramework:
| Package | Downloads |
|---|---|
|
CyTypes
Always-encrypted .NET primitive types — single package with all components. Includes Core, Primitives, Collections, FHE (Microsoft SEAL), Streams, EF Core, DI, Logging, and Analyzer. |
GitHub repositories
This package is not used by any popular GitHub repositories.