PostQuantum.DataProtection 0.1.0-preview.4

This is a prerelease version of PostQuantum.DataProtection.
There is a newer prerelease version of this package available.
See the version list below for details.
dotnet add package PostQuantum.DataProtection --version 0.1.0-preview.4
                    
NuGet\Install-Package PostQuantum.DataProtection -Version 0.1.0-preview.4
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PostQuantum.DataProtection" Version="0.1.0-preview.4" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="PostQuantum.DataProtection" Version="0.1.0-preview.4" />
                    
Directory.Packages.props
<PackageReference Include="PostQuantum.DataProtection" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add PostQuantum.DataProtection --version 0.1.0-preview.4
                    
#r "nuget: PostQuantum.DataProtection, 0.1.0-preview.4"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package PostQuantum.DataProtection@0.1.0-preview.4
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=PostQuantum.DataProtection&version=0.1.0-preview.4&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=PostQuantum.DataProtection&version=0.1.0-preview.4&prerelease
                    
Install as a Cake Tool

PostQuantum.DataProtection

License: MIT Target NuGet FIPS 203 ci CodeQL

Post-quantum / hybrid key wrapping for ASP.NET Core Data Protection. One line in Program.cs and every persisted Data Protection key — cookie keys, antiforgery keys, session tickets, Blazor circuit tokens, every IDataProtector payload at rest — is wrapped under an ML-KEM-768 (FIPS 203) + AES-256-GCM hybrid envelope.

builder.Services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("keys"))
    .ProtectKeysWithPostQuantum(o => o.KeyStorePath = "keys/pq-keystore.txt");

That's it.


What you get

Six packages, one CLI, four end-to-end samples. Mix and match.

Package Purpose
PostQuantum.DataProtection The core. Encryptor / decryptor, key manager, file-backed key store, DI extensions, health check, scheduled rotation, retention/prune helper, metrics + tracing. Supports ML-KEM-512, ML-KEM-768, ML-KEM-1024 + HKDF and X-Wing combiners.
PostQuantum.DataProtection.AzureKeyVault IPostQuantumKeyStore backed by Azure Key Vault Secrets. One line: services.AddPostQuantumDataProtectionAzureKeyVault(vaultUri).
PostQuantum.DataProtection.Aws IPostQuantumKeyStore backed by AWS Secrets Manager. One line: services.AddPostQuantumDataProtectionAws().
PostQuantum.DataProtection.Redis IPostQuantumKeyStore backed by Redis. Natural pair with PersistKeysToStackExchangeRedis.
PostQuantum.DataProtection.OpenTelemetry One-line OTel wiring. .AddPostQuantumDataProtectionInstrumentation() on a MeterProviderBuilder / TracerProviderBuilder.
PostQuantum.DataProtection.Testing FakePostQuantumKeyStore + AddPostQuantumDataProtectionTesting() for consumer unit tests — no cloud, no disk.
PostQuantum.DataProtection.Cli (pq-dp) dotnet tool for inspecting persisted DP key XML files. No secrets emitted.

Samples:

Sample What it shows
AspNetCore.Sample Minimal-API host with cookie auth + antiforgery, both PQ-protected.
WorkerService.Sample Worker Service using PQ outside ASP.NET Core, with scheduled rotation.
Blazor.Sample Blazor Server with PQ-protected circuit + cookie + IDataProtector roundtrip.
MultiReplica.Sample Two simulated replicas sharing one Key Vault — proves the multi-replica shape end to end.

Why hybrid (and why ML-KEM-768)

  • Hybrid = belt-and-braces. The AES-256-GCM key that wraps each Data Protection element is HKDF-derived from both the ML-KEM shared secret and a classical secret from your IContentKeyProvider. An attacker has to defeat both layers to recover plaintext. A classically-broken passphrase still has ML-KEM in the way; a (hypothetical) quantum-broken ML-KEM still has the classical wrap in the way. This is the IETF hybrid-KEM pattern.
  • ML-KEM-768 is the general-purpose pick. NIST category 3 (≈ 192-bit classical strength) — the level NIST recommends for general use. We default there; switch to 512 or 1024 when the selectable parameter set lands (see KNOWN-GAPS.md §C1).
  • Verified against FIPS 203. Our integration is pinned by a NIST-aligned KAT vector: given the seed bytes from post-quantum-cryptography/KAT, BouncyCastle's ML-KEM-768 produces the exact pk (1184 bytes), sk (2400 bytes), and decapsulates the published ciphertext to the published shared secret — byte for byte. The test runs on every PR.

