Cryptography software earns trust by being honest about its limits. This document explains how to
report a problem and what guarantees the library does — and does not — make today. For the running
list of limitations, see KNOWN-GAPS.md. For the precise statement of what we
defend against, the threat model, and the ten numbered security invariants the library is
designed to hold, see docs/threat-model.md. For the production
operational checklist, see docs/deployment.md.
Please do not open a public issue for security problems.
- Use GitHub Security Advisories ("Report a vulnerability") on this repository, or
- email the maintainer privately.
Please include enough detail to reproduce: affected version, target framework, a minimal repro, and the impact you observed. You will get an acknowledgement, and we will keep you informed as we investigate, fix, and (with credit, if you wish) disclose.
This is a faith-and-craft project — responses are best-effort, but security reports are always triaged first.
While in 0.x preview, only the latest released version receives security fixes. There is no
back-porting to older previews before 1.0. The 1.0 backport policy will be published at that
release; see future.md for the 1.0 checklist.
| Version | Supported |
|---|---|
0.4.0-preview.* |
✅ |
| older previews | ❌ |
- Confidentiality & integrity of wrapped keys. Content keys are wrapped with AES-256-GCM, an authenticated cipher. A modified or truncated wrapped key fails to unwrap rather than yielding attacker-influenced key material.
- Strong content keys. 256-bit keys from the platform CSPRNG (
RandomNumberGenerator). - Brute-force resistance for passphrases. The local provider stretches passphrases with Argon2id
(memory-hard), with tunable cost via
LocalKekOptions(presets aligned to RFC 9106 / OWASP). - Early detection of a wrong passphrase. Keyring metadata (v3) carries a 32-byte HMAC-SHA256
verifier per KEK;
Importchecks it in constant time so wrong passphrases are rejected up front rather than as a delayed authentication failure. v2 tokens (16-byte truncated verifier) and v1 tokens (no verifier) still import — the constant-time comparison handles either width via a prefix match against the recomputed 32-byte verifier. - Hostile-input resistance. Every token decoder uses overflow-safe length arithmetic and caps
every length-prefixed field; the keyring decoder additionally caps the number of KEKs.
TryDecodeoverloads exist for inputs from untrusted sources. - Boundary validation. Empty passphrases and obviously-malformed inputs are rejected with clear
ArgumentExceptions at the library boundary, before any cryptographic work runs. - Concurrent safety.
LocalContentKeyProviderdocuments and tests that rotation, wrap, and unwrap are safe under concurrent use; a rotating thread cannot dispose a KEK in use elsewhere. - Safe diagnostic output. The records that carry byte arrays override
ToString()to redact byte content as<NN bytes>. Log lines that include these records cannot leak ciphertext or the keyring verifier. - Cross-platform atomic file persistence.
FileKeyringStoreusesFile.Replace(POSIXrename(2)) with a bounded retry on Windows-specificIOExceptionfrom concurrent readers, so the single-writer + many-readers production model documented indocs/deployment.mdis race-free in practice. - Bounded exposure of plaintext keys.
ContentKeyzeroes its buffer onDispose; derived passphrase bytes and intermediate copies are zeroed after use.
- A compromised host. If an attacker can read your process memory or your passphrase as it is entered, no library can save the keys in use at that moment.
- Weak passphrases. Argon2id raises the cost of guessing; it cannot rescue a low-entropy secret.
- Harvest-now-decrypt-later against asymmetric wrapping. The current release has no asymmetric KEM at all (local wrapping is symmetric). When cloud/asymmetric wrapping arrives, classical RSA/ECC wrapping would be quantum-vulnerable until a PQ KEM (e.g. ML-KEM) or hybrid mode lands. See KNOWN-GAPS.md.
- Passphrase storage. Storing passphrases safely (env var, secret manager, prompt) is the
caller's responsibility. The exported keyring metadata is non-secret and safe to persist next to
your data — but the passphrases that derive the KEKs must be in a real secret store.
docs/deployment.mdwalks through the operational shape. - External audit. This library has been written with care, automated tests, static analysers,
a hostile-input test suite, and a published threat model — but it has not yet had a
third-party cryptographic review. That work is tracked in
future.md.
LocalKekOptions ships four presets aligned to RFC 9106 and OWASP. For the stated threat model —
an attacker who has obtained the persisted keyring metadata (salts + parameters + verifier) and is
mounting an offline GPU/ASIC-accelerated passphrase guess — the picks are:
| Use case | Preset | Why |
|---|---|---|
| Production floor (general server-side) | Moderate |
256 MiB / 4 iterations / parallelism 4. Buys meaningful margin against GPU/ASIC offline guessing while keeping derivation under ~1 s on modern hardware. |
| Long-lived master KEKs (root of trust) | Sensitive |
2 GiB / 1 iteration / parallelism 4 — RFC 9106 §4 first recommendation. Derived once per process or per rotation; pay the cost. |
| Latency-constrained server | Interactive |
64 MiB / 3 iterations / parallelism 4. Acceptable when the host is hardened against memory exfiltration and latency budget is the binding constraint. |
| Constrained host / CI | LowMemory |
OWASP minimum (19 MiB / 2 iterations / parallelism 1). Not a general production setting. |
Argon2id derivation is one-shot — once at startup, once per rotation — never on the request path. That means the latency cost is paid rarely and you should pick the highest preset whose one-shot latency you can afford. Tune by measuring on production-shape hardware; whatever you pick is recorded per-KEK in the keyring metadata so future imports reproduce the same KEK.
The
LowMemorypreset is OWASP's floor for environments where 64 MiB is genuinely unaffordable; it is not the right answer for production unless you have measured and ruled out everything stronger. The library's instance defaults matchInteractive, notLowMemory.
- AES-256-GCM, HMAC-SHA256, SHA-256, and the CSPRNG come from the .NET base class
library (
System.Security.Cryptography). - Argon2id comes from
Konscious.Security.Cryptography.Argon2.
We do not ship our own implementations of primitives.
We support coordinated disclosure and are happy to credit reporters. Thank you for helping keep users safe.
To God be the glory — 1 Corinthians 10:31.