Table of Contents

Encryption

Encryption is applied as a module, so it works with any settings class, hardcoded or dynamic. The serialized JSON is encoded to UTF-8 bytes and encrypted with a symmetric algorithm from the .NET base class library (System.Security.Cryptography) — no third-party cryptography is involved. By default the algorithm is AES-256-CBC, which is byte-for-byte compatible with every file this library has ever written.

Attaching it

The WithEncryption fluent extension attaches an EncryptionModule. The simplest form takes a password string:

using Nucs.JsonSettings;

var settings = JsonSettings.Load<MySettings>("config.json", q => q.WithEncryption("mysecretpassword"));
// or, explicitly:
var settings = JsonSettings.Configure<MySettings>("config.json")
                           .WithEncryption("mysecretpassword")
                     //or: .WithModule<MySettings, EncryptionModule>("pass");
                           .LoadNow();

Password sources

WithEncryption has overloads for every practical way of supplying a key. The password can be a constant, or a fetcher that is invoked when needed — and the fetcher can read from the settings object itself:

// A plain string or SecureString
q.WithEncryption("mysecretpassword");
q.WithEncryption(secureString);

// A getter/generator, evaluated lazily
q.WithEncryption(() => GetPasswordFromVault());
q.WithEncryption(() => GetSecureStringFromVault());

// Derive the password from a property on the settings object being loaded
q.WithEncryption(set => set.SomeProperty);

The secret can also be supplied as bytes. A byte[] password is stretched into the key with the same PBKDF2 derivation as a text password; a raw key is used verbatim and must match the algorithm's key length (16/24/32 bytes for AES, 32 for ChaCha20-Poly1305):

// Binary password - PBKDF2-derived. NOTE: this is a DIFFERENT credential from the text password
// whose UTF-8 encoding equals these bytes, because the text derivation folds in the string's
// character length. Pick one form per file.
q.WithEncryption(passwordBytes);
q.WithEncryption(() => GetPasswordBytesFromVault());

// Raw key - used as-is, no derivation. You own the key's quality, so supply high-entropy
// material (e.g. RandomNumberGenerator.GetBytes(32)), not a low-entropy value.
q.WithEncryptionRawKey(key32);
q.WithEncryptionRawKey(() => GetKeyFromVault());

The property-fetcher form is handy when part of your settings (loaded via a constructor argument, for example) is itself the key:

// SomeProperty is supplied as a constructor argument and then used as the password
var o = JsonSettings.Load<CasualExampleSettings>(fileName,
            s => s.WithEncryption(set => set.SomeProperty),
            new object[] { "SuperPassword" });

Choosing an algorithm

The default is AES-256-CBC. To pick another algorithm, pass an EncryptionAlgorithm (and optionally a KeySize) to WithEncryption, WithEncryption(byte[], ...) or WithEncryptionRawKey(byte[], ...):

q.WithEncryption("password", EncryptionAlgorithm.AesGcm);
q.WithEncryption("password", EncryptionAlgorithm.AesCbc, KeySize.Aes128);
q.WithEncryptionRawKey(key32, EncryptionAlgorithm.ChaCha20Poly1305);
EncryptionAlgorithm Authenticated Layout Availability
AesCbc (default) No (UTF-8 heuristic only) IV(16) ‖ ciphertext All targets
AesCbcHmac Yes (HMAC-SHA256, Encrypt-then-MAC) IV(16) ‖ ciphertext ‖ tag(32) All targets
AesGcm Yes (AEAD) nonce(12) ‖ ciphertext ‖ tag(16) .NET 6.0+
AesCcm Yes (AEAD) nonce(12) ‖ ciphertext ‖ tag(16) .NET 6.0+, OS support
ChaCha20Poly1305 Yes (AEAD) nonce(12) ‖ ciphertext ‖ tag(16) .NET 6.0+, OS support

The AEAD algorithms only exist in the BCL on .NET 6.0 and later; when the library is used from netstandard2.0 or net48 those enum members are not present, and AesCbc/AesCbcHmac are the options. ChaCha20Poly1305 and AesCcm additionally require OS support.

Important

There is no algorithm marker in the file. As with the password and key size, a file must be read back with the same EncryptionAlgorithm and KeySize it was written with. Only the default, AesCbc, is guaranteed to read files from older versions of this library.

Combining with Base64

WithBase64() attaches a Base64Module. Because modules order themselves correctly on the encrypt/decrypt pipeline (see the Modulation API), you can stack them:

var o = JsonSettings.Configure<CasualExampleSettings>(fileName)
                    .WithBase64()
                    .WithEncryption("SuperPassword")
                    .LoadNow();

A file that is not valid base64 (truncated, or edited by hand) is treated as a damaged file, exactly like a short encrypted one: it surfaces as a catchable JsonSettingsException, and a RecoveryModule absorbs it — rather than a raw FormatException escaping the decode stage ahead of the recovery hook.

Wrong passwords, authentication and file format

  • With the default AesCbc, a wrong password is reported as a wrong password. Decryption verifies the padding and checks that the decrypted payload is valid UTF-8, so a bad key surfaces as a decryption failure rather than as a misleading "corrupt file" JSON error. This is a diagnostic, not an integrity guarantee: AES-CBC does not authenticate its data.
  • The authenticated algorithms (AesCbcHmac, AesGcm, AesCcm, ChaCha20Poly1305) verify an authentication tag when decrypting. A wrong key or a tampered file fails with a real integrity error, not a heuristic. Choose one of these if you need to detect modification of the file, not only keep its contents confidential.
  • The on-disk format of the default is stable. Files written by earlier versions stay readable; the cipher, the IV layout and the PBKDF2-SHA1 key derivation are unchanged. The move onto System.Security.Cryptography did not change any bytes — it is verified against a ciphertext captured from a pre-migration build and against an independent, BCL-only reimplementation of the format.