When to use this

Situation Verdict
You run ASP.NET Core, Blazor, or any .NET host with Data Protection. ✅ Yes
Your threat model includes "harvest-now, decrypt-later" against the key store. ✅ Especially
You want defense-in-depth without ripping out your existing Data Protection wiring. ✅ One line
You need a FIPS 140-3 validated module today. ❌ Roadmap — see §C6
You want PQ session-key negotiation over the wire. ❌ Different layer — TLS hybrid groups belong in the stack

Quick start

dotnet add package PostQuantum.DataProtection --prerelease
dotnet add package PostQuantum.KeyManagement --prerelease
using Microsoft.AspNetCore.DataProtection;
using PostQuantum.DataProtection.Hybrid;
using PostQuantum.KeyManagement;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

builder.Services.AddPostQuantumKeyManagement(options =>
{
    options.Passphrase = builder.Configuration["KeyManagement:Passphrase"]
        ?? throw new InvalidOperationException("Missing passphrase");
    options.WorkFactor = KekWorkFactor.Moderate;
    options.KeyringPath = "keys/host-keyring.bin";
});

builder.Services
    .AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo("keys/data-protection"))
    .ProtectKeysWithPostQuantum(options =>
    {
        options.KeyStorePath = "keys/pq-keystore.txt";
        options.Mode = HybridKemMode.Hybrid;
        options.RotationInterval = TimeSpan.FromDays(90);  // optional auto-rotation
    });

builder.Services.AddHealthChecks().AddPostQuantumDataProtection();

Configure from appsettings.json instead of code

{
  "PostQuantumDataProtection": {
    "KeyStorePath": "keys/pq-keystore.txt",
    "Mode": "Hybrid",
    "RotationInterval": "90.00:00:00"
  }
}
builder.Services
    .AddDataProtection()
    .ProtectKeysWithPostQuantum(builder.Configuration.GetSection("PostQuantumDataProtection"));

Replace the file store with Azure Key Vault

builder.Services.AddPostQuantumDataProtectionAzureKeyVault(new Uri("https://my-vault.vault.azure.net/"));

Replace the file store with AWS Secrets Manager

builder.Services.AddPostQuantumDataProtectionAws(o => o.Region = Amazon.RegionEndpoint.USEast1);

Wire OpenTelemetry in one line

builder.Services.AddOpenTelemetry()
    .WithMetrics(m => m.AddPostQuantumDataProtectionInstrumentation().AddPrometheusExporter())
    .WithTracing(t => t.AddPostQuantumDataProtectionInstrumentation().AddOtlpExporter());

Unit-test consumer code without standing up a real chain

[Fact]
public void My_service_protects_and_unprotects()
{
    var services = new ServiceCollection();
    services.AddPostQuantumDataProtectionTesting();
    using ServiceProvider sp = services.BuildServiceProvider();

    IDataProtector p = sp.GetRequiredService<IDataProtectionProvider>().CreateProtector("p");
    Assert.Equal("hi", p.Unprotect(p.Protect("hi")));
}

Inspect a persisted key file from the CLI

dotnet tool install --global PostQuantum.DataProtection.Cli --prerelease
pq-dp inspect keys/data-protection/key-c6b3b03f-b73a-477b-92e5-d19ae0e0b5fd.xml
Format version:      1
Mode:                Hybrid
KEM algorithm:       ML-KEM-768
Public key id:       pq-mlkem768-4411e03446f5
KEM ciphertext:      1088 bytes
Classical wrap:      236 chars
AES-GCM nonce:       12 bytes
AES-GCM tag:         16 bytes
AES-GCM ciphertext:  120 bytes

What lands on disk

<encryptedSecret decryptorType="PostQuantum.DataProtection.PostQuantumXmlDecryptor, …">
  <pqEnvelope xmlns="https://schemas.systemslibrarian.dev/pq-dataprotection/2026/01"
              version="1" mode="Hybrid" publicKeyId="pq-mlkem768-…">
    BASE64URL…
  </pqEnvelope>
