SquidStd.Crypto
0.28.0
dotnet add package SquidStd.Crypto --version 0.28.0
NuGet\Install-Package SquidStd.Crypto -Version 0.28.0
<PackageReference Include="SquidStd.Crypto" Version="0.28.0" />
<PackageVersion Include="SquidStd.Crypto" Version="0.28.0" />
<PackageReference Include="SquidStd.Crypto" />
paket add SquidStd.Crypto --version 0.28.0
#r "nuget: SquidStd.Crypto, 0.28.0"
#:package SquidStd.Crypto@0.28.0
#addin nuget:?package=SquidStd.Crypto&version=0.28.0
#tool nuget:?package=SquidStd.Crypto&version=0.28.0
<h1 align="center">SquidStd.Crypto</h1>
OpenPGP key management and operations for SquidStd, built on PgpCore
(a maintained MIT wrapper over BouncyCastle). Provides key generation, encrypt/decrypt, sign/verify, and the
combined encrypt+sign / decrypt+verify flows over a stateful, indexed keyring with a pluggable persistence
backend. SquidStd.Core stays dependency-free; this module is the home for higher-level crypto features, with
PGP namespaced under SquidStd.Crypto.Pgp so future areas can coexist.
Install
dotnet add package SquidStd.Crypto
Usage
using DryIoc;
using SquidStd.Crypto.Pgp.Extensions;
using SquidStd.Crypto.Pgp.Interfaces;
using SquidStd.Crypto.Pgp.Services;
// Register the keyring, service, and a key store (all singletons).
container.RegisterPgp(_ => new FilePgpKeyStore("/var/lib/app/pgp"));
// or encrypted at rest with the app key:
// container.RegisterPgp(r => new AesGcmPgpKeyStore(r.Resolve<ISecretProtector>(), "/var/lib/app/pgp.bin"));
var keyring = container.Resolve<IPgpKeyring>();
var pgp = container.Resolve<IPgpService>();
// Generate a key and import it into the keyring.
var alice = pgp.GenerateKey("alice@example.com", "passphrase");
keyring.Import(alice.PrivateArmored!);
keyring.Import(bobPublicArmored); // a correspondent's public key
// Encrypt for a recipient, decrypt with the held secret key.
string armored = await pgp.EncryptForAsync("bob@example.com", payloadBytes);
byte[] plaintext = await pgp.DecryptAsync(armoredFromBob, "passphrase");
// Sign and verify (signed message - the data is embedded in the armored block).
string signed = await pgp.SignAsync(payloadBytes, "alice@example.com", "passphrase");
var verification = await pgp.VerifyAsync(signed); // verification.IsValid, verification.Data
// Combined: encrypt + sign, then decrypt + verify.
string sealed = await pgp.EncryptAndSignForAsync("bob@example.com", payloadBytes, "alice@example.com", "passphrase");
var result = await pgp.DecryptAndVerifyAsync(sealedFromBob, "passphrase"); // result.Data, result.IsSigned, result.IsValid
// Persist / restore the keyring.
await keyring.SaveAsync(container.Resolve<IPgpKeyStore>());
await keyring.LoadAsync(container.Resolve<IPgpKeyStore>());
Password-based encryption
PasswordCipher encrypts a payload under a password - Argon2id derives the key, AES-256-GCM seals the data,
and the result is a self-describing, versioned envelope (salt, nonce, tag and KDF cost are embedded, so
decryption needs only the password and the blob).
using SquidStd.Crypto.Password;
using SquidStd.Crypto.Password.Data;
byte[] blob = PasswordCipher.Encrypt(payloadBytes, "correct horse battery staple");
byte[] back = PasswordCipher.Decrypt(blob, "correct horse battery staple");
// Text + base64 envelope for storing in config/JSON:
string protectedText = PasswordCipher.EncryptString("a secret", "pw");
string clear = PasswordCipher.DecryptString(protectedText, "pw");
// Tune the Argon2id cost (defaults to PbkdfCost.Moderate):
byte[] strong = PasswordCipher.Encrypt(payloadBytes, "pw", PbkdfCost.Sensitive);
A wrong password or tampered data raises PasswordDecryptionException. For raw-key or app-key/KMS
encryption use CryptoUtils / ISecretProtector instead.
Key types
| Type | Purpose |
|---|---|
IPgpService |
Key generation, encrypt/decrypt, sign/verify, and combined encrypt+sign / decrypt+verify. |
IPgpKeyring |
Stateful, indexed keyring: import keys and save/load via an IPgpKeyStore. |
IPgpKeyStore |
Pluggable keyring persistence backend. |
FilePgpKeyStore |
One armored .asc per key (gpg-interoperable). |
AesGcmPgpKeyStore |
The whole keyring serialized to a single file, encrypted at rest via ISecretProtector. |
CryptoFileSystem |
ILockableFileSystem that encrypts content and names over any IVirtualFileSystem. |
PasswordCipher |
Password-based encryption: Argon2id + AES-256-GCM with a self-describing envelope. |
PbkdfCost |
Argon2id cost presets (Interactive / Moderate / Sensitive) + custom. |
Key stores
FilePgpKeyStore(directory)- one armored.ascper key (public, plus secret when held). gpg-interoperable.AesGcmPgpKeyStore(ISecretProtector, path)- the whole keyring serialized to a single file, encrypted at rest with the application key viaSquidStd'sISecretProtector.
Notes
- Signatures are signed messages, not detached signatures:
SignAsyncembeds the data in the armored block andVerifyAsyncrecovers it. Verification is pass/fail - PgpCore does not expose the signer's key id or identity, so the results carry no signer attribution. DecryptAndVerifyAsyncnever throws on a bad/absent signature when the ciphertext itself is valid: it always recoversDataand reportsIsSigned/IsValid.- The streaming
DecryptAsync(Stream, Stream, …)buffers its input internally so the recipient key id can be read before decrypting; the encrypt and sign stream paths flow straight through. - Passphrases are supplied per operation and never persisted (only passphrase-protected secret blocks are stored).
Crypto vault (encrypted virtual filesystem)
CryptoFileSystem is an ILockableFileSystem that decorates any IVirtualFileSystem
(SquidStd.Vfs), encrypting file content and names. Crypto(Zip("vault.dat")) is a single-file encrypted
vault you unlock with a passphrase, write to, and lock again.
using DryIoc;
using SquidStd.Crypto.Vfs.Extensions;
using SquidStd.Vfs.Abstractions.Interfaces;
// Single-file vault (crypto over a zip backend), registered as a singleton.
container.RegisterCryptoVault("/var/lib/app/vault.dat");
var vault = container.Resolve<ILockableFileSystem>();
vault.Unlock("my passphrase"); // derives the key (Argon2id)
await vault.WriteAllBytesAsync("docs/cv.pdf", bytes);
byte[]? back = await vault.ReadAllBytesAsync("docs/cv.pdf");
vault.Lock(); // flushes, prunes, zeroes the key
Compose other backends directly:
using SquidStd.Crypto.Vfs.Services;
using SquidStd.Vfs.Services;
var folderVault = new CryptoFileSystem(new PhysicalFileSystem("/secure/dir"));
Notes
- Unlock with a passphrase: a 256-bit key is derived with Argon2id (salt + cost params live in the cleartext header). Per-purpose subkeys are derived with HKDF-SHA256. The passphrase is never persisted.
- Per-entry encryption: each file is stored as chunked AES-GCM (64 KiB chunks), so large files stream with bounded memory and tampering/truncation is detected.
- Encrypted name index: logical paths and structure live in an encrypted index; backing entries use opaque ids, so a locked vault leaks neither file names nor layout.
- Read-write, lockable: add/update/delete any time while unlocked;
Lock()/Dispose()flush the encrypted index, prune orphaned blobs, and zero the key withCryptographicOperations.ZeroMemory. - A wrong passphrase fails the index authentication tag →
CryptographicException; operations on a locked vault throwInvalidOperationException.
Related
- Tutorial: Crypto (PGP)
License
MIT - part of SquidStd.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- BouncyCastle.Cryptography (>= 2.6.2)
- PgpCore (>= 7.1.0)
- SquidStd.Core (>= 0.28.0)
- SquidStd.Services.Core (>= 0.28.0)
- SquidStd.Vfs (>= 0.28.0)
- SquidStd.Vfs.Abstractions (>= 0.28.0)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.28.0 | 47 | 7/8/2026 |
| 0.27.0 | 54 | 7/7/2026 |
| 0.26.0 | 46 | 7/7/2026 |
| 0.25.0 | 62 | 7/6/2026 |
| 0.24.1 | 54 | 7/6/2026 |
| 0.24.0 | 62 | 7/6/2026 |
| 0.23.0 | 61 | 7/4/2026 |
| 0.22.0 | 55 | 7/4/2026 |
| 0.21.0 | 61 | 7/3/2026 |
| 0.20.0 | 54 | 7/3/2026 |
| 0.19.0 | 62 | 7/3/2026 |
| 0.18.0 | 62 | 7/3/2026 |
| 0.17.0 | 70 | 7/3/2026 |
| 0.16.0 | 61 | 7/3/2026 |
| 0.15.0 | 62 | 7/2/2026 |
| 0.14.1 | 68 | 7/2/2026 |
| 0.14.0 | 59 | 7/2/2026 |
| 0.13.0 | 57 | 7/2/2026 |
| 0.12.0 | 57 | 7/2/2026 |
| 0.11.0 | 61 | 7/2/2026 |