WARNING: THIS IS NOT STABLE - USE AT YOUR OWN RISK
GrainFS is a transparent encryption layer that implements the billy.Filesystem interface from github.com/go-git/go-billy/v5. It provides seamless file content encryption and filename obfuscation while maintaining full compatibility with the billy ecosystem.
- Full Billy Interface Compatibility: Implements all billy interfaces (
Basic,Dir,Symlink,Chroot,TempFile) - Strong Encryption: AES-256-GCM for file content encryption with unique nonces
- Filename Obfuscation: AES-256-CTR with HMAC-SHA256 for secure filename encryption
- Key Derivation: PBKDF2 with SHA-256 (100,000 iterations) for secure key generation
- Transparent Operation: Works as a drop-in replacement for any billy filesystem
- Concurrent Safe: Thread-safe operations with proper synchronization
- Streaming Support: Efficient handling of large files
File Content Encryption:
- Algorithm: AES-256-GCM
- Nonce: 96-bit random nonce per file
- Format:
[nonce][encrypted_data][auth_tag] - Authentication: Built-in authentication with GCM mode
Filename Obfuscation:
- Algorithm: AES-256-CTR + HMAC-SHA256
- Encoding: Base64url for filesystem compatibility
- Collision Handling: Automatic counter suffixes
- Maximum Length: 200 characters
Key Derivation:
- Master Key: PBKDF2-SHA256 with 100,000 iterations
- Filename Key: Derived from master key with different salt
- Salt Storage: Stored in
.grainfs/config.json
underlying_filesystem/
├── .grainfs/
│ ├── config.json # Encrypted configuration
│ └── filemap.json # Encrypted filename mappings
├── obfuscated_filename_1 # Encrypted file
├── obfuscated_filename_2 # Encrypted file
└── obfuscated_dir_name/ # Obfuscated directory
├── .grainfs/
│ └── filemap.json # Directory-specific mappings
└── obfuscated_file # Encrypted file in subdirectory
go get github.com/NovaCove/grainfspackage main
import (
"fmt"
"io"
"log"
"github.com/NovaCove/grainfs"
"github.com/go-git/go-billy/v5/osfs"
)
func main() {
// Create underlying filesystem
underlying := osfs.New("/path/to/storage")
// Create encrypted filesystem
fs, err := grainfs.New(underlying, "my-secret-password")
if err != nil {
log.Fatal(err)
}
// Use like any billy.Filesystem
file, err := fs.Create("secret.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// Write encrypted data
_, err = file.Write([]byte("sensitive information"))
if err != nil {
log.Fatal(err)
}
// Read decrypted data
file, err = fs.Open("secret.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
data, err := io.ReadAll(file)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Decrypted: %s\n", data)
}// Directory operations
err := fs.MkdirAll("documents/private", 0755)
// File operations with flags
file, err := fs.OpenFile("data.txt", os.O_RDWR|os.O_CREATE, 0644)
// Directory listing
infos, err := fs.ReadDir("documents")
// File operations
err = fs.Rename("old.txt", "new.txt")
err = fs.Remove("unwanted.txt")
// Chroot for sandboxing
subFS, err := fs.Chroot("documents")type GrainFS struct {
// Internal fields
}
func New(underlying billy.Filesystem, password string) (*GrainFS, error)billy.Filesystem- Core filesystem operationsbilly.Basic- Basic file operations (Create, Open, OpenFile, Stat, Rename, Remove, Join)billy.Dir- Directory operations (ReadDir, MkdirAll)billy.Symlink- Symbolic link operations (Lstat, Symlink, Readlink)billy.Chroot- Chroot operations (Chroot, Root)billy.TempFile- Temporary file operations (TempFile)
All standard billy file operations are supported:
// File creation and access
file, err := fs.Create(filename)
file, err := fs.Open(filename)
file, err := fs.OpenFile(filename, flag, perm)
// File information
info, err := fs.Stat(filename)
// File manipulation
err := fs.Rename(oldpath, newpath)
err := fs.Remove(filename)
// Directory operations
infos, err := fs.ReadDir(path)
err := fs.MkdirAll(path, perm)- AES-256-GCM: Provides both confidentiality and authenticity
- Unique Nonces: Each file write uses a cryptographically random nonce
- Key Derivation: PBKDF2 with high iteration count protects against brute force
- Authenticated Encryption: Prevents tampering with encrypted data
- Deterministic Obfuscation: Same filename always produces same obfuscated result
- HMAC Authentication: Prevents filename tampering
- Collision Resistance: Automatic handling of hash collisions
- Length Limits: Prevents filesystem compatibility issues
- Memory Safety: Keys are zeroed when possible
- Error Handling: No information leakage through error messages
- Constant Time: Filename comparisons use constant-time operations
- Atomic Operations: Filemap updates are atomic to prevent corruption
BenchmarkGrainFSWrite-12 161834 7566 ns/op
BenchmarkGrainFSRead-12 520732 2363 ns/op
- Write Performance: ~7.5μs per 1KB write operation
- Read Performance: ~2.4μs per 1KB read operation
- Memory Usage: Minimal overhead, streaming encryption/decryption
- Scalability: Concurrent operations supported
- Seek Operations: Limited seeking support for encrypted files
- Truncation: Only truncation to zero size supported
- ReadAt Performance: Requires full file decryption for random access
- File Size: Encrypted files have small overhead (nonce + auth tag)
- Block-based encryption for better random access
- Streaming ReadAt implementation
- Optimized seek operations
- Compression support
Run the comprehensive test suite:
# Run all tests
go test -v
# Run specific tests
go test -run TestGrainFSBasicOperations
go test -run TestGrainFSEncryption
# Run benchmarks
go test -bench=.
# Test with real filesystem
go test -run TestGrainFSWithOSFSSee the example/ directory for complete usage examples:
cd example
go run main.goGrainFS is fully compatible with the billy ecosystem:
- go-git: Can be used as storage backend for Git repositories
- Billy utilities: Works with all billy-compatible tools
- Filesystem adapters: Compatible with osfs, memfs, and other billy implementations
- Requires Go 1.19 or later
- Uses
golang.org/x/cryptofor cryptographic operations
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
MIT
For security issues, please email [email protected] instead of using the issue tracker.