Serilog.Sinks.File.Encrypt
6.0.0-preview.1
dotnet add package Serilog.Sinks.File.Encrypt --version 6.0.0-preview.1
NuGet\Install-Package Serilog.Sinks.File.Encrypt -Version 6.0.0-preview.1
<PackageReference Include="Serilog.Sinks.File.Encrypt" Version="6.0.0-preview.1" />
<PackageVersion Include="Serilog.Sinks.File.Encrypt" Version="6.0.0-preview.1" />
<PackageReference Include="Serilog.Sinks.File.Encrypt" />
paket add Serilog.Sinks.File.Encrypt --version 6.0.0-preview.1
#r "nuget: Serilog.Sinks.File.Encrypt, 6.0.0-preview.1"
#:package Serilog.Sinks.File.Encrypt@6.0.0-preview.1
#addin nuget:?package=Serilog.Sinks.File.Encrypt&version=6.0.0-preview.1&prerelease
#tool nuget:?package=Serilog.Sinks.File.Encrypt&version=6.0.0-preview.1&prerelease
Serilog.Sinks.File.Encrypt
A Serilog.File.Sink hook that encrypts log files using RSA and AES-GCM hybrid encryption. This package provides secure logging by encrypting log data before writing to disk, ensuring sensitive information remains protected.
v6.0.0 Breaking Changes The writer now emits the v2 on-disk format: every frame binds its session and position into the AES-GCM authenticated data, and a cleanly closed log ends with an authenticated end-of-log seal, making truncation, reordering, and splicing detectable (#83).
- v6 readers still decrypt v1-format files produced by v3.x–v5.x; v5.x and earlier cannot read v6 files.
- The ignored
versionparameter was removed fromEncryptHooksandEncryptionOptions. - See the CHANGELOG for the full migration guide.
Features
- Hybrid Encryption: Uses RSA for key exchange and AES-GCM for efficient, authenticated data encryption
- Tamper-Evident Framing (v2): Frame order, session identity, and header metadata are bound into the AES-GCM authenticated data; a sealed log's truncation is cryptographically detectable
- Key Rotation: Assign a key ID to
EncryptHooks; the decryption layer selects the correct private key automatically - Seamless Integration: Plugs directly into Serilog.File.Sink using file lifecycle hooks
- Optimal Performance: Optimized encryption performance using hybrid encryption
Use Cases
- Secure logging of sensitive application data, especially in desktop applications
- Compliance with data protection regulations by encrypting log files
- Protection against unauthorized access to log files in shared or cloud environments
Performance
Production-ready performance with minimal overhead (v6.0.0 benchmark run, including the v2 format's per-frame authenticated data and end-of-log seal):
- ✅ ~5-13% time overhead in real-world unbuffered scenarios (well under typical targets)
- ✅ ~1.00x memory — allocations are effectively identical to unencrypted logging
- ✅ Integrity effectively free — the v2 tamper-evidence features add no measurable cost over plain AES-GCM
- 🚀 Buffered mode is dramatically faster than non-encrypted unbuffered — up to ~5x at high volume
- ✅ Zero lock contentions — safe for multithreaded applications handled by Serilog.File.Sink
Buffering Trade-off: While buffered writes provide excellent performance, they carry a risk of data loss if the application crashes before flushing. Always call Log.CloseAndFlush() on application shutdown.
For detailed benchmarks and analysis, see the Benchmark Documentation.
Installation
Install the package via NuGet:
dotnet add package Serilog.Sinks.File.Encrypt
For decryption in your application, install the Decrypt package:
dotnet add package Serilog.Sinks.File.Decrypt
For key generation and ad-hoc log decryption, install the CLI tool:
dotnet tool install --global Serilog.Sinks.File.Encrypt.Cli
Quick Start
1. Generate RSA Key Pair
Generate an RSA key pair using the CLI tool:
serilog-encrypt generate --output ./keys
This creates:
public_key.xml: Used for encryption (safe to include with your application)private_key.xml: Used for decryption (keep secure, do not distribute)
2. Configure Serilog with Encryption
using Serilog;
using Serilog.Sinks.File.Encrypt;
// Load your public key
string publicKeyXml = File.ReadAllText("./keys/public_key.xml");
// Configure Serilog with encryption.
// Assign a key ID to support future key rotation (recommended).
Log.Logger = new LoggerConfiguration()
.WriteTo.File(
path: "logs/app.log",
hooks: new EncryptHooks(publicKeyXml, keyId: "my-app-key-2026"))
.CreateLogger();
// Log as usual
Log.Information("This message will be encrypted!");
// Always flush on shutdown
Log.CloseAndFlush();
💡 Performance Tip: For high-volume scenarios where you can tolerate potential data loss on crashes, use
buffered: true. See the Advanced Usage section below.
3. Decrypt Logs
To decrypt log files, see the Serilog.Sinks.File.Decrypt package for programmatic decryption, or the Serilog.Sinks.File.Encrypt.Cli tool for ad-hoc decryption.
Advanced Usage
High-Performance Configuration (Buffered Mode)
For high-volume logging scenarios where you can tolerate potential data loss on crashes:
using Serilog;
using Serilog.Sinks.File.Encrypt;
string publicKeyXml = File.ReadAllText("./keys/public_key.xml");
Log.Logger = new LoggerConfiguration()
.WriteTo.File(
path: "logs/app.log",
buffered: true, // buffered writes
flushToDiskInterval: TimeSpan.FromSeconds(5), // flush every X seconds (adjust as needed)
hooks: new EncryptHooks(publicKeyXml, keyId: "my-app-key-2026"))
.CreateLogger();
// CRITICAL: Always flush on shutdown
Log.CloseAndFlush();
⚠️ Warning: Buffered writes risk data loss on crashes. Only use when:
- Application has reliable shutdown handling
- You can tolerate loss of recent logs (up to
flushToDiskInterval) - Performance is critical (background workers, high-volume systems)
⚠️ Minor Risks
- On a crash, buffered (unflushed) entries are lost and the file may end with a partially written session or frame, and without the end-of-log seal. This is a completeness/data-loss concern rather than a confidentiality one: because each session generates a fresh random AES key and nonce, and the decryptor skips incomplete trailing data, a crash does not cause key or nonce reuse. The decryptor reports such sessions as unsealed (see
SealStatusin the Decrypt package) rather than silently treating them as complete. - Nonce-counter wrapping within a single session is not explicitly handled. Each session uses a 96-bit AES-GCM nonce — a 32-bit random prefix plus a 64-bit counter — so wrapping would require 2^64 encryptions in one continuous session before the counter cycles.
- At 1 million logs/second, that is roughly 585,000 years.
Key Rotation
Assign a unique keyId to each key generation cycle. The ID is embedded in every session header
so the decryption layer knows which private key to use without any manual lookup.
// Old deployment — key from 2025
hooks: new EncryptHooks(oldPublicKey, keyId: "my-app-key-2025")
// New deployment — key from 2026
hooks: new EncryptHooks(newPublicKey, keyId: "my-app-key-2026")
For the decryption side of key rotation, see the Serilog.Sinks.File.Decrypt documentation.
Programmatic Key Generation
using Serilog.Sinks.File.Encrypt;
// Generate a new RSA key pair
var (publicKey, privateKey) = CryptographicUtils.GenerateRsaKeyPair();
// Save keys to files
File.WriteAllText("public_key.xml", publicKey);
File.WriteAllText("private_key.xml", privateKey);
Web Application Example
var builder = WebApplication.CreateBuilder(args);
// Load public key from configuration
string publicKeyXml = builder.Configuration["Logging:PublicKeyXml"]
?? throw new InvalidOperationException("Logging:PublicKeyXml is required");
builder.Host.UseSerilog((context, configuration) =>
configuration
.ReadFrom.Configuration(context.Configuration)
.WriteTo.File(
path: "logs/webapp-.log",
rollingInterval: RollingInterval.Day,
hooks: new EncryptHooks(publicKeyXml, keyId: "webapp-key-2026")));
var app = builder.Build();
app.Run();
API Reference
Key Management
// Generates a new RSA key pair with the specified key size and format (XML or PEM).
(string publicKey, string privateKey) CryptographicUtils.GenerateRsaKeyPair(int keySize = 2048, KeyFormat format = KeyFormat.Xml)
Encryption Hook
// publicKey — RSA public key in XML or PEM format
// keyId — optional identifier embedded in every session header (max 32 bytes UTF-8); default ""
new EncryptHooks(string publicKey, string keyId = "")
On-disk format (v2)
Each file open starts a session: a header (magic(8) + version(1) + keyId(32) + RSA-OAEP-SHA256(AES key ‖ nonce)), followed by self-framing AES-256-GCM frames (length(4, big-endian) + ciphertext + tag(16)). In v2, every frame authenticates 41 bytes of associated data — the SHA-256 hash of the session header, the frame's sequence number, and a frame-type byte — so dropped, reordered, duplicated, or cross-session-spliced frames fail authentication. On clean close (Dispose/Log.CloseAndFlush()) the writer appends a 28-byte authenticated seal record carrying the final frame count, encrypted under a reserved nonce so it stays verifiable even if trailing frames are missing.
Security Considerations
- Keep private keys secure and never include them in your application deployment
- Store private keys in secure key management systems in production (Azure Key Vault, AWS Secrets Manager, etc.)
- Use 2048-bit RSA keys minimum (4096-bit for enhanced security)
- Restrict filesystem access to encrypted log files and private keys
- Rotate keys periodically and use the
keyIdparameter to track which key encrypted which files
Threat model & known limitations
This package protects the confidentiality and integrity of your log data, and — as of the v2 format — makes tampering within a session cryptographically detectable. Understand what it does and does not defend against before relying on it for security/audit purposes.
What is protected (v2 format, v6.0.0+)
- ✅ Confidentiality — log contents are encrypted with AES-256-GCM and the per-session key is wrapped with RSA-OAEP-SHA256. Reading the logs requires the private key.
- ✅ Per-frame integrity — every encrypted frame carries a 128-bit GCM authentication tag, so modifying the bytes of an existing frame is detected during decryption.
- ✅ Frame ordering and completeness within a session — each frame's associated data binds the session header (version, keyId, wrapped key) and the frame's sequence number. Dropping, reordering, duplicating, or splicing frames between sessions fails authentication.
- ✅ Truncation of a cleanly closed log — a sealed session's authenticated frame count exposes removed trailing frames (
SealCountMismatch). A session with no seal is reported as unsealed: either the application crashed or the tail was truncated — the two are byte-for-byte indistinguishable by design, so the decryptor reports the ambiguity instead of silently claiming completeness.
What is not protected
- ❌ Fabricated sessions. Encryption only requires the public key, which ships with your application. Anyone who has that public key and can write to the log file can generate their own AES session key, wrap it with the public key, and append entirely fabricated sessions (they cannot inject frames into your existing sessions — the AAD binding prevents that). They cannot read or alter the contents of your existing sessions (that requires the private key). Preventing whole-session fabrication requires a secret the attacker does not have — for example a symmetric MAC or a producer-side signing key kept off the public distribution — which this package does not currently provide.
- ❌ Deletion of entire sessions. Sessions are independently keyed; nothing chains one session to the next, so removing a whole session (e.g. one rolled file's worth) from the middle of a multi-session file is not detectable by the format itself.
If your use case needs full tamper-evidence against a writer with the public key (for example security or audit logs), pair the encrypted file with an external integrity mechanism such as append-only/WORM storage, remote log shipping, or signing.
Migration
For step-by-step migration guides, see the CHANGELOG.md:
Versioning
All packages in this repository (Serilog.Sinks.File.Encrypt, Serilog.Sinks.File.Decrypt, Serilog.Sinks.File.Encrypt.Cli, Serilog.Sinks.File.Encrypt.Core) are released in lockstep. Every package is versioned and published together on every release, even when a change only affects one of them. Always use the same version across all packages you reference.
Requirements
- .NET 8.0 (LTS) or .NET 10.0 (LTS), or a compatible higher runtime
- A project using Serilog.Sinks.File
- RSA key pair in XML or PEM format (generated via CLI tool or programmatically)
Support policy: This library targets .NET Long-Term Support (LTS) releases only. A new LTS TFM is added when it ships; the oldest LTS TFM is dropped when Microsoft ends support for it. Users on STS or EOL runtimes can pin an older package version that targets a compatible LTS TFM.
Related Packages
| Package | Purpose |
|---|---|
Serilog.Sinks.File.Decrypt |
Programmatic decryption of encrypted log files |
Serilog.Sinks.File.Encrypt.Cli |
CLI tool for key generation and ad-hoc log decryption |
Serilog.Sinks.File.Encrypt.Core |
Shared cryptographic primitives (transitive dependency) |
| Product | Versions 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 was computed. 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. |
-
net10.0
- Serilog.Sinks.File (>= 7.0.0)
- Serilog.Sinks.File.Encrypt.Core (>= 6.0.0-preview.1)
-
net8.0
- Serilog.Sinks.File (>= 7.0.0)
- Serilog.Sinks.File.Encrypt.Core (>= 6.0.0-preview.1)
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 |
|---|---|---|
| 6.0.0-preview.1 | 0 | 7/6/2026 |
| 5.1.0-preview.1 | 37 | 7/2/2026 |
| 5.0.0 | 95 | 6/29/2026 |
| 5.0.0-preview.4 | 52 | 6/29/2026 |
| 4.0.0 | 3,538 | 4/13/2026 |
| 4.0.0-preview.1 | 95 | 4/8/2026 |
| 3.0.0 | 1,080 | 3/23/2026 |
| 2.0.1-alpha.0.16 | 65 | 3/23/2026 |
| 2.0.0 | 4,153 | 12/2/2025 |
| 1.0.0 | 468 | 12/1/2025 |
| 0.50.1 | 237 | 11/27/2025 |
| 0.0.0-alpha.0.27 | 177 | 11/27/2025 |
| 0.0.0-alpha.0.17 | 185 | 11/25/2025 |
| 0.0.0-alpha.0.14 | 167 | 11/24/2025 |
| 0.0.0-alpha.0.13 | 174 | 11/24/2025 |
| 0.0.0-alpha.0.12 | 184 | 11/24/2025 |