</encryptedSecret>

The Base64Url blob is a versioned binary envelope: [FormatVersion | Mode | KemAlgorithm | PublicKeyId | KemCiphertext | ClassicalWrappedKey | Nonce | Tag | Ciphertext]. Every field is length-prefixed and capped at 1 MiB. Full byte layout in docs/wire-format.md.

Performance

Real numbers from BenchmarkDotNet on a modern x86_64 host (full table in docs/benchmarks.md):

Operation Mean
ML-KEM-768 encapsulate ~93 µs
ML-KEM-768 decapsulate ~101 µs
Full envelope encrypt (Hybrid) ~89 µs
Full envelope decrypt (Hybrid) ~137 µs

Envelope work happens at DP key persist / load — startup-path, not request-path. Cookie verification, antiforgery validation, and IDataProtector.Unprotect all read keys from the in-memory key ring; they never go through the envelope.

Observability

The library publishes a Meter and an ActivitySource named PostQuantum.DataProtection. Subscribe with OpenTelemetry (or any IMeterListener):

Signal What it tells you
pq_dataprotection.encryptions Rate of fresh DP keys being wrapped. Tagged by mode.
pq_dataprotection.decryptions Rate of envelope reads. Tagged by mode.
pq_dataprotection.decrypt_failures Tagged by reason: wrong_xml_element, malformed_envelope, unsupported_algorithm, unknown_keypair, auth_failed. Page on any non-zero rate.
pq_dataprotection.rotations Rate of PQ keypair rotations. Should be quiet outside scheduled windows.
pq_dataprotection.encrypt.duration / decrypt.duration Histograms in ms. P95 should sit < 1 ms on modern hardware.
AddPostQuantumDataProtection() health check Real roundtrip on every probe. Page if Unhealthy.

Threat model and security posture

docs/threat-model.md is the precise statement: attacker model (A1 → A6 vs. B1 → B4) and 10 numbered security invariants the library is designed to hold. Each invariant corresponds to one or more tests.

SECURITY.md covers reporting vulnerabilities (use GitHub Security Advisories, not public issues), supported versions, and the recommended deployment posture.

docs/deployment.md is the production operations checklist: pre-deploy verification, multi-replica model, KEK rotation playbook, disaster recovery matrix, monitoring signals.

Honest scope of "post-quantum"

The library wraps Data Protection keys at rest with a verified-against-FIPS-203 ML-KEM-768 + AES-256-GCM hybrid envelope. That is the entire claim. We do not claim to make ASP.NET Core's request pipeline post-quantum, to negotiate PQ session keys, or to be FIPS 140-3 validated today. See KNOWN-GAPS.md for the full breakdown of what's deliberate, what's roadmap, and what's been closed across previews.

Migrating from another IXmlEncryptor

Whether you're on ProtectKeysWithDpapiNG, ProtectKeysWithAzureKeyVault (the key wrap, not the secrets store), or ProtectKeysWithCertificate, the migration is non-disruptive — existing keys keep decrypting under their old decryptor type while fresh keys roll forward under PQ. Step by step in docs/migration.md.

Supply chain

  • Deterministic builds with CI-enforced reproducibility (the CI repacks twice and asserts byte-identical .nupkg SHA-256).
  • SourceLink + symbol packages so debuggers fetch the exact GitHub source for every commit you ship.
  • EnablePackageValidation catches API surface drift between framework targets.
  • TreatWarningsAsErrors plus latest-recommended analyzers — zero-warning policy across the repo.
  • Pinned transitive overrides for known-vulnerable packages (e.g. System.Security.Cryptography.Xml pinned to the patched 8.0.3 to avoid GHSA-37gx-xxp4-5rgx and GHSA-w3x6-4m5h-cxqf).
  • SBOM-friendly metadata. Every dependency is an explicit <PackageReference>. Recipes for CycloneDX and the Microsoft SBOM tool in docs/supply-chain.md.

To verify a published package:

sourcelink test PostQuantum.DataProtection.<version>.nupkg
# Reproduce the build from the matching commit and compare SHA-256:
git checkout v<version>
dotnet pack -c Release -o /tmp/local
sha256sum /tmp/local/PostQuantum.DataProtection.<version>.nupkg

Testing

dotnet build PostQuantum.DataProtection.slnx -c Release
dotnet test PostQuantum.DataProtection.slnx -c Release --no-build

87 tests across four suites — core, AzureKeyVault, Aws, Testing. Coverage gate at ≥ 85% line / ≥ 75% branch (current 89.5% line / 78.6% branch). Property-based fuzz-lite contract tests drive 30 000 random inputs through both decoders on every run; a standalone SharpFuzz harness in fuzz/ is set up for AFL-driven exploration.

Requirements

  • .NET 8.0, 9.0, or 10.0 (multi-targeted).
  • A registered PostQuantum.KeyManagement IContentKeyProvider for the classical KEK layer.

Project status

0.1.0-preview.4. The API surface and the binary envelope wire format are versioned and backward-compatible across the preview series (every envelope written by preview.1 decodes under preview.4). Pre-1.0 the wire format may still change with a deliberate version bump and a CHANGELOG.md note. The path to 1.0 is mapped in future.md.

The two remaining gates on 1.0 are calendar-time, not code-time: third-party cryptographic review, and at least one cloud-backed key store in real production use. Both are tracked in KNOWN-GAPS.md §D.

License

MIT © 2026 Paul Clark.


To God be the glory — 1 Corinthians 10:31.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  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 is compatible.  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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (6)

Showing the top 5 NuGet packages that depend on PostQuantum.DataProtection:

Package Downloads
PostQuantum.DataProtection.Testing

Test fakes for PostQuantum.DataProtection. Provides an in-memory IPostQuantumKeyStore (FakePostQuantumKeyStore) so consumer projects can write unit tests against their own code without standing up a real ML-KEM keypair, a real classical KEK, or a real file on disk. One-line wiring: services.AddPostQuantumDataProtectionTesting().

PostQuantum.DataProtection.Aws

AWS Secrets Manager-backed IPostQuantumKeyStore for PostQuantum.DataProtection. Stores ML-KEM-768 keypairs as Secrets Manager secrets (one secret per keypair plus a small "active" pointer secret). The PQ secret key is already envelope-encrypted by the host IContentKeyProvider before it touches AWS, so AWS sees only opaque bytes — the service is a durable persistence target, not a new trust boundary. One-line wiring: services.AddPostQuantumDataProtectionAws(...). Multi-targets net8.0 / net9.0 / net10.0 with deterministic builds, SourceLink, and symbol packages.

PostQuantum.DataProtection.Redis

StackExchange.Redis-backed IPostQuantumKeyStore for PostQuantum.DataProtection. Stores ML-KEM keypairs in a Redis hash and the active key id in a single pointer key. Suitable for hosts that already use Redis for ASP.NET Core Data Protection key persistence (PersistKeysToStackExchangeRedis). One-line wiring: services.AddPostQuantumDataProtectionRedis(connectionString).

PostQuantum.DataProtection.AzureKeyVault

Azure Key Vault-backed IPostQuantumKeyStore for PostQuantum.DataProtection. Stores the ML-KEM-768 keypairs that wrap ASP.NET Core Data Protection keys in an Azure Key Vault Secrets vault — durable, audited, and shareable across replicas — without ever placing plaintext on disk. The PQ secret key is wrapped by the host IContentKeyProvider before it reaches Key Vault, so Key Vault sees only an opaque blob; the vault is a durable persistence target, not a new trust boundary. One-line wiring: services.AddPostQuantumDataProtectionAzureKeyVault(vaultUri). Multi-targets net8.0 / net9.0 / net10.0 with deterministic builds, SourceLink, and symbol packages.

PostQuantum.DataProtection.OpenTelemetry

OpenTelemetry instrumentation helper for PostQuantum.DataProtection. One-line wiring: builder.AddPostQuantumDataProtectionInstrumentation() on a MeterProviderBuilder and a TracerProviderBuilder, and the PQ data-protection counters / histograms / activities flow into whatever exporter the host configures (Prometheus, OTLP, Console, etc.).

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0-preview.7 83 6/4/2026
0.1.0-preview.6 82 6/4/2026
0.1.0-preview.5 82 6/4/2026
0.1.0-preview.4 73 6/4/2026

