Zero-dependency file encryption for Go, fully compatible with @time-file/browser-file-crypto.
English | 한국어
- Zero-Dependency - Go standard library only (Go 1.24+)
- Browser Compatible - Identical wire format with
@time-file/browser-file-crypto - AES-256-GCM - Industry-standard authenticated encryption
- Password & Keyfile - Two modes for different use cases
- Streaming Support - Memory-efficient large file handling via
io.Reader/io.Writer - Auto-Detection - Automatically detects encryption format and decrypts
- Progress Callbacks - Track encryption/decryption progress
- Custom Profiles - Configurable salt, IV, tag, chunk sizes and markers
The time-file ecosystem already has browser-native encryption with @time-file/browser-file-crypto. This module provides the same data format in Go, so encrypted files move seamlessly between runtimes:
Browser ──encrypt──► .enc file ──decrypt──► Go server
Go CLI ──encrypt──► .enc file ──decrypt──► Browser
Without this library, you'd need to manually handle:
- ❌ Matching marker bytes and header layouts
- ❌ Identical PBKDF2 parameters and salt handling
- ❌ Little-endian chunk size encoding
- ❌ Per-chunk IV derivation for streaming
- ❌ GCM tag appending behavior differences
This library handles it all.
// ✅ With go-file-crypto
enc, _ := filecrypto.Encrypt(data, filecrypto.EncryptOptions{Password: "secret"})
dec, _ := filecrypto.Decrypt(enc, filecrypto.DecryptOptions{Password: "secret"})Done. Files encrypted in the browser can be decrypted in Go, and vice versa.
| Feature | crypto/aes (direct) |
age | go-file-crypto |
|---|---|---|---|
| Browser compatible | ❌ | ❌ | ✅ |
| Zero dependency | ✅ | ❌ | ✅ |
| File-focused API | ❌ | ✅ | ✅ |
| Streaming support | Manual | ✅ | ✅ |
| Password + Keyfile | Manual | ❌ | ✅ |
| Auto-detect format | ❌ | ❌ | ✅ |
| Progress callbacks | ❌ | ❌ | ✅ |
- Go 1.24+
- Uses standard library
crypto/pbkdf2(added in Go 1.24)
- Uses standard library
go get github.com/Time-File/go-file-cryptopackage main
import (
"fmt"
filecrypto "github.com/Time-File/go-file-crypto"
)
func main() {
plain := []byte("hello-go-file-crypto")
// Encrypt
enc, err := filecrypto.Encrypt(plain, filecrypto.EncryptOptions{
Password: "secret-password",
})
if err != nil {
panic(err)
}
// Decrypt
dec, err := filecrypto.Decrypt(enc, filecrypto.DecryptOptions{
Password: "secret-password",
})
if err != nil {
panic(err)
}
fmt.Println(string(dec)) // "hello-go-file-crypto"
}// Password-based
encrypted, err := filecrypto.Encrypt(data, filecrypto.EncryptOptions{
Password: "secret",
})
// Keyfile-based
encrypted, err := filecrypto.Encrypt(data, filecrypto.EncryptOptions{
KeyData: "<base64-encoded-32-byte-key>",
})
// Decrypt (auto-detects password vs keyfile from marker byte)
plain, err := filecrypto.Decrypt(encrypted, filecrypto.DecryptOptions{
Password: "secret",
})Automatically switches between standard and streaming based on data size:
encrypted, err := filecrypto.EncryptAuto(data, filecrypto.AutoEncryptOptions{
Password: "secret",
AutoStreaming: true,
StreamingThreshold: 100 * 1024 * 1024, // 100MB (default)
})
// Decrypt handles both formats automatically
plain, err := filecrypto.Decrypt(encrypted, filecrypto.DecryptOptions{
Password: "secret",
})For large files using io.Reader / io.Writer:
// Encrypt
err := filecrypto.EncryptReader(src, dst, filecrypto.StreamEncryptOptions{
Password: "secret",
ChunkSize: 64 * 1024, // 64KB (default)
})
// Decrypt
err = filecrypto.DecryptReader(srcEnc, dstPlain, filecrypto.StreamDecryptOptions{
Password: "secret",
})// Encrypt file (always writes streaming format)
err := filecrypto.EncryptFileStream("plain.bin", "encrypted.bin", filecrypto.StreamEncryptOptions{
Password: "secret",
})
// Decrypt file (auto-detects standard or streaming)
err = filecrypto.DecryptFile("encrypted.bin", "decrypted.bin", filecrypto.DecryptOptions{
Password: "secret",
})// Generate keyfile
kf, err := filecrypto.GenerateKeyFile()
// kf.Key = base64-encoded 32-byte key
// Compute hash for server-side verification
hash, err := filecrypto.ComputeKeyFileHash(kf.Key)
// Parse uploaded keyfile
parsed, err := filecrypto.ParseKeyFile(jsonBytes, filecrypto.Profile{})t := filecrypto.DetectEncryptionType(data, filecrypto.Profile{})
// "password" | "keyfile" | "password-stream" | "keyfile-stream" | "unknown"
ok := filecrypto.IsEncryptedData(data, filecrypto.Profile{})
// true | false
isStream := filecrypto.IsStreamingEncryption(t)
// true if streaming formatenc, err := filecrypto.Encrypt(data, filecrypto.EncryptOptions{
Password: "secret",
OnProgress: func(p filecrypto.Progress) {
fmt.Printf("%s: %d%%\n", p.Phase, p.Progress)
},
})plain, err := filecrypto.Decrypt(encrypted, filecrypto.DecryptOptions{
Password: "wrong",
})
if err != nil {
if filecrypto.IsCryptoErrorCode(err, filecrypto.ErrInvalidPassword) {
fmt.Println("Wrong password")
}
}Error codes: ErrInvalidInput, ErrPasswordRequired, ErrKeyfileRequired, ErrInvalidPassword, ErrInvalidKeyfile, ErrInvalidEncryptedData, ErrEncryptionFailed, ErrDecryptionFailed, ErrUnsupportedFormat
| Component | Value |
|---|---|
| Algorithm | AES-256-GCM |
| Key Derivation | PBKDF2 (SHA-256, 100,000 iterations) |
| Salt | 16 bytes (random per encryption) |
| IV | 12 bytes (random per encryption) |
| Auth Tag | 16 bytes |
| Key Size | 256 bits (32 bytes) |
Password-encrypted (standard):
=> [0x01] + [salt:16] + [iv:12] + [ciphertext + auth_tag:16]
Keyfile-encrypted (standard):
=> [0x02] + [iv:12] + [ciphertext + auth_tag:16]
Password-encrypted (streaming):
=> [0x11] + [version:1] + [chunkSize:4 LE] + [salt:16] + [baseIV:12] + [chunks...]
Keyfile-encrypted (streaming):
=> [0x12] + [version:1] + [chunkSize:4 LE] + [baseIV:12] + [chunks...]
Each streaming chunk:
=> [chunkLength:4 LE] + [ciphertext + auth_tag:16]
- Random IV/salt per encryption = no identical ciphertexts
- AES-GCM = authenticated encryption (tamper detection)
- 100,000 PBKDF2 iterations = brute-force resistant
- Per-chunk authentication in streaming mode
This module is intentionally compatible with @time-file/browser-file-crypto.
Files encrypted in the browser can be decrypted in Go, and files encrypted in Go can be decrypted in the browser. The wire format is byte-for-byte identical.
timefile-cli uses this module for local file encryption before upload:
timefile upload secret.zip --crypto-password "my-pass"
timefile upload secret.zip --crypto-keyfile ./mykey.tfkeyThis repository includes three layers of testing:
- Unit tests - Standard encrypt/decrypt round-trips for all modes
- Deterministic tests - Fixed randomness for reproducible byte-level verification
- Browser interop tests - Cross-runtime vector tests against WebCrypto output
go test ./...
go vet ./...- Encryption & Decryption - Core API reference
- Streaming - Large file handling
- Keyfile Management - Keyfile generation and usage
- Type Detection - Encryption type detection
- Error Handling - Error codes and patterns
- Security - Cryptographic specifications
- Wire Format - Browser compatibility spec
- Testing - Testing guide and vectors
- Changelog