Skip to content

Latest commit

 

History

32 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

* AgeSharp

C# implementation of the [[https://github.com/FiloSottile/age][Age]] file encryption format.

=Age= is a simple, modern and secure file encryption tool.

*Note: This is not affiliated with the official Age project.*

** Known Limitations

Some features from the Age specification are not yet implemented:
- Post-quantum encryption (ML-KEM/ML-DSA)

** Prerequisites

- .NET 10 SDK

** Dependencies

- [[https://www.nuget.org/packages/NSec.Cryptography/][NSec.Cryptography]] - X25519, ChaCha20-Poly1305, HKDF
- [[https://www.nuget.org/packages/Norgerman.Cryptography.Scrypt/][Norgerman.Cryptography.Scrypt]] - Scrypt for passphrases

** Library Usage

AgeSharp provides a complete C# library for encrypting and decrypting data.

#+begin_src csharp
using AgeSharp.Core;

// Generate a new identity
var identity = AgeKeyGenerator.GenerateX25519Key();
var recipient = identity.ToRecipient();

// Encrypt data
var data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var encrypted = await Age.EncryptAsync(data, new[] { recipient });

// Decrypt data
var decrypted = await Age.DecryptAsync(encrypted, new[] { identity });
var message = System.Text.Encoding.UTF8.GetString(decrypted);
#+end_src

*** Using Passphrase

#+begin_src csharp
using AgeSharp.Core;

// Create a passphrase recipient
var passphrase = "your-passphrase";
var recipient = AgeParser.ParseRecipient(passphrase);

// Encrypt data with passphrase
var data = System.Text.Encoding.UTF8.GetBytes("Hello, World!");
var encrypted = await Age.EncryptAsync(data, new[] { recipient });

// Decrypt using passphrase identity
var identity = AgeParser.ParseIdentity(passphrase);
var decrypted = await Age.DecryptAsync(encrypted, new[] { identity });
var message = System.Text.Encoding.UTF8.GetString(decrypted);
#+end_src

** Security Notes

*** Memory Security

For security, private keys and passphrases are stored as byte arrays and are zeroed in memory immediately after each encryption/decryption operation. This prevents sensitive key material from persisting in memory longer than necessary.

- X25519 private keys are zeroed after each ~Unwrap()~ call
- Scrypt passphrases are zeroed after each ~CreateStanza()~ or ~Unwrap()~ call
- To decrypt multiple files, re-read the key file or re-enter the passphrase

*** Passphrase Identities

Passphrase identities cannot be serialized to a string. This prevents accidental leakage of passphrases in logs or debug output. Use ~AgeParser.ParseIdentity()~ with the original passphrase to decrypt.

*** Using ASCII Armor

#+begin_src csharp
using AgeSharp.Core;

// Encrypt with armor
var options = new EncryptionOptions { Armor = true };
using var output = new MemoryStream();
await Age.EncryptAsync(input, output, recipients, options);

var armored = System.Text.Encoding.ASCII.GetString(output.ToArray());
// Output: -----BEGIN AGE ENCRYPTED FILE-----
// ...
// -----END AGE ENCRYPTED FILE-----
#+end_src

*** Parsing Keys

#+begin_src csharp
using AgeSharp.Core;

// Parse a recipient from a string
var recipient = AgeParser.ParseRecipient("age1n0szz7y4u757g66s2qkmv7tmrtpq55h6ve9tvugutwttwtcs99jsqlrx6j");

// Parse an identity from a string
var identity = AgeParser.ParseIdentity("AGE-SECRET-KEY-1RJD99JTCZLF60ACFAH9T34FF7XPFH5JF79VJNL8X3GA856KMDU5SZ0UU85");

// Parse recipients from a file
var recipients = AgeParser.ParseRecipientsFile("recipients.txt");

// Parse identities from a file
var identities = AgeParser.ParseIdentitiesFile("identities.txt");
#+end_src

*** Inspecting Files

#+begin_src csharp
using AgeSharp.Core;

// Inspect an encrypted file
var info = AgeInspector.Inspect("file.age");

Console.WriteLine($"Version: {info.Version}");
Console.WriteLine($"Armor: {info.IsArmor}");
Console.WriteLine($"Post-quantum: {info.PostQuantum}");
Console.WriteLine($"Stanza types: {string.Join(", ", info.StanzaTypes)}");
Console.WriteLine($"Header size: {info.HeaderSize}");
Console.WriteLine($"Payload size: {info.PayloadSize}");
#+end_src

** Build

#+begin_src shell
dotnet build
#+end_src

** Test

#+begin_src shell
dotnet test
#+end_src

** CLI Usage

*** age-keygen

Generate a new X25519 identity key pair.

#+begin_src shell
# Generate key to stdout
dotnet run --project src/AgeSharp.CLI.KeyGen

# Generate key to file
dotnet run --project src/AgeSharp.CLI.KeyGen -- -o key.txt

# Convert identity to recipient (print public key)
dotnet run --project src/AgeSharp.CLI.KeyGen -- -y -i key.txt
#+end_src

Output format:
#+begin_example
# created: 2026-02-27T00:30:33+02:00
# public key: age1n0szz7y4u757g66s2qkmv7tmrtpq55h6ve9tvugutwttwtcs99jsqlrx6j
AGE-SECRET-KEY-1RJD99JTCZLF60ACFAH9T34FF7XPFH5JF79VJNL8X3GA856KMDU5SZ0UU85
#+end_example

*** age (main CLI)

Encrypt or decrypt files using X25519 keys or passphrases.

#+begin_src shell
# Encrypt a file to a recipient
dotnet run --project src/AgeSharp.CLI.Age -- -r age1n0szz7y4u757g66s2qkmv7tmrtpq55h6ve9tvugutwttwtcs99jsqlrx6j -o output.txt.age input.txt

# Encrypt with ASCII armor
dotnet run --project src/AgeSharp.CLI.Age -- -r age1n0szz7y4u757g66s2qkmv7tmrtpq55h6ve9tvugutwttwtcs99jsqlrx6j -a -o output.txt.age input.txt

# Decrypt a file
dotnet run --project src/AgeSharp.CLI.Age -- --decrypt -i key.txt -o output.txt input.txt.age

# Encrypt to multiple recipients
dotnet run --project src/AgeSharp.CLI.Age -- -r recipient1 -r recipient2 -o output.txt.age input.txt
#+end_src

**** Options

| Option | Description |
|--------|-------------|
| =-e=, =--encrypt= | Encrypt mode (default) |
| =-d=, =--decrypt= | Decrypt mode |
| =-r=, =--recipient= | Recipient public key or passphrase (can repeat) |
| =-R=, =--recipients-file= | File containing recipients (can repeat) |
| =-i=, =--identity= | Identity file for decryption (can repeat) |
| =-a=, =--armor= | Use ASCII armor (PEM encoding) |
| =-p=, =--passphrase= | Prompt for passphrase to encrypt |
| =-o=, =--output= | Output file path |
| =-h=, =--help= | Show help |

*** age-inspect

Inspect an age encrypted file to view its metadata.

#+begin_src shell
# Inspect a file
dotnet run --project src/AgeSharp.CLI.Inspect -- file.age

# Inspect and output as JSON
dotnet run --project src/AgeSharp.CLI.Inspect -- --json file.age
#+end_src

** Project Structure

#+begin_example
AgeSharp/
├── src/
│   ├── AgeSharp.Core/           # Core library
│   │   ├── Encoding/            # Base64, Bech32 utilities
│   │   ├── Headers/             # Age header parsing/generation
│   │   ├── Keys/                # Key types (X25519, etc.)
│   │   └── Exceptions/          # Custom exceptions
│   ├── AgeSharp.CommandLine/   # Command line parsing
│   ├── AgeSharp.CLI.Age/        # Main CLI (age)
│   ├── AgeSharp.CLI.KeyGen/     # Key generator (age-keygen)
│   └── AgeSharp.CLI.Inspect/    # File inspector (age-inspect)
├── tests/
│   └── AgeSharp.Tests/          # Unit tests
├── doc/                         # Documentation
│   └── requirements.org         # Detailed requirements
├── README.org                   # This file
└── LICENSE                      # Apache 2.0 license
#+end_example

** Specification

See the [[https://age-encryption.org/v1][Age specification]] for detailed format information.

** License

Licensed under the Apache License, Version 2.0. See [[file:LICENSE][LICENSE]] for details.

About

An Age implementation in C#

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages