AgeSharp 0.1.0-preview.4
dotnet add package AgeSharp --version 0.1.0-preview.4
NuGet\Install-Package AgeSharp -Version 0.1.0-preview.4
<PackageReference Include="AgeSharp" Version="0.1.0-preview.4" />
<PackageVersion Include="AgeSharp" Version="0.1.0-preview.4" />
<PackageReference Include="AgeSharp" />
paket add AgeSharp --version 0.1.0-preview.4
#r "nuget: AgeSharp, 0.1.0-preview.4"
#:package AgeSharp@0.1.0-preview.4
#addin nuget:?package=AgeSharp&version=0.1.0-preview.4&prerelease
#tool nuget:?package=AgeSharp&version=0.1.0-preview.4&prerelease
<p align="center"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo_white.svg"> <source media="(prefers-color-scheme: light)" srcset="https://github.com/FiloSottile/age/blob/main/logo/logo.svg"> <img alt="The age logo, a wireframe of St. Peters dome in Rome, with the text: age, file encryption" width="600" src="https://github.com/FiloSottile/age/blob/main/logo/logo.svg"> </picture> </p>
AgeSharp is a C# implementation of the
age file encryption format, fully interoperable
with the reference Go implementation and
other age-compatible tools.
It depends only on BouncyCastle.Cryptography and targets .NET 10.
Features
- All standard recipient types: X25519, scrypt/passphrase, SSH-Ed25519, SSH-RSA
- Post-quantum ML-KEM-768-X25519 hybrid encryption
- Plugin protocol — interoperates with
age-plugin-*binaries - Encrypt to multiple recipients
- ASCII armor support
- Pull-based streaming (
EncryptReader/DecryptReader) - Detached header APIs (
EncryptDetached/DecryptDetached) - Random-access decryption (
AgeRandomAccess) — seek into encrypted files - Header inspection without decryption (
AgeHeader.Parse) - Encrypted identity files (passphrase-protected)
- Recipients file parsing (
-Rstyle files with comments) - Fully interoperable — files produced by AgeSharp decrypt with
age,rage, and vice versa
Installation
dotnet add package AgeSharp
Usage
Encrypt and decrypt
using Age;
using Age.Recipients;
using var identity = X25519Identity.Generate();
var recipient = identity.Recipient;
using var input = new MemoryStream("Hello, age!"u8.ToArray());
using var encrypted = new MemoryStream();
AgeEncrypt.Encrypt(input, encrypted, recipient);
encrypted.Position = 0;
using var decrypted = new MemoryStream();
AgeEncrypt.Decrypt(encrypted, decrypted, identity);
Passphrase encryption
var passphrase = new ScryptRecipient("correct-horse-battery-staple");
using var input = new MemoryStream("Hello, age!"u8.ToArray());
using var encrypted = new MemoryStream();
AgeEncrypt.Encrypt(input, encrypted, passphrase);
encrypted.Position = 0;
using var decrypted = new MemoryStream();
AgeEncrypt.Decrypt(encrypted, decrypted, passphrase);
ASCII armor
AgeEncrypt.Encrypt(input, encrypted, armor: true, recipient);
// -----BEGIN AGE ENCRYPTED FILE-----
// YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSA...
// -----END AGE ENCRYPTED FILE-----
Multiple recipients
using var alice = X25519Identity.Generate();
using var bob = X25519Identity.Generate();
AgeEncrypt.Encrypt(input, encrypted, alice.Recipient, bob.Recipient);
// Either identity can decrypt
AgeEncrypt.Decrypt(encrypted, decrypted, bob);
SSH keys
var recipient = SshEd25519Recipient.Parse("ssh-ed25519 AAAA...");
var identity = SshEd25519Identity.CreateFromFile("/path/to/id_ed25519");
AgeEncrypt.Encrypt(input, encrypted, recipient);
encrypted.Position = 0;
AgeEncrypt.Decrypt(encrypted, decrypted, identity);
Post-quantum (ML-KEM-768-X25519)
using var identity = MlKem768X25519Identity.Generate();
var recipient = identity.Recipient;
AgeEncrypt.Encrypt(input, encrypted, recipient);
Pull-based streaming
Returns a readable Stream — header and key setup is eager, payload encryption/decryption is lazy (chunk-by-chunk on Read()).
// Encrypt: returns a Stream you read ciphertext from
using var encryptedStream = AgeEncrypt.EncryptReader(plaintext, recipient);
encryptedStream.CopyTo(networkStream);
// Decrypt: returns a Stream you read plaintext from
using var decryptedStream = AgeEncrypt.DecryptReader(ciphertext, identity);
decryptedStream.CopyTo(outputStream);
Detached headers
Splits the age header and payload into separate streams — useful for storing the header and payload in different locations.
// Encrypt with separate header and payload
AgeEncrypt.EncryptDetached(input, headerOutput, payloadOutput, recipient);
// Decrypt from separate streams
AgeEncrypt.DecryptDetached(headerInput, payloadInput, output, identity);
Random-access decryption
Seek into an encrypted file and decrypt individual chunks without reading the whole file — useful for encrypted archives, databases, and large files.
using var ra = new AgeRandomAccess(ciphertext, identity);
Console.WriteLine($"Plaintext length: {ra.PlaintextLength}");
// Read 100 bytes at offset 50000
var buf = new byte[100];
ra.ReadAt(50000, buf);
// Or get a seekable Stream
using var stream = ra.GetStream();
stream.Seek(50000, SeekOrigin.Begin);
stream.Read(buf);
Header inspection
Parse the header of an encrypted file without decrypting it.
var header = AgeHeader.Parse(stream);
Console.WriteLine($"Recipients: {header.RecipientCount}");
Console.WriteLine($"Armored: {header.IsArmored}");
Console.WriteLine($"Payload offset: {header.PayloadOffset}");
foreach (var stanza in header.Recipients)
Console.WriteLine($" {stanza.Type}: {stanza.Args[0]}");
Parse existing keys
using var identity = AgeKeygen.ParseIdentity("AGE-SECRET-KEY-1...");
var recipient = AgeKeygen.ParseRecipient("age1...");
Custom recipients and identities
Implement IRecipient and IIdentity to integrate custom key types,
remote secrets managers, or age plugins.
public class MyRecipient : IRecipient
{
public string? Label => null; // or a security label to prevent mixing
public Stanza Wrap(ReadOnlySpan<byte> fileKey)
{
// Wrap the file key using your custom scheme
return new Stanza("MyType", ["arg1"], wrappedKey);
}
}
public class MyIdentity : IIdentity
{
public byte[]? Unwrap(IReadOnlyList<Stanza> stanzas)
{
// Return the file key if matched, null if not
}
}
CLI
AgeSharp ships a CLI compatible with the age command.
# Encrypt
age -r age1... -o encrypted.age plaintext.txt
# Decrypt
age -d -i key.txt -o plaintext.txt encrypted.age
# Generate a key pair
age-keygen -o key.txt
# Inspect an encrypted file (no decryption needed)
age inspect encrypted.age
Feature comparison
See FEATURE_COMPARISON.md for a detailed comparison
with the Go reference implementation and Rust's rage.
See also
- age-encryption.org/v1 — the age format specification
- age — the reference Go implementation and CLI
- rage — a Rust implementation of age
- awesome-age — age plugins, tools, and integrations
| 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)
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.1.0-preview.4 | 30 | 2/25/2026 |
| 0.1.0-preview.3 | 29 | 2/25/2026 |
| 0.1.0-preview.2 | 36 | 2/25/2026 |
| 0.1.0-preview.1 | 33 | 2/24/2026 |