Virsimo.CryptFS
0.1.0
dotnet add package Virsimo.CryptFS --version 0.1.0
NuGet\Install-Package Virsimo.CryptFS -Version 0.1.0
<PackageReference Include="Virsimo.CryptFS" Version="0.1.0" />
<PackageVersion Include="Virsimo.CryptFS" Version="0.1.0" />
<PackageReference Include="Virsimo.CryptFS" />
paket add Virsimo.CryptFS --version 0.1.0
#r "nuget: Virsimo.CryptFS, 0.1.0"
#:package Virsimo.CryptFS@0.1.0
#addin nuget:?package=Virsimo.CryptFS&version=0.1.0
#tool nuget:?package=Virsimo.CryptFS&version=0.1.0
CryptFsNet
A C# port of gocryptfs: a library that reads and writes
gocryptfs encrypted directories, and a cryptfs command line tool built on it.
The on-disk format is the real thing, not a look-alike. Filesystems created by gocryptfs open here, and filesystems created here open in gocryptfs. This is verified by running the port against the example filesystems that ship with gocryptfs, covering every format generation from v0.7 to v2.2 and all three content-encryption backends.
src/CryptFsNet— the library (net10.0, no external dependencies).src/CryptFsNet.Cli— thecryptfscommand line tool.tests/CryptFsNet.Tests— 90 tests: RFC test vectors, gocryptfs interoperability, round trips, CLI.gocryptfs/— a checkout of upstream gocryptfs, used as the reference and as the source of test fixtures.
What is ported
| Area | Status |
|---|---|
| AES-256-GCM content encryption (default) | ✅ |
AES-SIV-512 content encryption (-aessiv) |
✅ |
XChaCha20-Poly1305 content encryption (-xchacha) |
✅ |
| EME filename encryption, per-directory IVs | ✅ |
Long names (gocryptfs.longname.*) |
✅ |
PlaintextNames, DeterministicNames, Raw64, LongNameMax, HKDF feature flags |
✅ |
| Pre-v1.3 filesystems (no HKDF, 96-bit key IV, padded base64) | ✅ |
scrypt master key wrapping, gocryptfs.conf read and write |
✅ |
| Symlinks (encrypted targets) | ✅ |
| Random-access read, write, truncate, sparse files | ✅ |
| Password change, master key export and import | ✅ |
| FUSE mounting | ❌ — no kernel filesystem; use extract/import |
Reverse mode (-reverse) |
❌ — the PathIv derivation is ported, the frontend is not |
| Extended attributes | ➖ — name encryption is ported, no xattr I/O |
| FIDO2-protected master keys | ➖ — detected and reported; open such a filesystem with --masterkey |
Control socket (-ctlsock) |
❌ — the encpath/decpath commands cover the same need |
Mounting is the one substantial gap. gocryptfs is a FUSE daemon; there is no portable managed
equivalent, and a Windows implementation would need a third-party driver (WinFsp or Dokan). The
library is structured so that a mount layer could sit on top of CryptFs and CryptFileStream
without changing anything below them.
Quick start
$ dotnet build -c Release
$ alias cryptfs='./src/CryptFsNet.Cli/bin/Release/net10.0/cryptfs'
$ cryptfs init myvault # create an encrypted directory
$ cryptfs import myvault ~/Documents # encrypt a tree into it
$ cryptfs ls -l myvault
$ cryptfs cat myvault notes/todo.md
$ cryptfs extract myvault ./restored # decrypt it all back out
$ cryptfs fsck myvault # verify every block authenticates
cryptfs help lists all commands: init, info, passwd, dumpmasterkey, ls, tree, cat,
get, readlink, put, mkdir, rm, mv, ln, extract, import, encpath, decpath,
fsck, xray, speed.
Passwords come from -p PASS, --passfile FILE, --extpass CMD, the CRYPTFS_PASSWORD
environment variable, or an interactive prompt. --masterkey HEX bypasses the password entirely.
Exit codes match gocryptfs (12 for a wrong password, 26 for fsck errors, and so on).
Library usage
using CryptFsNet.Fs;
// Create a filesystem
using (var fs = CryptFs.Create("myvault", new CryptFsInitOptions { Password = "hunter2" }))
{
fs.CreateDirectory("notes");
fs.WriteAllText("notes/todo.md", "- port gocryptfs\n");
}
// Open one gocryptfs made
using (var fs = CryptFs.Open("myvault", "hunter2"))
{
foreach (CryptFsEntry entry in fs.ListDirectory("notes"))
Console.WriteLine($"{entry.Name} {entry.Length}");
// Streams are seekable and support partial writes and truncation
using CryptFileStream stream = fs.OpenReadWrite("notes/todo.md");
stream.Seek(0, SeekOrigin.End);
stream.Write("- write a README\n"u8);
}
CryptFs works in plaintext paths (/ separated, relative to the vault root) and handles the
encrypted names, directory IV files and long-name sidecars underneath. EncryptPath and
DecryptPath expose the translation directly if you need it.
The lower layers are public too, so you can use them without the filesystem wrapper:
ConfigFile (gocryptfs.conf), CryptoCore (key derivation), ContentEnc (block encryption),
NameTransform (EME names), and the primitives in CryptFsNet.Crypto.
How it maps to gocryptfs
| gocryptfs | CryptFsNet |
|---|---|
internal/cryptocore |
CryptoCore, Crypto/ |
internal/contentenc |
ContentEnc, FileHeader |
internal/configfile |
Config/ConfigFile, Config/ScryptKdf, Config/FeatureFlag |
internal/nametransform |
Names/NameTransform, Names/DirIv, Names/LongNames, Names/Base64Url |
internal/siv_aead |
Crypto/AesSivCipher |
internal/stupidgcm |
Crypto/AesGcmCipher, Crypto/XChaCha20Poly1305Cipher |
internal/pathiv |
PathIv |
internal/fusefrontend |
Fs/CryptFs, Fs/CryptFileStream |
gocryptfs-xray |
cryptfs xray, cryptfs dumpmasterkey |
Cryptography that had to be written from scratch
The .NET base class library does not cover everything gocryptfs needs, so these are implemented in
src/CryptFsNet/Crypto and pinned by test vectors:
- AES-GCM with 128-bit nonces. The BCL's
AesGcmonly accepts 96-bit nonces, and there is no way to supply a pre-counter block of your own. GCM is therefore built on hardware-accelerated AES-ECB plus a table-driven GHASH. It is checked against the BCL where the two overlap (96-bit nonces) and against gocryptfs's own ciphertext for the 128-bit case. - AES-SIV (RFC 5297), including AES-CMAC and S2V. Verified against RFC 5297 appendix A.1 and A.2.
- XChaCha20-Poly1305, including HChaCha20.
System.Security.Cryptography.ChaCha20Poly1305is not available on every platform and does not expose HChaCha20. - scrypt (RFC 7914), on top of the BCL's PBKDF2. Verified against three RFC 7914 vectors.
- EME, the wide-block mode used for file names, ported from
github.com/rfjakob/eme.
Verification
$ dotnet test
Passed! - Failed: 0, Passed: 90, Skipped: 0, Total: 90
The interoperability tests read the seven example filesystems in
gocryptfs/tests/example_filesystems and the fixtures in gocryptfs/gocryptfs-xray/xray_tests,
which were all produced by the real gocryptfs. They check that:
- the password unlocks the master key (scrypt plus the AES-GCM key wrapping),
- file content decrypts and authenticates on all three backends,
- file names, directory IVs and long names decrypt,
- re-encrypting a decrypted name reproduces gocryptfs's bytes exactly — EME is deterministic, so this pins the encrypt direction, not just decryption,
cryptfs xrayoutput is byte-identical to the recordedgocryptfs-xrayoutput,dumpmasterkeyreturns the master key documented in gocryptfs's own test.
Note on the vendored checkout: the fixtures are binary files, so the
gocryptfscheckout must not have gone through CRLF conversion.core.autocrlfis set tofalseandcore.longpathstotrueingocryptfs/.git/configfor this reason. Without that,v1.3/gocryptfs.dirivcomes out 17 bytes instead of 16 and that filesystem fails to open.
Performance
cryptfs speed on a release build, 4 KiB blocks:
AES-GCM-256 337 MiB/s
AES-SIV-512 333 MiB/s
XChaCha20-Poly1305 75 MiB/s
Fast enough to be disk-bound for most work, but well short of gocryptfs with OpenSSL. The limit is GHASH for the AES modes and the managed ChaCha20 core for XChaCha20. A PCLMULQDQ GHASH would be the next significant win; it is deliberately not implemented, because the table version is portable and its correctness is pinned by the test vectors.
Security notes
- This is a port, not an audit. The format and algorithms follow gocryptfs, but the implementation is new code and has not been reviewed by anyone else.
- Managed memory cannot be reliably wiped: the garbage collector may copy a key before
CryptographicOperations.ZeroMemoryruns. Keys are zeroed where practical, which raises the bar without being a guarantee. - The table-driven GHASH and the AES-ECB keystream are not hardened against cache-timing attacks on the same machine. Tag comparison is constant time.
-p PASSputs the password in the process list. Prefer--passfile,--extpass, or the prompt.
License
gocryptfs itself is MIT licensed; see gocryptfs/LICENSE. This port, however, is proprietary — see
LICENSE.txt. No permission is granted to use, copy, modify, or distribute this
software without the copyright holder's prior written permission.
| 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
- No dependencies.
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 | 138 | 9/3/2026 |