{"id":50486814,"url":"https://github.com/systemslibrarian/postquantum-file-encryption","last_synced_at":"2026-07-02T17:01:11.554Z","repository":{"id":361469637,"uuid":"1254533772","full_name":"systemslibrarian/postquantum-file-encryption","owner":"systemslibrarian","description":"Simple, high-security post-quantum file encryption library for .NET 10. Built on PostQuantum.FileFormat with passphrase and hybrid support.","archived":false,"fork":false,"pushed_at":"2026-06-23T10:25:43.000Z","size":473,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T12:41:03.676Z","etag":null,"topics":["aes-gcm","age-alternative","encryption","file-at-rest","file-encryption","hybrid-cryptography","net10","post-quantum","postquantum","pqc","secure-files"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/systemslibrarian.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":"audit-scripts/ADVERSARIAL-AUDIT.md","citation":null,"codeowners":null,"security":"SECURITY.md","support":"SUPPORT.md","governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T17:29:23.000Z","updated_at":"2026-06-23T10:25:47.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/systemslibrarian/postquantum-file-encryption","commit_stats":null,"previous_names":["systemslibrarian/postquantum-file-encryption"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/systemslibrarian/postquantum-file-encryption","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fpostquantum-file-encryption","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fpostquantum-file-encryption/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fpostquantum-file-encryption/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fpostquantum-file-encryption/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/systemslibrarian","download_url":"https://codeload.github.com/systemslibrarian/postquantum-file-encryption/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/systemslibrarian%2Fpostquantum-file-encryption/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35055546,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-02T02:00:06.368Z","response_time":173,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aes-gcm","age-alternative","encryption","file-at-rest","file-encryption","hybrid-cryptography","net10","post-quantum","postquantum","pqc","secure-files"],"created_at":"2026-06-01T23:02:30.875Z","updated_at":"2026-07-02T17:01:11.545Z","avatar_url":"https://github.com/systemslibrarian.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PostQuantum.FileEncryption\n\n\u003c!-- Badge images are served via img.shields.io so they render on nuget.org,\n     which only displays images from an allow-list of trusted domains. --\u003e\n[![CI](https://img.shields.io/github/actions/workflow/status/systemslibrarian/postquantum-file-encryption/ci.yml?branch=main\u0026label=CI)](https://github.com/systemslibrarian/postquantum-file-encryption/actions/workflows/ci.yml)\n[![CodeQL](https://img.shields.io/github/actions/workflow/status/systemslibrarian/postquantum-file-encryption/codeql.yml?branch=main\u0026label=CodeQL)](https://github.com/systemslibrarian/postquantum-file-encryption/actions/workflows/codeql.yml)\n[![OpenSSF Scorecard](https://img.shields.io/ossf-scorecard/github.com/systemslibrarian/postquantum-file-encryption?label=openssf%20scorecard)](https://securityscorecards.dev/viewer/?uri=github.com/systemslibrarian/postquantum-file-encryption)\n[![codecov](https://img.shields.io/codecov/c/github/systemslibrarian/postquantum-file-encryption/main?label=codecov)](https://codecov.io/gh/systemslibrarian/postquantum-file-encryption)\n[![NuGet](https://img.shields.io/nuget/v/PostQuantum.FileEncryption.svg)](https://www.nuget.org/packages/PostQuantum.FileEncryption/)\n[![NuGet Hybrid](https://img.shields.io/nuget/v/PostQuantum.FileEncryption.Hybrid.svg?label=nuget%20hybrid)](https://www.nuget.org/packages/PostQuantum.FileEncryption.Hybrid/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/LICENSE)\n\n**Open-source (MIT), fail-closed file and stream encryption for .NET 8 and .NET 10 —\nconstant-memory streaming for files of any size, a frozen and publicly specified container\nformat, and a production post-quantum upgrade path.**\n\nTwo friendly classes — `PqFileEncryptor` and `PqFileDecryptor` — handle authenticated,\nchunked, streaming encryption with strong, modern defaults. A 10 GB backup encrypts in\nroughly 130 KB of working memory, stream-to-stream or file-to-file. You should not have to\nread a cryptographic spec to protect a file: call a method, and the library does the\ncareful, paranoid, fail-closed thing every time. And because the code, the format\nspecification, the test vectors, the threat model, and the gaps ledger are all public,\nyou never have to take that on faith.\n\n\u003e **Status: `1.5.0` — stable release.**\n\u003e The symmetric, passphrase-based engine is production-ready and the `.pqfe` v2 container\n\u003e format is **FROZEN for the `1.x` line**. The companion **`PostQuantum.FileEncryption.Hybrid`**\n\u003e package provides production X25519 + ML-KEM-768 hybrid public-key encryption with\n\u003e multi-recipient support. The inline ML-KEM-only recipient mode in the core is **deprecated\n\u003e (`PQFE002`)** — see [Post-quantum \u0026 the upgrade path](#post-quantum--the-upgrade-path).\n\n---\n\n## Why this library\n\n- **Production-ready core.** Authenticated AES-256-GCM with constant-memory chunked\n  streaming — files of any size, multi-gigabyte included, in ~130 KB of working memory —\n  plus atomic file output, cancellation, progress, and zeroable secrets. 150 tests on both\n  target frameworks, continuous fuzzing, byte-compatible Rust/WASM reference, native-AOT\n  smoke-tested in CI.\n- **Frozen format.** `.pqfe` v2 is pinned by [cross-checked known-answer\n  vectors](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/TEST-VECTORS.md) and a [conformance specification](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/CONFORMANCE.md). A\n  file you encrypt today opens with every `1.x` build, on every platform, in either\n  implementation.\n- **Locked public API.** `Microsoft.CodeAnalysis.PublicApiAnalyzers` baselines every public\n  member; `\u003cEnablePackageValidation\u003e` checks binary compatibility against the previous\n  release at pack time. Accidental breakage fails the build.\n- **Honest supply chain.** Every release artifact ships with a [CycloneDX\n  SBOM](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SUPPLY-CHAIN.md), a [SLSA-style build-provenance\n  attestation](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SUPPLY-CHAIN.md#verify-build-provenance-attestation), and SourceLink. The\n  release workflow runs `Meziantou.Framework.NuGetPackageValidation` against every `.nupkg`\n  before publish.\n- **Honest about limits.** The [Known Gaps](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/KNOWN-GAPS.md) ledger lists everything that is\n  not yet done. The library has not been independently audited; engagements are welcome.\n\n## When to use this\n\n- You're on **.NET 8 or .NET 10** and want a drop-in, fail-closed file/stream encryptor\n  with excellent defaults and no FFI.\n- You stream **large files** — backups, media, exports — and need encryption that runs in\n  constant memory regardless of size, with progress and cancellation.\n- You want your cryptography **open**: MIT-licensed code, a published format specification,\n  cross-implementation test vectors, and a public threat model — not a proprietary binary\n  with a license-activation call.\n- You need **post-quantum data confidentiality today** (AES-256 against a harvest-now-\n  decrypt-later adversary) and a clear path to **post-quantum public-key encryption** via\n  the Hybrid package.\n- You want **enterprise affordances**: telemetry, atomic output, a documented format with\n  test vectors, a published threat model, signed releases, and a locked API.\n- You want a [comparison vs. `age`, libsodium, and OpenSSL](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/COMPARISON.md) before\n  committing.\n\nFor a side-by-side with other encryption libraries and migration guidance, see\n[docs/MIGRATION.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/MIGRATION.md).\n\n### Not the right tool if\n\nBeing clear about scope is part of the security contract. Reach for something else when:\n\n- You need **full-disk or volume encryption** — use BitLocker, FileVault, LUKS, or VeraCrypt.\n- You need to **hide metadata** — file names, paths, sizes, and timestamps are *not* protected;\n  plaintext length is revealed to within a chunk (see [KNOWN-GAPS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/KNOWN-GAPS.md)).\n- You want **key management at rest** (generation, storage, rotation of long-lived keys) — this\n  library encrypts data; pair it with a KMS/HSM via `IContentKeyProvider`.\n- You expect **a standardized, cross-tool container** — `.pqfe` is its own\n  [documented format](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/FILE-FORMAT.md), not PGP, CMS, age, or JOSE, and will not open in\n  those tools.\n- You need **binary content typing, compression, or de-duplication** — all out of scope by design.\n\n---\n\n## Install\n\n```bash\n# Core (passphrase + envelope-key engine)\ndotnet add package PostQuantum.FileEncryption --version 1.5.0\n\n# Add this only if you need public-key (recipient) encryption\ndotnet add package PostQuantum.FileEncryption.Hybrid --version 1.5.0\n\n# Optional: detached Ed25519 + ML-DSA-65 signatures (sender authenticity)\ndotnet add package PostQuantum.FileEncryption.Signing --version 1.5.0\n\n# Optional: cloud envelope-key providers (the master key stays in your KMS/HSM)\ndotnet add package PostQuantum.FileEncryption.Aws            # AWS KMS\ndotnet add package PostQuantum.FileEncryption.AzureKeyVault  # Azure Key Vault / Managed HSM\n\n# Optional: Microsoft.Extensions.DependencyInjection integration\n# (AddPqFileEncryption() / AddPqHybridFileEncryption())\ndotnet add package PostQuantum.FileEncryption.Extensions.DependencyInjection --version 1.5.0\n\n# Recommended: compile-time misuse checks (hard-coded passphrases, raw keys on disk,\n# discarded tasks, swallowed fail-closed exceptions). Development-only dependency.\ndotnet add package PostQuantum.FileEncryption.Analyzers --version 1.5.0\n```\n\nTargets **.NET 8 and .NET 10** (`net8.0`; `net10.0`), with an identical public API on\nboth. Core depends only on\n`Konscious.Security.Cryptography.Argon2` (and only when you select Argon2id); everything\nelse is from .NET's `System.Security.Cryptography`. The Hybrid package additionally pulls in\n`BouncyCastle.Cryptography` so it runs on every platform without a native ML-KEM dependency.\n\n---\n\n## ▶ Try it\n\nThree ways to drive the library — all produce the same `.pqfe` format:\n\n### 1. Command-line — install the `pqfe` dotnet tool\n\nNo code required:\n\n```bash\ndotnet tool install -g PostQuantum.FileEncryption.Tool\n\npqfe encrypt report.pdf report.pdf.pqfe --argon2id     # prompts for a passphrase\npqfe decrypt report.pdf.pqfe report.pdf\n\npqfe keygen me.key                                     # Ed25519 + ML-DSA-65 signing key pair\npqfe sign   report.pdf.pqfe me.key                     # writes report.pdf.pqfe.sig\npqfe verify report.pdf.pqfe me.key.pub                 # exit 0 = authentic, 65 = reject\n```\n\nThe source lives at [`samples/Pqfe.Cli`](https://github.com/systemslibrarian/postquantum-file-encryption/tree/main/samples/Pqfe.Cli) and is built on the public\nAPI. It's also the canary that proves `IsAotCompatible=true` end-to-end: CI publishes it\nwith `PublishAot=true` and round-trips a real file as the smoke test.\n\n```bash\n# Run from source via dotnet:\nPQFE_PASS='correct horse battery staple' \\\n  dotnet run -c Release --project samples/Pqfe.Cli -- \\\n  encrypt report.pdf report.pdf.pqfe --argon2id --passphrase-env PQFE_PASS\n\n# Or publish a single-file native binary:\ndotnet publish samples/Pqfe.Cli -c Release -p:PublishAot=true -o ./bin\n./bin/pqfe --help\n```\n\n### 2. Browser demo — fully client-side (Rust → WebAssembly)\n\n[`samples/pqfe-web`](https://github.com/systemslibrarian/postquantum-file-encryption/tree/main/samples/pqfe-web) is a static page whose **file never leaves your\nbrowser**: a small Rust core compiled to WebAssembly does passphrase-based AES-256-GCM\nlocally. It's hostable on **GitHub Pages** with no server (see the\n[Pages workflow](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/.github/workflows/pages.yml)).\n\n```bash\ncd samples/pqfe-wasm\nrustup target add wasm32-unknown-unknown\nwasm-pack build --target web --release --out-dir ../pqfe-web/pkg\ncd ../pqfe-web \u0026\u0026 python3 -m http.server 8080   # open http://localhost:8080\n```\n\nThis Rust core is an independent re-implementation of the format, kept **byte-compatible**\nwith the .NET library: the Rust tests decrypt the .NET known-answer vectors, and the .NET\ntests decrypt a Rust-produced container (`CrossImplementationTests`). A file encrypted in\nthe browser opens with the library, and vice versa.\n\n### 3. .NET demo — runs the real library (Blazor Server)\n\n[`samples/PostQuantum.FileEncryption.Demo`](https://github.com/systemslibrarian/postquantum-file-encryption/tree/main/samples/PostQuantum.FileEncryption.Demo)\nexercises the actual library through a web UI. Files are processed **in memory and never\nwritten to disk**.\n\n```bash\ndotnet run --project samples/PostQuantum.FileEncryption.Demo\n# then open the printed http://localhost:\u003cport\u003e URL\n```\n\nIt's a **Blazor Server** app on purpose: .NET's `AesGcm` is unsupported in browser\nWebAssembly, so the cryptography runs on the server runtime. (The browser demo above\nsidesteps this with the Rust/WASM core.)\n\n---\n\n## Public API at a glance\n\nThe surface is small on purpose — these are the types you actually touch:\n\n| Type | Package | What it does |\n| --- | --- | --- |\n| `PqFileEncryptor` | core | Encrypts files, streams, and bytes with a passphrase or an envelope-key provider. |\n| `PqFileDecryptor` | core | Fail-closed decryption, including `DecryptAtomicAsync` (all-or-nothing streams). |\n| `PqEncryptionOptions` | core | Immutable options (KDF choice, work factor, chunk size) with `WithArgon2id` / `WithPbkdf2` / `WithChunkSize`. |\n| `PqProgress` | core | Progress reporting payload for `IProgress\u003cPqProgress\u003e`. |\n| `IContentKeyProvider` / `LocalKekContentKeyProvider` | core | Envelope encryption seam (KMS/HSM) and a built-in local-KEK implementation. |\n| `PqDecryptionException` / `PqFormatException` | core | Fail-closed signals — generic by design, never a decryption oracle. |\n| `PqHybridEncryptor` / `PqHybridDecryptor` | Hybrid | X25519 + ML-KEM-768 public-key encryption, single- or multi-recipient. |\n| `PqHybridKeyPair` / `PqHybridPublicKey` / `PqHybridPrivateKey` | Hybrid | Hybrid recipient key pair; `Export()` / `Import()` for storage and transport. |\n| `AddPqFileEncryption()` / `AddPqHybridFileEncryption()` | DI Extensions | `IServiceCollection` registration for the encryptor/decryptor pairs. |\n\nEvery member is XML-documented; the generated reference lives under [Documentation](#documentation).\n\n---\n\n## Usage\n\n### Quick start — encrypt some bytes in memory\n\n```csharp\nusing PostQuantum.FileEncryption;\n\nbyte[] secret    = \"meet me at dawn\"u8.ToArray();\nbyte[] container = await new PqFileEncryptor().EncryptBytesAsync(secret, \"correct horse battery staple\");\nbyte[] recovered = await new PqFileDecryptor().DecryptBytesAsync(container, \"correct horse battery staple\");\n// recovered.SequenceEqual(secret) == true\n```\n\nThat's the whole happy path. Everything below is the same idea for files, streams, and\noptions.\n\n### Encrypt and decrypt a file with a passphrase\n\n```csharp\nusing PostQuantum.FileEncryption;\n\nawait new PqFileEncryptor().EncryptFileAsync(\"report.pdf\", \"report.pdf.pqfe\", \"correct horse battery staple\");\nawait new PqFileDecryptor().DecryptFileAsync(\"report.pdf.pqfe\", \"report.restored.pdf\", \"correct horse battery staple\");\n```\n\n### Use Argon2id instead of PBKDF2\n\n```csharp\n// Quickest — preset with OWASP-recommended defaults:\nawait new PqFileEncryptor(PqEncryptionOptions.Argon2id)\n    .EncryptFileAsync(\"in\", \"out.pqfe\", passphrase);\n\n// Or tune the work factor (returns a new options instance — leave the others as-is):\nvar stronger = PqEncryptionOptions.Default.WithArgon2id(memoryKiB: 64 * 1024);\nawait new PqFileEncryptor(stronger).EncryptFileAsync(\"in\", \"out.pqfe\", passphrase);\n\n// Decryption needs no options — the KDF and its parameters travel in the container header.\nawait new PqFileDecryptor().DecryptFileAsync(\"out.pqfe\", \"in.copy\", passphrase);\n```\n\n`PqEncryptionOptions` is immutable; `WithArgon2id`, `WithPbkdf2`, and `WithChunkSize` each\nreturn a new instance with the requested change and the rest carried through, so you can\ncompose them without re-stating every field.\n\n### Public-key encryption — use `PostQuantum.FileEncryption.Hybrid`\n\n```csharp\nusing PostQuantum.FileEncryption.Hybrid;\n\n// Recipient generates a key pair once:\nusing var keyPair = PqHybridKeyPair.Generate();\nbyte[] publish = keyPair.PublicKey.Export();   // share this freely\n\n// Sender encrypts to the public key — X25519 + ML-KEM-768 combined:\nvar recipient = PqHybridPublicKey.Import(publish);\nbyte[] container = await new PqHybridEncryptor().EncryptBytesAsync(secret, recipient);\n\n// Only the holder of the private key can decrypt:\nbyte[] plaintext = await new PqHybridDecryptor().DecryptBytesAsync(container, keyPair.PrivateKey);\n```\n\nThe Hybrid package supports **multiple recipients** in a single container and a **hybrid\ncombiner** that keeps the content key safe if either X25519 or ML-KEM is later broken. See\n[Post-quantum \u0026 the upgrade path](#post-quantum--the-upgrade-path) below.\n\n\u003e The inline ML-KEM-768-only recipient overloads on `PqFileEncryptor`/`PqFileDecryptor` in\n\u003e the core package are **deprecated (`PQFE002`)** and retained for source-compatibility\n\u003e only. Migrate to the Hybrid package shown above.\n\n### Detached signatures — use `PostQuantum.FileEncryption.Signing`\n\nEncryption proves a container wasn't altered; a signature proves **who produced it**. The\nSigning package signs any file or stream with **Ed25519 + ML-DSA-65 (FIPS 204) together**\nand writes a small detached `.sig` sidecar — unforgeable even if either algorithm is later\nbroken, and constant-memory for files of any size (streaming SHA-512 pre-hash).\n\n```csharp\nusing PostQuantum.FileEncryption.Signing;\n\nusing var keyPair = PqSigningKeyPair.Generate();\n\n// Sign the finished container; verification is fail-closed (throws on any mismatch):\nawait new PqSigner().SignFileAsync(\"report.pdf.pqfe\", \"report.pdf.pqfe.sig\", keyPair.PrivateKey);\nawait new PqVerifier().VerifyFileAsync(\"report.pdf.pqfe\", \"report.pdf.pqfe.sig\", keyPair.PublicKey);\n```\n\nThe sidecar format is versioned and byte-exactly specified in\n[docs/SIGNATURE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SIGNATURE-FORMAT.md);\nthe `.pqfe` v2 container format itself is unchanged and stays **FROZEN**.\n\n### Streams\n\n```csharp\nawait using var source = File.OpenRead(\"video.mp4\");\nawait using var sink   = File.Create(\"video.mp4.pqfe\");\nawait new PqFileEncryptor().EncryptAsync(source, sink, passphrase);\n```\n\n### Zeroable passphrase (bytes you control)\n\n```csharp\nbyte[] passphrase = GetPassphraseUtf8Bytes();\ntry\n{\n    await new PqFileEncryptor().EncryptFileAsync(\"in\", \"out.pqfe\", passphrase); // ReadOnlyMemory\u003cbyte\u003e overload\n}\nfinally\n{\n    System.Security.Cryptography.CryptographicOperations.ZeroMemory(passphrase);\n}\n```\n\n### Synchronous span overload (no async, stack-friendly)\n\n```csharp\n// Useful in CLIs and tight loops; the span is UTF-8 encoded into a temporary buffer\n// that is zeroed before this method returns. True sync code path — no deadlock surface.\nbyte[] container = new PqFileEncryptor().EncryptBytes(plaintext, \"correct horse battery staple\".AsSpan());\nbyte[] plaintext = new PqFileDecryptor().DecryptBytes(container, \"correct horse battery staple\".AsSpan());\n```\n\n### Report progress\n\n```csharp\nvar progress = new Progress\u003cPqProgress\u003e(p =\u003e\n    Console.WriteLine($\"{p.Fraction:P0} ({p.BytesProcessed:N0} bytes)\"));\nawait new PqFileEncryptor().EncryptFileAsync(\"big.iso\", \"big.iso.pqfe\", passphrase, progress);\n```\n\n### Handle failure (fail-closed)\n\n```csharp\ntry\n{\n    await new PqFileDecryptor().DecryptFileAsync(\"in.pqfe\", \"out.bin\", passphrase);\n}\ncatch (PqDecryptionException) { /* wrong key, or altered/corrupted/truncated — no output written */ }\ncatch (PqFormatException)     { /* not a PostQuantum.FileEncryption container at all */ }\n```\n\nEvery authentication failure raises the same generic `PqDecryptionException` with the same\nmessage — the library never tells an attacker *why* decryption failed, so it can never act\nas a decryption oracle.\n\n### All-or-nothing stream decryption\n\n```csharp\n// Writes to `output` only if the WHOLE container authenticates — nothing on a truncated input.\nawait new PqFileDecryptor().DecryptAtomicAsync(input, output, passphrase);\n```\n\nPrefer this (or the file API, which is atomic via temp-file-plus-rename) for stream input\nyou don't control: plain `DecryptAsync(Stream, Stream, …)` writes each chunk as it\nauthenticates, so a truncated container can emit an authentic plaintext *prefix* before the\nfailure is raised (see [KNOWN-GAPS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/KNOWN-GAPS.md)).\n\n### Decrypting untrusted input — cost ceilings\n\nA container's KDF cost and chunk size live in its header and are honored *before* anything\nauthenticates, so a hostile file could legally demand the format maximum (2 GiB of Argon2id\nmemory) from a few dozen bytes. If you open files from sources you don't control, cap it:\n\n```csharp\nvar decryptor = new PqFileDecryptor(PqDecryptionLimits.Untrusted); // or your own ceilings\n// Headers demanding more than the limits throw PqFormatException before any KDF work.\nawait decryptor.DecryptFileAsync(\"untrusted.pqfe\", \"out.bin\", passphrase);\n```\n\n`PqHybridDecryptor` takes the same limits (`new PqHybridDecryptor(PqDecryptionLimits.Untrusted)`);\non that path only the chunk-size ceiling applies, since key unwrap is a fixed-cost KEM\noperation. The defaults (`new PqFileDecryptor()` / `new PqHybridDecryptor()`) keep the\npermissive format maxima, so every legal container still opens.\n\n### Envelope encryption (KMS / HSM)\n\nEncrypt under an external key provider so the master key never enters your process. A\nbuilt-in, dependency-free local-KEK provider is included, and production cloud providers\nship as companion packages:\n[**PostQuantum.FileEncryption.Aws**](https://www.nuget.org/packages/PostQuantum.FileEncryption.Aws)\n(AWS KMS) and\n[**PostQuantum.FileEncryption.AzureKeyVault**](https://www.nuget.org/packages/PostQuantum.FileEncryption.AzureKeyVault)\n(Azure Key Vault / Managed HSM).\n\n```csharp\nusing var provider = LocalKekContentKeyProvider.Generate();   // or new(kek)...\nbyte[] container = await new PqFileEncryptor().EncryptBytesAsync(secret, provider);\nbyte[] plaintext = await new PqFileDecryptor().DecryptBytesAsync(container, provider);\n\n// ...or keep the master key in AWS KMS / Azure Key Vault — rotation re-wraps the small\n// content key; the multi-gigabyte payload is never re-encrypted:\nvar kmsProvider = new AwsKmsContentKeyProvider(new AmazonKeyManagementServiceClient(), \"alias/my-app-key\");\nawait new PqFileEncryptor().EncryptFileAsync(\"backup.tar\", \"backup.tar.pqfe\", kmsProvider);\n```\n\nSee [docs/KEY-MANAGEMENT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/KEY-MANAGEMENT.md).\n\n### Telemetry (SIEM / OpenTelemetry)\n\nThe library emits non-sensitive events on an `EventSource` named\n`PostQuantum.FileEncryption` (operation, KDF/key-source label, byte counts, elapsed time,\nfailure category — **never** keys or plaintext). Subscribe via `EventListener`,\n`dotnet-trace`, EventPipe, or OpenTelemetry. See [docs/DEPLOYMENT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/DEPLOYMENT.md).\n\n---\n\n## Security posture\n\nPostQuantum.FileEncryption is built to be **boring and predictable** where it matters:\n\n- **Authenticated encryption everywhere.** Every chunk is sealed with AES-256-GCM. The\n  header (key-establishment parameters, chunk size) and each chunk's ordinal position and\n  final-chunk marker are bound into the authenticated additional data, so reordering,\n  splicing, header tampering, and truncation are all detected as authentication failures.\n- **Hybrid KEM-DEM for public-key recipients.** The Hybrid package combines X25519 and\n  ML-KEM-768 (FIPS 203) via HKDF-SHA256 to derive a key-wrapping key; AES-256-GCM wraps a\n  fresh random content key. The data itself is always AES-256-GCM.\n- **Unique nonces by construction**, **fresh key material per file**, and **no decryption\n  oracle** — every authentication failure raises the same generic `PqDecryptionException`.\n- **Bounded work on untrusted input.** KDF cost parameters read from a container are\n  range-checked, so a malicious header cannot force unbounded memory or CPU — and\n  `PqDecryptionLimits` lets callers who open untrusted files lower those ceilings further\n  (the buffer for a container of known length is additionally capped by what the container\n  could actually hold).\n- **Key hygiene.** Derived keys, wrapped secrets, and private keys are zeroed with\n  `CryptographicOperations.ZeroMemory`.\n- **No novel cryptography.** Primitives come from .NET's `System.Security.Cryptography`,\n  the Konscious Argon2id implementation, and BouncyCastle (for the Hybrid package); this\n  library only composes them in standard patterns.\n\nFor deeper references:\n\n- [docs/COOKBOOK.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/COOKBOOK.md) — complete, copy-paste-runnable recipes for the common\n  jobs, with the failure handling done right\n- [docs/ANTI-PATTERNS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/ANTI-PATTERNS.md) — how to hold it wrong: the misuse shapes the\n  analyzers flag, and the ones only a human can catch\n- [docs/COMPLIANCE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/COMPLIANCE.md) — honest mapping to the PQC-migration mandates\n  (OMB M-23-02, CNSA 2.0, FIPS 203/204, 800-171) — including what is *not* claimed\n- [SECURITY.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/SECURITY.md) — supported versions, disclosure process, and the explicit\n  *\"does NOT defend against\"* list\n- [KNOWN-GAPS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/KNOWN-GAPS.md) — the honest open-issues ledger\n- [docs/AUDIT-GUIDE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/AUDIT-GUIDE.md) — the reviewer's entry point: the ~2,200-line\n  attack surface, the invariants to attack, and how to run the evidence\n- [docs/THREAT-MODEL.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/THREAT-MODEL.md) — assets, adversaries, trust boundaries\n- [docs/FILE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/FILE-FORMAT.md) — the on-disk container specification\n- [docs/SIGNATURE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SIGNATURE-FORMAT.md) — the detached `.sig` sidecar specification\n  (Ed25519 + ML-DSA-65)\n- [docs/KEY-FILE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/KEY-FILE-FORMAT.md) — the `PQKF` passphrase-encrypted key-file\n  specification (`ExportEncrypted` / `ImportEncrypted`)\n- [docs/HYBRID-COMBINER.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/HYBRID-COMBINER.md) — the X25519 + ML-KEM-768 combiner,\n  vs. X-Wing / HPKE / RFC 9794\n- [docs/CONFORMANCE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/CONFORMANCE.md) — the contract another implementation must meet\n- [docs/TEST-VECTORS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/TEST-VECTORS.md) — pinned known-answer vectors\n- [docs/ROADMAP-2.0.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/ROADMAP-2.0.md) — the candidate feature set for the next container\n  format revision (embedded signatures, metadata protection, SLH-DSA)\n\n\u003e Cryptographic software earns trust slowly. This library has **not been independently\n\u003e audited**; please review the code, the format, and [KNOWN-GAPS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/KNOWN-GAPS.md) before\n\u003e depending on it. Funded audit engagements are welcome — contact the maintainer.\n\u003e A criteria-by-criteria self-assessment — including what's still missing — is published\n\u003e at [docs/GOLD-STANDARD.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/GOLD-STANDARD.md).\n\n---\n\n## Post-quantum \u0026 the upgrade path\n\nBe clear-eyed about what *post-quantum* means here today:\n\n- **What's stable now:** the symmetric, passphrase-based engine. AES-256 is\n  quantum-resistant for the *confidentiality of your data* (≈128-bit security under\n  Grover), so a passphrase-encrypted file is sound against a harvest-now-decrypt-later\n  adversary. This is the engine being finalized for `1.0`.\n- **What's the recommended public-key path:** the **`PostQuantum.FileEncryption.Hybrid`**\n  package — a **hybrid X25519 + ML-KEM-768 combiner** plus **multiple recipients**. Fully\n  managed (BouncyCastle for *both* primitives), so it runs **anywhere** with no native\n  ML-KEM requirement, and the content key stays safe if *either* X25519 or ML-KEM is later\n  broken.\n- **What's deprecated:** the inline ML-KEM-768-only recipient mode in the **core**\n  (`PqKeyPair`, `PqRecipientPublicKey`, `PqRecipientPrivateKey`, recipient overloads on\n  `PqFileEncryptor`/`PqFileDecryptor`). Marked `[Obsolete]` with diagnostic id `PQFE002`\n  since `1.0.0-rc.2`, kept for source-compatibility only. Migrate to the Hybrid package.\n\n```bash\ndotnet add package PostQuantum.FileEncryption.Hybrid --version 1.5.0\n```\n\n```csharp\nusing PostQuantum.FileEncryption.Hybrid;\n\nusing var keyPair = PqHybridKeyPair.Generate();        // recipient\nbyte[] publish = keyPair.PublicKey.Export();\n\nvar recipient = PqHybridPublicKey.Import(publish);     // sender\nbyte[] container = await new PqHybridEncryptor().EncryptBytesAsync(secret, recipient);\n\nbyte[] plaintext = await new PqHybridDecryptor().DecryptBytesAsync(container, keyPair.PrivateKey);\n```\n\nDesign and format details: [docs/ROADMAP-v3.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/ROADMAP-v3.md).\n\n---\n\n## Supply chain \u0026 verification\n\nEvery release tag attaches a CycloneDX SBOM and a SLSA-style build-provenance attestation\nto the `.nupkg` artifacts. The release workflow runs\n`Meziantou.Framework.NuGetPackageValidation` against every produced `.nupkg` *before*\n`nuget push`, with the strict icon-must-be-set rule enabled. Coverage-guided fuzzers\n(cargo-fuzz + SharpFuzz) run nightly against both parsers with a cached corpus.\n\nQuick verification of any release:\n\n```bash\n# Verify the build-provenance attestation on a downloaded .nupkg:\ngh attestation verify PostQuantum.FileEncryption.1.5.0.nupkg \\\n  --owner systemslibrarian\n\n# Inspect the CycloneDX SBOM bundled with the release:\ngh release download v1.5.0 -p 'sbom.core.cdx.json' \u0026\u0026 jq . sbom.core.cdx.json\n\n# Confirm the conformance vectors decrypt locally:\ndotnet test --filter \"FullyQualifiedName~KnownAnswerVector|FullyQualifiedName~CrossImplementation\"\n```\n\nThe full verification recipe — including how to re-run conformance vectors against the\nRust/WASM reference implementation — is in [docs/SUPPLY-CHAIN.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SUPPLY-CHAIN.md).\n\n---\n\n## Documentation\n\n| Topic | Doc |\n| --- | --- |\n| Roadmap (`1.0` / `1.x` / beyond) | [ROADMAP.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/ROADMAP.md) |\n| Changelog | [CHANGELOG.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/CHANGELOG.md) |\n| Migrating from other libraries (age / libsodium / OpenSSL / .NET) | [docs/MIGRATION.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/MIGRATION.md) |\n| Cookbook (task-oriented recipes) | [docs/COOKBOOK.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/COOKBOOK.md) |\n| Anti-patterns (how to hold it wrong) | [docs/ANTI-PATTERNS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/ANTI-PATTERNS.md) |\n| Compliance \u0026 PQC-migration mapping | [docs/COMPLIANCE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/COMPLIANCE.md) |\n| Comparison vs. age / libsodium / OpenSSL | [docs/COMPARISON.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/COMPARISON.md) |\n| Benchmarks (methodology + reproduce-it-yourself) | [docs/BENCHMARKS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/BENCHMARKS.md) |\n| Security policy \u0026 disclosure | [SECURITY.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/SECURITY.md) |\n| Threat model (assets, adversaries, audit focus) | [docs/THREAT-MODEL.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/THREAT-MODEL.md) |\n| Auditor's guide (attack surface, invariants, evidence) | [docs/AUDIT-GUIDE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/AUDIT-GUIDE.md) |\n| Security reviews (reports + per-finding dispositions) | [docs/audits/](https://github.com/systemslibrarian/postquantum-file-encryption/tree/main/docs/audits) |\n| Security architecture \u0026 crypto inventory (+ FIPS) | [docs/SECURITY-ARCHITECTURE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SECURITY-ARCHITECTURE.md) |\n| On-disk container format | [docs/FILE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/FILE-FORMAT.md) |\n| Detached-signature sidecar format | [docs/SIGNATURE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SIGNATURE-FORMAT.md) |\n| Encrypted key-file format (`PQKF`) | [docs/KEY-FILE-FORMAT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/KEY-FILE-FORMAT.md) |\n| Hybrid combiner rationale (vs. X-Wing, HPKE, RFC 9794) | [docs/HYBRID-COMBINER.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/HYBRID-COMBINER.md) |\n| Conformance spec (re-implementer's contract) | [docs/CONFORMANCE.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/CONFORMANCE.md) |\n| Known-answer test vectors | [docs/TEST-VECTORS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/TEST-VECTORS.md) |\n| Supply chain (SBOM, attestations, verification) | [docs/SUPPLY-CHAIN.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/SUPPLY-CHAIN.md) |\n| Gold-standard self-assessment (incl. open gaps) | [docs/GOLD-STANDARD.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/GOLD-STANDARD.md) |\n| Reproducible builds (verify the .nupkg against the source) | [docs/REPRODUCIBLE-BUILDS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/REPRODUCIBLE-BUILDS.md) |\n| Deployment \u0026 hardening | [docs/DEPLOYMENT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/DEPLOYMENT.md) |\n| Versioning \u0026 compatibility policy | [docs/VERSIONING.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/VERSIONING.md) |\n| Key management (KMS/HSM, rotation) — design | [docs/KEY-MANAGEMENT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/KEY-MANAGEMENT.md) |\n| Hybrid \u0026 multi-recipient — design | [docs/ROADMAP-v3.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/ROADMAP-v3.md) |\n| Fuzzing (cargo-fuzz + SharpFuzz + OSS-Fuzz) | [docs/FUZZING.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/FUZZING.md) |\n| Known gaps (the honest ledger) | [KNOWN-GAPS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/KNOWN-GAPS.md) |\n| Support \u0026 lifecycle | [SUPPORT.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/SUPPORT.md) · Contributing: [CONTRIBUTING.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/CONTRIBUTING.md) |\n\nAPI reference (DocFX) is generated from the XML docs — see `docfx/`.\n\n---\n\n## Performance\n\nThroughput is dominated by two things: the **AES-256-GCM data plane** (which uses hardware\nAES and runs at multiple GB/s) and a **one-time key derivation** per file (a deliberate\ncost that hardens passphrases). The bigger the file, the more the KDF amortizes.\n\nIndicative end-to-end numbers (16 MiB, *including* full key establishment), measured with\nthe included BenchmarkDotNet project on one Windows 11 x64 machine in one session — treat\nas rough, not lab-grade:\n\n| Operation | Key establishment | Approx. throughput |\n| --------- | --- | ------------------ |\n| Encrypt   | PBKDF2 (100k) | ~675 MiB/s |\n| Decrypt   | PBKDF2 (100k) | ~755 MiB/s |\n| Encrypt   | Argon2id (8 MiB, 1 pass) | ~360 MiB/s |\n| Decrypt   | Argon2id (8 MiB, 1 pass) | ~550 MiB/s |\n| Encrypt   | Hybrid (X25519 + ML-KEM-768), 1 recipient | ~955 MiB/s |\n| Decrypt   | Hybrid, 1 recipient | ~1.16 GiB/s |\n\n**The post-quantum \"hybrid tax\" is sub-millisecond.** The entire hybrid key establishment\n— ML-KEM-768 *plus* X25519, HKDF, and the key wrap — measures ~0.5 ms per recipient, a\nfixed per-file cost independent of payload size. Hybrid public-key encryption is *faster*\nend-to-end than passphrase mode, because a KEM is cheap while a KDF is expensive on\npurpose.\n\nRun it yourself (and tune the KDF cost):\n\n```bash\ndotnet run -c Release --project benchmarks/PostQuantum.FileEncryption.Benchmarks -- --filter '*'\n```\n\nThe default PBKDF2 cost is 600,000 iterations (OWASP), so small files are KDF-bound by\ndesign; raise/lower it (or pick Argon2id) via `PqEncryptionOptions` to trade hardening for\nspeed.\n\nFull methodology, hybrid/multi-recipient numbers, and how to compare fairly against\nother tools: [docs/BENCHMARKS.md](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/docs/BENCHMARKS.md).\n\n---\n\n## Project layout\n\n```\nsrc/        PostQuantum.FileEncryption        — the library (symmetric core)\nsrc/        PostQuantum.FileEncryption.Hybrid — X25519 + ML-KEM-768 hybrid public-key package\nsrc/        PostQuantum.FileEncryption.Extensions.DependencyInjection — IServiceCollection integration\nsrc/        PostQuantum.FileEncryption.Analyzers — Roslyn misuse checks (PQFE101–PQFE104)\ntests/      PostQuantum.FileEncryption.Tests  — round-trip, KDF, recipient, hybrid, known-answer, cross-impl, property, fuzz tests\ntests/      PostQuantum.FileEncryption.Analyzers.Tests — analyzer rule tests\nbenchmarks/ PostQuantum.FileEncryption.Benchmarks — BenchmarkDotNet throughput suite\nsamples/    Pqfe.Cli                           — minimal CLI (encrypt/decrypt; AOT-publishable)\nsamples/    PostQuantum.FileEncryption.Demo   — .NET demo (Blazor Server, runs the library)\nsamples/    pqfe-wasm                          — Rust → WASM re-implementation of the .pqfe format\nsamples/    pqfe-web                           — fully client-side browser demo (GitHub Pages)\nsamples/    quickstarts/                       — smallest complete programs: backup encryption, ASP.NET Core encrypted uploads\ndocs/       *.md                               — format spec, threat model, test vectors, cookbook, anti-patterns, compliance, roadmap\n```\n\n### Why Blazor Server?\n\nA pure client-side WebAssembly demo would be lovely — files would never leave the browser\n— but .NET's `AesGcm` is annotated `[UnsupportedOSPlatform(\"browser\")]` and throws in\nWebAssembly. Rather than ship a demo that breaks the moment you click *Encrypt*, or quietly\nswap in a different (non-library) cipher, the .NET demo runs as **Blazor Server** so the\nreal library performs the encryption on the server runtime. Uploaded bytes are held in\nmemory only and are never persisted. The browser demo (`samples/pqfe-web`) sidesteps the\nproblem with a Rust/WASM core that re-implements the format byte-compatibly.\n\n---\n\n## Building from source\n\n```bash\ndotnet build -c Release\ndotnet test  -c Release\ndotnet pack  src/PostQuantum.FileEncryption -c Release\n```\n\n## License\n\n[MIT](https://github.com/systemslibrarian/postquantum-file-encryption/blob/main/LICENSE).\n\n---\n\n*To God be the glory — 1 Corinthians 10:31.*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemslibrarian%2Fpostquantum-file-encryption","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsystemslibrarian%2Fpostquantum-file-encryption","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsystemslibrarian%2Fpostquantum-file-encryption/lists"}