A transparent encryption layer for the AbsFs filesystem abstraction, providing secure at-rest encryption with modern cryptographic primitives.
encryptfs is a filesystem wrapper that provides transparent encryption and decryption of file contents and optionally filenames. It implements the absfs.FileSystem interface, allowing it to be layered on top of any AbsFs-compatible filesystem.
- AES-256-GCM - Advanced Encryption Standard with 256-bit keys and Galois/Counter Mode for authenticated encryption
- ChaCha20-Poly1305 - Modern stream cipher with Poly1305 message authentication
Both cipher suites provide:
- Authenticated Encryption with Associated Data (AEAD)
- Protection against tampering and corruption
- 128-bit authentication tags
- Nonce/IV uniqueness guarantees
Protected Against:
- Unauthorized access to encrypted files at rest
- Data tampering and corruption (authenticated encryption)
- Known-plaintext attacks (with proper key management)
- Offline brute-force attacks (with strong key derivation)
Not Protected Against:
- Memory dumps while files are decrypted in memory
- Side-channel attacks (timing, cache)
- Compromised systems with keyloggers or malware
- Physical access attacks on running systems
- Metadata leakage (file sizes, access patterns, unless filename encryption is enabled)
-
Key Material Protection
- Keys must never be written to disk unencrypted
- Use secure memory clearing after key derivation
- Consider using OS keychain/keyring for key storage
- Rotate keys periodically
-
Nonce/IV Management
- Each encryption operation MUST use a unique nonce
- Nonce reuse with the same key catastrophically breaks security
- Implementation uses random nonces (96-bit for GCM, 192-bit for ChaCha20)
-
Authentication
- Always verify authentication tags before decrypting
- Reject any modified or corrupted ciphertext immediately
- Never expose unauthenticated plaintext
File Format Design
- Magic header for encrypted files
- Version field for future compatibility
- Cipher suite identifier
- Nonce/IV storage
- Authentication tag placement
- Optional metadata section (encrypted filename mappings)
Cipher Implementation
- AES-256-GCM encryption/decryption
- ChaCha20-Poly1305 encryption/decryption
- Secure nonce generation
- Key derivation integration
- AEAD interface abstraction
Basic File Operations
- Read encrypted files with transparent decryption
- Write files with transparent encryption
- Handle file headers and metadata
- Error handling and validation
Key Derivation Functions
- PBKDF2 with configurable iterations (minimum 100,000)
- Argon2id with memory-hard parameters
- Salt generation and storage
- Key caching with secure clearing
Key Provider Interface
- Pluggable key source abstraction
- Password-based key provider
- Environment variable key provider
- OS keychain/keyring integration
- Hardware security module (HSM) support preparation
Key Rotation
- Re-encryption support for key changes
- Bulk re-encryption utilities
- Multiple key support for migration
Deterministic Filename Encryption
- SIV mode (Synthetic IV) for deterministic encryption (RFC 5297)
- Preserves directory structure
- Allows path-based lookups
- Base64 URL-safe encoding for filesystem compatibility
- Same filename always encrypts to same ciphertext
Random Filename Encryption
- UUID-based encrypted filenames
- JSON metadata database for filename mappings
- Maximum security - no filename correlation
- Directory enumeration protection
- Persistent mapping storage
Configuration Options
- Three modes: None, Deterministic, Random
- Extension preservation option
- Configurable metadata path
- Transparent path translation
Chunk-Based Encryption (Implemented)
- Multi-chunk file format with 64 KB default chunks
- Efficient random access without decrypting entire file
- LRU cache for 16 chunks minimizes disk I/O
- Independent nonce per chunk for security
- Thread-safe operations with mutex locks
- Read-modify-write support for partial updates
- Configurable chunk sizes: 64 bytes to 16 MB
- Automatic mode selection via Config.ChunkSize
Usage Example
config := &encryptfs.Config{
Cipher: encryptfs.CipherAES256GCM,
KeyProvider: keyProvider,
ChunkSize: 64 * 1024, // Enable chunked encryption
}
fs, _ := encryptfs.New(base, config)Future Advanced Features
Compression Integration (Planned)
- Optional pre-encryption compression
- Cipher suite selection based on compressibility
- Compression detection
Access Control (Planned)
- Per-file key derivation
- Directory-based key hierarchies
- Multi-user support with key escrow
Hardware Acceleration
- AES-NI instruction set usage
- ARM crypto extensions
- Hardware offload detection
Caching Strategies
- Decrypted block caching
- Key derivation caching
- Metadata caching
Benchmarking
- Throughput measurements
- Latency profiling
- Memory usage optimization
package main
import (
"github.com/absfs/absfs"
"github.com/absfs/encryptfs"
"github.com/absfs/osfs"
)
func main() {
// Create base filesystem
base := osfs.New()
// Create encrypted filesystem with password-based key
config := &encryptfs.Config{
Cipher: encryptfs.CipherAES256GCM,
KeyProvider: encryptfs.NewPasswordKeyProvider(
[]byte("my-secure-password"),
encryptfs.Argon2idParams{
Memory: 64 * 1024, // 64 MB
Iterations: 3,
Parallelism: 4,
},
),
}
fs, err := encryptfs.New(base, config)
if err != nil {
panic(err)
}
// Use like any absfs.FileSystem
file, err := fs.Create("/secret.txt")
if err != nil {
panic(err)
}
// Writes are transparently encrypted
_, err = file.WriteString("This will be encrypted on disk")
file.Close()
// Reads are transparently decrypted
file, err = fs.Open("/secret.txt")
if err != nil {
panic(err)
}
defer file.Close()
data, _ := io.ReadAll(file)
fmt.Println(string(data)) // "This will be encrypted on disk"
}// PBKDF2 key derivation
keyProvider := encryptfs.NewPasswordKeyProvider(
[]byte("password"),
encryptfs.PBKDF2Params{
Iterations: 100000,
HashFunc: encryptfs.SHA256,
},
)
// Argon2id key derivation (recommended)
keyProvider := encryptfs.NewPasswordKeyProvider(
[]byte("password"),
encryptfs.Argon2idParams{
Memory: 64 * 1024, // 64 MB
Iterations: 3,
Parallelism: 4,
SaltSize: 32,
KeySize: 32,
},
)
// Environment variable key provider
keyProvider := encryptfs.NewEnvKeyProvider("ENCRYPTION_KEY")
// Custom key provider
type MyKeyProvider struct{}
func (p *MyKeyProvider) DeriveKey(salt []byte) ([]byte, error) {
// Custom key derivation logic
return key, nil
}
func (p *MyKeyProvider) GenerateSalt() ([]byte, error) {
// Generate salt
return salt, nil
}// AES-256-GCM (default, hardware accelerated on most platforms)
config := &encryptfs.Config{
Cipher: encryptfs.CipherAES256GCM,
}
// ChaCha20-Poly1305 (better for systems without AES-NI)
config := &encryptfs.Config{
Cipher: encryptfs.CipherChaCha20Poly1305,
}
// Auto-select based on hardware capabilities
config := &encryptfs.Config{
Cipher: encryptfs.CipherAuto,
}// No filename encryption (only content encrypted)
config := &encryptfs.Config{
FilenameEncryption: encryptfs.FilenameEncryptionNone,
}
// Deterministic filename encryption (SIV mode)
config := &encryptfs.Config{
FilenameEncryption: encryptfs.FilenameEncryptionDeterministic,
PreserveExtensions: true, // Keep .txt, .jpg, etc visible
}
// Random filename encryption with metadata database
config := &encryptfs.Config{
FilenameEncryption: encryptfs.FilenameEncryptionRandom,
MetadataPath: "/.encryptfs-metadata",
}// Chunk-based encryption for large files
config := &encryptfs.Config{
ChunkSize: 64 * 1024, // 64 KB chunks
EnableSeek: true, // Allow seeking within encrypted files
}
// Use the filesystem normally - chunking is transparent
file, _ := fs.Create("/large-video.mp4")
io.Copy(file, videoReader) // Efficiently handles large streams
file.Close()
// Seeking works within encrypted files
file, _ = fs.Open("/large-video.mp4")
file.Seek(1024*1024, io.SeekStart) // Seek to 1MB offsetPros:
- Simple implementation
- Preserves directory structure
- Easy debugging and administration
- Compatible with all filesystems
Cons:
- Filenames are visible
- Metadata leakage (file count, sizes, names)
- Directory structure is exposed
Use Case: Protection against disk theft where metadata leakage is acceptable
Pros:
- Preserves directory hierarchy
- Same filename always encrypts to same ciphertext
- Path-based lookups still work
- Reasonable security
Cons:
- Identical filenames in different directories reveal correlation
- Slightly weaker than random encryption
- Requires SIV-mode cipher
Use Case: Balance between security and usability, when directory structure should remain navigable
Pros:
- Maximum security for filenames
- No correlation between similar names
- Protects directory structure
Cons:
- Requires metadata database
- More complex implementation
- Database corruption risk
- Slower directory operations
Use Case: Maximum security scenarios, compliance requirements
Target performance characteristics (will be measured and documented during implementation):
- AES-256-GCM (with AES-NI): >2 GB/s on modern CPUs
- ChaCha20-Poly1305: >1 GB/s on modern CPUs
- Overhead: <5% compared to unencrypted I/O for sequential access
- Small file (<4KB): <100 microseconds overhead
- Key derivation (Argon2id): 50-200ms (tunable)
- Chunk encryption (64KB): <1ms
- Base overhead: <10 MB
- Per-file overhead: <100 KB
- Chunk buffer: Configurable (default 64 KB)
- Use
crypto/aesfrom Go's FIPS-validated build - AES-256-GCM is FIPS-approved
- ChaCha20-Poly1305 is NOT FIPS-approved (use AES for compliance)
- Key derivation should use PBKDF2 with SHA-256 for FIPS
- Encryption at rest satisfies many data protection requirements
- Key management is critical for compliance
- Consider "right to erasure" - secure key deletion ensures data is unrecoverable
- Document key lifecycle and retention policies
- NIST SP 800-38D: GCM mode specifications
- NIST SP 800-175B: Key derivation guidelines
- RFC 7539: ChaCha20-Poly1305 specification
- RFC 5297: SIV mode for deterministic encryption
params := encryptfs.PBKDF2Params{
Iterations: 100000, // Minimum recommended
HashFunc: encryptfs.SHA256,
SaltSize: 32, // 256 bits
KeySize: 32, // 256 bits for AES-256
}Characteristics:
- Widely supported and FIPS-approved
- Simple implementation
- CPU-intensive only (vulnerable to GPU attacks)
- Iterations should be tuned to ~100ms on target hardware
Use Case: FIPS compliance, compatibility with legacy systems
params := encryptfs.Argon2idParams{
Memory: 64 * 1024, // 64 MB
Iterations: 3, // Time parameter
Parallelism: 4, // CPU cores to use
SaltSize: 32, // 256 bits
KeySize: 32, // 256 bits
}Characteristics:
- Memory-hard function (resistant to GPU/ASIC attacks)
- Winner of Password Hashing Competition
- Configurable memory, time, and parallelism
- Recommended for new implementations
Use Case: Modern systems with no FIPS requirement, maximum resistance to brute-force
-
Salt Management
- Use cryptographically random salts
- Minimum 128 bits (16 bytes), recommend 256 bits
- Store salt with encrypted data
- Never reuse salts
-
Parameter Tuning
- Target 100-500ms derivation time for interactive use
- Higher iterations/memory for data-at-rest keys
- Balance security vs. user experience
-
Key Caching
- Cache derived keys in memory for session duration
- Clear keys from memory when done
- Consider time-limited caching
- Never cache to disk
Comprehensive test coverage will include:
- Unit tests for all encryption primitives
- Property-based testing for encryption/decryption round-trips
- Fuzzing for malformed ciphertext handling
- Integration tests with various base filesystems
- Performance benchmarks
- Security audits of key management
- Cross-platform compatibility tests
Contributions are welcome! Please ensure:
- All tests pass
- Code follows Go conventions
- Security-sensitive changes are well-documented
- Performance impacts are measured
- Cryptographic changes are reviewed carefully
If you discover a security vulnerability, please email security@absfs.dev (DO NOT open a public issue).
MIT License - see LICENSE file for details
- Uses Go's standard
cryptopackage for cryptographic primitives - Inspired by various encrypted filesystem implementations (EncFS, gocryptfs, CryFS)
- Part of the AbsFs filesystem abstraction project