0.1.0-preview.4: Adoption-focused pass on top of preview.3. Backward-compatible. (1) ILogger<T> integration with structured EventIds across encryptor, decryptor, key manager, and the new rotation hosted service. (2) Actionable error messages naming the offending option, the file path, and the fix. (3) IConfigurationSection overload for ProtectKeysWithPostQuantum(...) — appsettings.json-only wiring. (4) PostQuantumRotationHostedService driven by PostQuantumDataProtectionOptions.RotationInterval; TimeSpan.Zero disables, any positive value enables. (5) New companion packages: PostQuantum.DataProtection.Aws (AWS Secrets Manager keystore), PostQuantum.DataProtection.Testing (FakePostQuantumKeyStore + AddPostQuantumDataProtectionTesting() for consumer unit tests), PostQuantum.DataProtection.OpenTelemetry (one-line AddPostQuantumDataProtectionInstrumentation()). (6) Companion CLI: PostQuantum.DataProtection.Cli ("pq-dp") — pq-dp inspect <key.xml> prints envelope routing fields, no secret material. (7) Migration guide for DPAPI / Azure Key Vault key / Certificate -> PQ wrap without disruption. (8) Community files: CONTRIBUTING.md, CODE_OF_CONDUCT.md, GitHub issue templates, PR template, dependabot.yml. (9) DocFX site + GitHub Pages workflow. (10) WorkerService sample demonstrating non-ASP.NET-Core hosting. Carries forward from preview.3: NIST ACVP / FIPS 203 KAT, Meter + ActivitySource telemetry, concurrency stress tests, coverage CI gate, reproducible-build verification, BenchmarkDotNet harness, AOT honesty doc, fuzz harness. — earlier 0.1.0-preview.3: Production-readiness pass on top of preview.2. Backward-compatible — every envelope and keystore file written by preview.1/preview.2 still decodes byte-for-byte. No crypto-logic changes (ML-KEM-768 encapsulation, HKDF-SHA-256 derivation, AES-256-GCM are byte-for-byte identical). (1) NIST ACVP / FIPS 203 KAT (AcvpKatTests): pins BouncyCastle's ML-KEM-768 implementation against a real NIST-aligned vector from the post-quantum-cryptography/KAT repository — d || z seed → expected pk (1184 bytes), expected sk (2400 bytes), decapsulate the NIST ct → expected ss (32 bytes). This is FIPS 203 standard-conformance verification, not "our integration is self-consistent" verification. (2) System.Diagnostics.Metrics integration: Meter("PostQuantum.DataProtection") with counters (encryptions/decryptions/decrypt_failures/rotations), duration histograms, and an ActivitySource for distributed tracing. (3) Concurrency stress tests: 16-thread Parallel.ForEachAsync exercise of the encryptor / decryptor / RotateAsync — the documented "thread-safe" claim is now a tested claim. Plus a "first-run race" test that proves N concurrent first-callers see exactly one inaugural keypair. (4) 89.5% line / 78.6% branch code coverage measured with coverlet; CI gates at 85% line / 75% branch. (5) Reproducible-build verification: CI repacks and asserts byte-identical .nupkg SHA-256 across two independent packs. (6) AOT/trimming audit (docs/aot.md): not AOT-compatible today because BouncyCastle 2.6.x uses runtime reflection — documented honestly, IsTrimmable=false set explicitly. (7) BenchmarkDotNet harness (benchmarks/) with real numbers (docs/benchmarks.md): ML-KEM-768 keygen ~75µs, encap ~93µs, decap ~101µs; full envelope encrypt ~89µs, decrypt ~137µs on the reference host. (8) Production deployment guide (docs/deployment.md): pre-deploy checklist, multi-replica model, KEK rotation playbook, disaster recovery matrix, monitoring signal list. (9) Property-based "fuzz-lite" tests: 30 000 randomly-generated byte arrays driven through HybridKemEnvelope.Decode and PostQuantumKeyPair.Decode on every CI run, asserting the documented exception-type contract. Companion SharpFuzz harness in fuzz/ for AFL-driven exploration. Companion package: PostQuantum.DataProtection.AzureKeyVault 0.1.0-preview.3 — Azure Key Vault-backed IPostQuantumKeyStore with DefaultAzureCredential, narrow seam-tested implementation, in-memory fake for unit tests. 87 tests passing in the core suite (+ 6 in the AKV suite + 3 NIST ACVP KATs).