Skip to content

Time-File/go-file-crypto

Repository files navigation

go-file-crypto

Zero-dependency file encryption for Go, fully compatible with @time-file/browser-file-crypto.

Go Reference Go Report Card stars License: MIT

English | 한국어

Features

  • 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

Why?

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.

Comparison

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

Requirements

  • Go 1.24+
    • Uses standard library crypto/pbkdf2 (added in Go 1.24)

Install

go get github.com/Time-File/go-file-crypto

Quick Start

package 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"
}

API

Standard Encryption / Decryption

// 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",
})

Auto Encryption

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",
})

Streaming Encryption / Decryption

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",
})

File Helpers

// 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",
})

Keyfile Management

// 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{})

Detection Utilities

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 format

Progress Tracking

enc, err := filecrypto.Encrypt(data, filecrypto.EncryptOptions{
	Password: "secret",
	OnProgress: func(p filecrypto.Progress) {
		fmt.Printf("%s: %d%%\n", p.Phase, p.Progress)
	},
})

Error Handling

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

Security

Spec

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)

File Format

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]

Notes

  • 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

Browser Compatibility

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 Integration

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.tfkey

Testing

This repository includes three layers of testing:

  1. Unit tests - Standard encrypt/decrypt round-trips for all modes
  2. Deterministic tests - Fixed randomness for reproducible byte-level verification
  3. Browser interop tests - Cross-runtime vector tests against WebCrypto output
go test ./...
go vet ./...

Documentation

License

MIT


Made by timefile.co

About

Zero-dependency file encryption for Go with streaming AES-256-GCM.

Resources

License

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors