encryptor

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 14, 2025 License: MIT Imports: 11 Imported by: 1

README ΒΆ

πŸ” Encryptor

Go Version Go Report Card

A lightweight, secure, and fast encryption service for Go applications using AES-256-GCM encryption.

✨ Features

  • πŸ”’ AES-256-GCM Encryption - Industry-standard authenticated encryption
  • πŸ”‘ Flexible Key Management - Support for random keys, user passwords, exported keys, and file-based keys
  • πŸ›‘οΈ PBKDF2 Key Derivation - Secure password-to-key transformation
  • πŸ“ Secure File Storage - Keys stored with restrictive permissions (0600)
  • πŸ”„ Key Export/Import - Safely share keys across services or persist for later use
  • ⚑ High Performance - Optimized for speed with minimal overhead
  • 🧡 Thread-Safe - 100% safe for concurrent operations with comprehensive race testing
  • βœ… Comprehensive Testing - 95%+ test coverage with benchmarks and race detection
  • πŸ“¦ Zero Dependencies - Uses only Go standard library + golang.org/x/crypto

πŸ“¦ Installation

go get github.com/AlexanderEl/encryptor

πŸš€ Quick Start

Command Line Tool

The easiest way to get started is using the CLI tool for encrypting and decrypting files:

# Clone the repository
git clone https://github.com/AlexanderEl/encryptor.git
cd encryptor

# Build the CLI tool
go build -o encryptor cmd/encryptor/main.go

# Encrypt a file (automatically generates passkey.txt)
./encryptor -op encrypt -file document.txt

# Decrypt the file (uses existing passkey.txt)
./encryptor -op decrypt -file document.txt.enc

See the CLI Usage section for complete documentation.

Library Usage
package main

import (
    "fmt"
    "log"
    
    "github.com/AlexanderEl/encryptor"
)

func main() {
    // Create a new encryption service
    service := encryptor.NewService()
    
    // Set a passkey (will be automatically derived to 32 bytes using PBKDF2)
    if err := service.SetNewPassKey([]byte("my-secret-key")); err != nil {
        log.Fatal(err)
    }
    
    // Encrypt data
    plaintext := []byte("super secret message")
    encrypted, err := service.Encrypt(plaintext)
    if err != nil {
        log.Fatal(err)
    }
    
    // Decrypt data
    decrypted, err := service.Decrypt(encrypted)
    if err != nil {
        log.Fatal(err)
    }
    
    fmt.Printf("Original:  %s\n", plaintext)
    fmt.Printf("Decrypted: %s\n", decrypted)
}
Auto-Generated Keys
// Let the service generate a secure random key
service := encryptor.NewService()

encrypted, err := service.Encrypt([]byte("secret data"))
// Key is automatically generated on first encryption
Password-Based Encryption
service := encryptor.NewService()

// Derive a secure key from a password using PBKDF2
password := "user-password-123"
if err := service.SetNewPassKeyFromPassword(password, nil); err != nil {
    log.Fatal(err)
}

encrypted, err := service.Encrypt([]byte("sensitive data"))
Key Export and Import
// Export a key from one service
service1 := encryptor.NewService()
service1.GeneratePassKey()

exportedKey, err := service1.ExportPassKey()
if err != nil {
    log.Fatal(err)
}

// Import the key into another service
service2 := encryptor.NewService()
if err := service2.SetExportedKey(exportedKey); err != nil {
    log.Fatal(err)
}

// Both services can now encrypt/decrypt each other's data
message := []byte("shared secret")
encrypted, _ := service1.Encrypt(message)
decrypted, _ := service2.Decrypt(encrypted)
// decrypted == message
Key Export/Import Use Cases
  1. Distributed Systems - Share keys across multiple servers

    // Central key management
    masterKey, _ := masterService.ExportPassKey()
    
    // Distribute to worker nodes
    for _, worker := range workers {
        worker.SetExportedKey(masterKey)
    }
    
  2. Key Persistence - Store derived keys for reuse

    // Derive once from password
    service.SetNewPassKeyFromPassword("user-password", nil)
    derivedKey, _ := service.ExportPassKey()
    
    // Store in secure storage (e.g., encrypted database)
    db.StoreKey(userID, derivedKey)
    
    // Later: Load and use without re-deriving
    storedKey := db.LoadKey(userID)
    newService.SetExportedKey(storedKey)
    
  3. Key Migration - Move between different storage mechanisms

    // Load from file
    oldService, _ := encryptor.LoadEncryptionServiceFromFile("key.txt")
    key, _ := oldService.ExportPassKey()
    
    // Store in database
    database.SaveKey(key)
    
Persistent Key Storage
// Generate and save key to file
service := encryptor.NewService()
service.SetWriteKeyToFile(true)
service.SetKeyFilePath("my-secret-key.txt")

if err := service.GeneratePassKey(); err != nil {
    log.Fatal(err)
}

// Later, load the key from file
loadedService, err := encryptor.LoadEncryptionServiceFromFile("my-secret-key.txt")
if err != nil {
    log.Fatal(err)
}

πŸ–₯️ CLI Usage

The encryptor comes with a command-line interface for easy file encryption and decryption.

Building the CLI
# Clone the repository
git clone https://github.com/AlexanderEl/encryptor.git
cd encryptor

# Build the CLI tool
go build -o encryptor cmd/encryptor/main.go

# Optionally, install it to your PATH
go install github.com/AlexanderEl/encryptor/cmd/encryptor@latest
Command Syntax
encryptor -op <operation> -file <path> [options]
Available Flags
Flag Description Required Default
-op Operation: encrypt or decrypt Yes -
-file Path to input file Yes -
-out Path to output file No Auto-generated
-key Path to passkey file No passkey.txt
-v Verbose output No false
-version Show version and exit No false
Encryption Examples
# Basic encryption (creates passkey.txt automatically)
./encryptor -op encrypt -file document.txt
# Output: document.txt.enc

# Encrypt with verbose output
./encryptor -op encrypt -file confidential.pdf -v
# Shows detailed progress and warnings

# Encrypt with custom output path
./encryptor -op encrypt -file data.json -out encrypted_data.bin

# Encrypt using a specific key file
./encryptor -op encrypt -file report.docx -key my-secret-key.txt

# Encrypt multiple files (bash example)
for file in *.txt; do
    ./encryptor -op encrypt -file "$file"
done
Decryption Examples
# Basic decryption (uses existing passkey.txt)
./encryptor -op decrypt -file document.txt.enc
# Output: document.txt

# Decrypt with verbose output
./encryptor -op decrypt -file confidential.pdf.enc -v

# Decrypt to a specific location
./encryptor -op decrypt -file encrypted_data.bin -out original_data.json

# Decrypt using a specific key file
./encryptor -op decrypt -file report.docx.enc -key my-secret-key.txt

# Decrypt multiple files (bash example)
for file in *.enc; do
    ./encryptor -op decrypt -file "$file"
done
CLI Output Behavior

Default Output Paths:

  • Encryption: Adds .enc extension
    • document.txt β†’ document.txt.enc
  • Decryption: Removes .enc extension or adds .dec
    • document.txt.enc β†’ document.txt
    • document.bin β†’ document.bin.dec

Normal Mode (default):

$ ./encryptor -op encrypt -file test.txt
Encrypted: test.txt β†’ test.txt.enc

Verbose Mode (-v flag):

$ ./encryptor -op encrypt -file test.txt -v

=========================================================
                                                         
    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ•—   β–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•—   β–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•— β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—
    β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β•β•β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ–ˆβ–ˆβ•— β–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β•šβ•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•”β•β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—  β–ˆβ–ˆβ•”β–ˆβ–ˆβ•— β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•”β• β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•
    β–ˆβ–ˆβ•”β•β•β•  β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘     β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—  β•šβ–ˆβ–ˆβ•”β•  β–ˆβ–ˆβ•”β•β•β•β•    β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—
    β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘   β–ˆβ–ˆβ•‘        β–ˆβ–ˆβ•‘   β•šβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•”β•β–ˆβ–ˆβ•‘  β–ˆβ–ˆβ•‘
    β•šβ•β•β•β•β•β•β•β•šβ•β•  β•šβ•β•β•β• β•šβ•β•β•β•β•β•β•šβ•β•  β•šβ•β•   β•šβ•β•   β•šβ•β•        β•šβ•β•    β•šβ•β•β•β•β•β• β•šβ•β•  β•šβ•β•
                                                         
         πŸ” Secure File Encryption Tool v1.1.0
                                                         
=========================================================

πŸ”’ Encrypting file: test.txt
   File size: 1234 bytes
   βœ“ New encryption key generated and saved to: passkey.txt
   ⚠️  Keep this key file secure - you'll need it for decryption!
   Encrypted size: 1262 bytes
   βœ“ Encrypted file saved to: test.txt.enc

βœ“ Operation completed successfully!
CLI Error Messages

The CLI provides clear error messages for common issues:

# Missing input file
$ ./encryptor -op encrypt -file nonexistent.txt
Error: input file does not exist: nonexistent.txt

# Missing key file for decryption
$ ./encryptor -op decrypt -file document.txt.enc
Error: passkey file not found: passkey.txt (needed for decryption)

# Wrong key or corrupted file
$ ./encryptor -op decrypt -file document.txt.enc
Error: decryption failed: cipher: message authentication failed (wrong key or corrupted file?)

# Invalid operation
$ ./encryptor -op invalid -file test.txt
Error: invalid operation 'invalid'. Use 'encrypt' or 'decrypt'
CLI Best Practices
  1. Keep Your Key File Safe

    # After encrypting, backup your key file
    cp passkey.txt ~/secure-backup/passkey-backup.txt
    
    # Set restrictive permissions
    chmod 600 passkey.txt
    
  2. Batch Processing

    # Encrypt all text files in a directory
    find . -name "*.txt" -exec ./encryptor -op encrypt -file {} \;
    
    # Decrypt all encrypted files
    find . -name "*.enc" -exec ./encryptor -op decrypt -file {} \;
    
  3. Use Different Keys for Different Projects

    # Project A
    ./encryptor -op encrypt -file project-a-data.txt -key keys/project-a.txt
    
    # Project B
    ./encryptor -op encrypt -file project-b-data.txt -key keys/project-b.txt
    
  4. Verify Encryption Worked

    # Encrypt a file
    ./encryptor -op encrypt -file important.txt
    
    # Try to view encrypted file (should be unreadable)
    cat important.txt.enc
    
    # Decrypt and verify
    ./encryptor -op decrypt -file important.txt.enc -out verified.txt
    diff important.txt verified.txt
    
Integration with Scripts
#!/bin/bash
# backup-and-encrypt.sh

BACKUP_DIR="/backup"
KEY_FILE="$HOME/.secrets/backup-key.txt"

# Create backup
tar -czf backup.tar.gz /important/data

# Encrypt backup
./encryptor -op encrypt -file backup.tar.gz -key "$KEY_FILE"

# Remove unencrypted backup
rm backup.tar.gz

# Move to backup location
mv backup.tar.gz.enc "$BACKUP_DIR/backup-$(date +%Y%m%d).tar.gz.enc"

echo "Backup completed and encrypted successfully!"

πŸ“– API Documentation

Core Methods
NewService() *Service

Creates a new encryption service with default settings.

SetNewPassKey(key []byte) error

Sets the encryption key with automatic PBKDF2 derivation. Keys are securely derived to 32 bytes.

Parameters:

  • key - Encryption key (max 32 bytes)

Returns: Error if key is empty or exceeds 32 bytes

Note: This method derives the key using PBKDF2. To set a pre-derived or exported key without derivation, use SetExportedKey().

SetExportedKey(key []byte) error

Sets a pre-derived encryption key without additional derivation. Use this for importing previously exported keys.

Parameters:

  • key - Pre-derived encryption key (16, 24, or 32 bytes for AES-128, AES-192, or AES-256)

Returns:

  • ErrEmptyPassKey if key is empty
  • ErrKeyTooShort if key is less than 16 bytes
  • ErrPassKeyTooLong if key exceeds 32 bytes

Important: Unlike SetNewPassKey(), this method does NOT derive the key. Use it for:

  • Importing keys exported via ExportPassKey()
  • Loading pre-derived keys from secure storage
  • Sharing keys across multiple service instances
// Example: Don't mix SetNewPassKey and SetExportedKey
password := []byte("my-password-1234")

s1 := encryptor.NewService()
s1.SetNewPassKey(password)  // Derives key using PBKDF2
encrypted, _ := s1.Encrypt(data)

s2 := encryptor.NewService()
s2.SetExportedKey(password)  // Uses password directly (NO derivation)
// s2.Decrypt(encrypted) will FAIL - keys don't match!

// Correct approach: Export the derived key
derivedKey, _ := s1.ExportPassKey()
s3 := encryptor.NewService()
s3.SetExportedKey(derivedKey)  // Now keys match
decrypted, _ := s3.Decrypt(encrypted)  // SUCCESS
SetNewPassKeyFromPassword(password string, salt []byte) error

Derives a secure 32-byte key from a password using PBKDF2 with 100,000 iterations.

Parameters:

  • password - User password
  • salt - Optional salt (auto-generated if nil)

Returns: Error if password is empty

Encrypt(data []byte) ([]byte, error)

Encrypts data using AES-256-GCM. Auto-generates a key if not set.

Parameters:

  • data - Plaintext to encrypt

Returns: Encrypted data with prepended nonce, or error

Decrypt(data []byte) ([]byte, error)

Decrypts AES-256-GCM encrypted data.

Parameters:

  • data - Ciphertext to decrypt

Returns: Decrypted plaintext, or error

GeneratePassKey() error

Generates a cryptographically secure random 32-byte key.

Returns: Error on failure

ExportPassKey() ([]byte, error)

Returns a copy of the current encryption key. Use with caution.

Returns: Key copy, or ErrPassKeyNotSet if key not set

Security Note: The exported key is a copy, so modifying it won't affect the service's internal key. However, treat exported keys with the same security as passwords.

ClearPassKey()

Securely zeros out the key from memory.

Configuration Methods
SetWriteKeyToFile(write bool)

Thread-safe setter to enable/disable writing keys to file.

GetWriteKeyToFile() bool

Thread-safe getter for the write-to-file setting.

SetKeyFilePath(path string)

Thread-safe setter for the key file path.

GetKeyFilePath() string

Thread-safe getter for the key file path.

File Operations
LoadEncryptionServiceFromFile(filePath string) (*Service, error)

Convenience function to load an encryption service from a key file.

Parameters:

  • filePath - Path to key file

Returns: Service instance, or error

πŸ”§ Configuration

Service Options
service := encryptor.NewService()

// Write generated keys to file
service.SetWriteKeyToFile(true)

// Custom key file location
service.SetKeyFilePath("/secure/path/encryption.key")
Constants
const (
    KeyByteLength          = 32      // AES-256 key size
    MinKeyByteLength       = 16      // Minimum key size (AES-128)
    DefaultPassKeyFileName = "passkey.txt"
    FilePermissions        = 0600    // Owner read/write only
    PBKDF2Iterations       = 100000  // Key derivation iterations
    SaltLength             = 16      // Salt size in bytes
)
Error Constants
var (
    ErrEmptyPassKey      = errors.New("passkey cannot be empty")
    ErrPassKeyTooLong    = errors.New("passkey exceeds maximum length of 32 bytes")
    ErrKeyTooShort       = errors.New("key must be at least 16 bytes for AES-128")
    ErrEmptyData         = errors.New("data cannot be empty")
    ErrInvalidCiphertext = errors.New("ciphertext too short")
    ErrPassKeyNotSet     = errors.New("passkey not set")
)

πŸ”’ Security Features

Encryption Algorithm
  • AES-256-GCM - Authenticated encryption with associated data (AEAD)
  • Unique Nonces - Random nonce for each encryption operation
  • Authentication Tags - Prevents tampering and ensures data integrity
Key Management
  • PBKDF2 Key Derivation - SHA-256 with 100,000 iterations
  • Secure Random Generation - Uses crypto/rand for key generation
  • Memory Protection - ClearPassKey() zeros memory before cleanup
  • File Permissions - Keys stored with 0600 (owner-only access)
  • Key Export/Import - Safely share keys with proper copying to prevent external modification
Thread Safety
  • Full Concurrency Support - All operations are thread-safe with optimized locking
  • Race Condition Free - Extensively tested with Go's race detector
  • Minimal Lock Duration - Expensive operations performed outside critical sections
  • Memory-Safe Key Management - Protected copying prevents concurrent modification issues
Best Practices

βœ… Never reuse keys across different applications
βœ… Store key files in secure locations with restricted permissions
βœ… Use password-based keys with strong, unique passwords
βœ… Call ClearPassKey() when done with sensitive operations
βœ… Never log or transmit raw encryption keys
βœ… Safe for concurrent use in multi-threaded applications
βœ… Use SetExportedKey() only for pre-derived keys, never for passwords
βœ… Export keys only when necessary and handle them as securely as passwords

⚑ Performance

Quick Reference:

Operation Speed Throughput
Encrypt 557 ns/op ~1.8M ops/sec
Decrypt 488 ns/op ~2.0M ops/sec
Key Generation 92 ns/op ~10.9M ops/sec
Password Derivation 11.8 ms/op Security feature

Benchmarks on Intel Core i9-14900HX (with thread-safe locking):

TEST_NAME                             ITERATIONS  AVG_ITER_DURATION    MEMORY_USED  NUM_MEMORY_ALLOCATIONS
BenchmarkEncrypt-32                      2168189        556.7 ns/op      1376 B/op       4 allocs/op
BenchmarkDecrypt-32                      2448208        487.6 ns/op      1328 B/op       3 allocs/op
BenchmarkGeneratePassKey-32             12290386        92.06 ns/op        32 B/op       1 allocs/op
BenchmarkSetPassKeyFromPassword-32            99     11848985 ns/op       788 B/op      11 allocs/op

Performance Highlights:

  • ⚑ Encryption: ~557 ns/op (~1.8 million ops/sec) - Thread-safe with minimal overhead
  • ⚑ Decryption: ~488 ns/op (~2.0 million ops/sec) - Optimized concurrent access
  • ⚑ Key Generation: ~92 ns/op (~10.9 million ops/sec) - Lock-free random generation
  • πŸ” Password Derivation: ~11.8 ms/op (intentionally slow for security - 100k PBKDF2 iterations)

Thread-Safety Impact: The implementation uses optimized read-write locking (sync.RWMutex) for safe concurrent access:

  • Read operations (encryption/decryption) can run concurrently
  • Write operations (key setting) have minimal lock duration
  • Expensive cryptographic operations performed outside critical sections
  • The baseline implementation was highly optimized (~420ns per encryption), operating near the theoretical limits of AES-GCM performance. Even minimal synchronization overhead becomes proportionally significant at this performance tier
  • Despite the relative overhead, absolute performance remains exceptional at ~1.8-2.0 million operations per second, making this trade-off worthwhile for production applications requiring concurrent access

Note: Password-based key derivation is intentionally slow to protect against brute-force attacks. This is a security feature, not a performance issue.

πŸ§ͺ Testing

The encryptor package includes comprehensive testing with multiple test categories.

Basic Testing
# Run all tests
go test -v

# Run tests with coverage
go test -cover

# Generate detailed coverage report
go test -coverprofile=coverage.out
go tool cover -html=coverage.out

# Run specific test
go test -v -run TestEncryptDecrypt

# Run benchmarks
go test -bench=. -benchmem
Race Condition Testing

The package includes extensive race condition tests to ensure 100% thread-safety. These tests use Go's built-in race detector to identify data races.

# Run ALL tests with race detection (RECOMMENDED)
go test -race

# Run tests with race detection and verbose output
go test -race -v

# Run only race-specific tests
go test -race -v -run Race

# Run race tests with coverage
go test -race -cover

# Run intensive stress test (takes ~1 second)
go test -race -v -run TestRaceStressTest

Race Test Categories:

  1. Concurrent Key Operations

    • TestRaceConcurrentSetPassKey - Multiple goroutines setting different keys
    • TestRaceConcurrentGeneratePassKey - Concurrent key generation
    • TestRaceSetPassKeyFromPasswordConcurrent - Password-based key derivation races
  2. Key Operations During Encryption/Decryption

    • TestRaceSetPassKeyWhileEncrypting - Key changes during encryption
    • TestRaceGeneratePassKeyWhileEncrypting - Key generation during encryption
    • TestRaceClearPassKeyWhileEncrypting - Clearing key during encryption
    • TestRaceClearPassKeyWhileDecrypting - Clearing key during decryption
  3. Auto-Generation Races

    • TestRaceEncryptWithAutoGenerateRace - Multiple encryptions triggering auto-generation
  4. Configuration Field Races

    • TestRaceConfigFieldsWhileOperating - Concurrent config changes during operations
    • TestRaceExportPassKeyWhileModifying - Exporting key while modifying
  5. Export/Import Operations

    • TestRaceExportPassKeyWhileModifying - Exporting key while modifying
  6. Mixed Operations

    • TestRaceMixedOperations - Realistic concurrent usage patterns
    • TestRaceStressTest - Intensive continuous operations for 1 second
  7. Different Keys Scenario

    • TestRaceEncryptDecryptDifferentKeys - Encryption/decryption with concurrent key changes
Continuous Integration Testing
# Complete CI test suite (what should run in CI/CD)
go test -race -cover -v -timeout 30s

# With JSON output for CI tools
go test -race -cover -json > test-results.json
Test Coverage

The package maintains 95%+ test coverage across:

  • βœ… All public API methods
  • βœ… Error conditions and edge cases
  • βœ… Concurrent operations
  • βœ… File I/O operations
  • βœ… Race conditions
  • βœ… Memory safety
  • βœ… Key export/import workflows
Performance Testing
# Run all benchmarks
go test -bench=.

# Run specific benchmark
go test -bench=BenchmarkEncrypt

# Run benchmarks with memory allocation stats
go test -bench=. -benchmem

# Run benchmarks multiple times for accuracy
go test -bench=. -benchtime=10s -count=5

# Compare benchmarks (requires benchstat tool)
go test -bench=. -benchmem > old.txt
# Make changes...
go test -bench=. -benchmem > new.txt
benchstat old.txt new.txt
Testing Best Practices
  1. Always Run Race Detection Locally

    # Before committing code
    go test -race -v
    
  2. Test Concurrent Scenarios

    # Run stress tests multiple times
    for i in {1..10}; do go test -race -run TestRaceStressTest; done
    
  3. Check for Memory Leaks

    go test -bench=. -benchmem -memprofile=mem.out
    go tool pprof mem.out
    
  4. Verify Coverage

    go test -cover -coverprofile=coverage.out
    go tool cover -func=coverage.out | grep total
    
Expected Test Results

When all tests pass, you should see:

PASS
coverage: 95%+ of statements
ok      github.com/AlexanderEl/encryptor    12.456s

Warning Signs:

  • ⚠️ WARNING: DATA RACE - Indicates a race condition (should not occur)
  • ⚠️ Coverage below 90% - May indicate untested code paths
  • ⚠️ Benchmark performance degradation >10% - May indicate performance regression

πŸ“‹ Examples

Example 1: Secure File Encryption
package main

import (
    "io/ioutil"
    "log"
    
    "github.com/AlexanderEl/encryptor"
)

func encryptFile(inputPath, outputPath, keyPath string) error {
    // Read file
    data, err := ioutil.ReadFile(inputPath)
    if err != nil {
        return err
    }
    
    // Load or create encryption service
    var service *encryptor.Service
    service, err = encryptor.LoadEncryptionServiceFromFile(keyPath)
    if err != nil {
        // Create new service if key doesn't exist
        service = encryptor.NewService()
        service.SetWriteKeyToFile(true)
        service.SetKeyFilePath(keyPath)
        if err := service.GeneratePassKey(); err != nil {
            return err
        }
    }
    
    // Encrypt
    encrypted, err := service.Encrypt(data)
    if err != nil {
        return err
    }
    
    // Write encrypted file
    return ioutil.WriteFile(outputPath, encrypted, 0644)
}
Example 2: Distributed System with Shared Keys
package main

import (
    "github.com/AlexanderEl/encryptor"
)

// Central key management service
type KeyManager struct {
    masterService *encryptor.Service
    masterKey     []byte
}

func NewKeyManager() (*KeyManager, error) {
    service := encryptor.NewService()
    if err := service.GeneratePassKey(); err != nil {
        return nil, err
    }
    
    key, err := service.ExportPassKey()
    if err != nil {
        return nil, err
    }
    
    return &KeyManager{
        masterService: service,
        masterKey:     key,
    }, nil
}

// Distribute key to worker nodes
func (km *KeyManager) CreateWorkerService() (*encryptor.Service, error) {
    worker := encryptor.NewService()
    if err := worker.SetExportedKey(km.masterKey); err != nil {
        return nil, err
    }
    return worker, nil
}

// Usage
func main() {
    manager, _ := NewKeyManager()
    
    // Create multiple workers with same key
    worker1, _ := manager.CreateWorkerService()
    worker2, _ := manager.CreateWorkerService()
    
    // All can encrypt/decrypt each other's data
    data := []byte("shared message")
    encrypted, _ := worker1.Encrypt(data)
    decrypted, _ := worker2.Decrypt(encrypted)
    // decrypted == data
}
Example 3: Environment-Based Keys
package main

import (
    "encoding/hex"
    "errors"
    "os"
    
    "github.com/AlexanderEl/encryptor"
)

func getServiceFromEnv() (*encryptor.Service, error) {
    keyHex := os.Getenv("ENCRYPTION_KEY")
    if keyHex == "" {
        return nil, errors.New("ENCRYPTION_KEY not set")
    }
    
    key, err := hex.DecodeString(keyHex)
    if err != nil {
        return nil, err
    }
    
    service := encryptor.NewService()
    // Use SetExportedKey for pre-derived keys
    if err := service.SetExportedKey(key); err != nil {
        return nil, err
    }
    
    return service, nil
}
Example 4: Concurrent Processing
package main

import (
    "io/ioutil"
    "sync"
    
    "github.com/AlexanderEl/encryptor"
)

func encryptMultipleFiles(files []string, service *encryptor.Service) error {
    var wg sync.WaitGroup
    errChan := make(chan error, len(files))
    
    // Process files concurrently - service is thread-safe
    for _, file := range files {
        wg.Add(1)
        go func(f string) {
            defer wg.Done()
            
            data, err := ioutil.ReadFile(f)
            if err != nil {
                errChan <- err
                return
            }
            
            encrypted, err := service.Encrypt(data)
            if err != nil {
                errChan <- err
                return
            }
            
            if err := ioutil.WriteFile(f+".enc", encrypted, 0644); err != nil {
                errChan <- err
            }
        }(file)
    }
    
    wg.Wait()
    close(errChan)
    
    // Check for errors
    if err := <-errChan; err != nil {
        return err
    }
    
    return nil
}
Example 5: Key Rotation Strategy
package main

import (
    "github.com/AlexanderEl/encryptor"
)

type EncryptionService struct {
    current *encryptor.Service
    old     *encryptor.Service
}

// Rotate to a new key while keeping old key for decryption
func (es *EncryptionService) RotateKey() error {
    newService := encryptor.NewService()
    if err := newService.GeneratePassKey(); err != nil {
        return err
    }
    
    // Keep old service for decrypting old data
    es.old = es.current
    es.current = newService
    
    return nil
}

// Encrypt always uses current key
func (es *EncryptionService) Encrypt(data []byte) ([]byte, error) {
    return es.current.Encrypt(data)
}

// Decrypt tries current key, falls back to old key
func (es *EncryptionService) Decrypt(data []byte) ([]byte, error) {
    decrypted, err := es.current.Decrypt(data)
    if err != nil && es.old != nil {
        // Try old key if current fails
        return es.old.Decrypt(data)
    }
    return decrypted, err
}

❌ Error Handling

The service defines several sentinel errors for clear error handling:

var (
    ErrEmptyPassKey      = errors.New("passkey cannot be empty")
    ErrPassKeyTooLong    = errors.New("passkey exceeds maximum length of 32 bytes")
    ErrKeyTooShort       = errors.New("key must be at least 16 bytes for AES-128")
    ErrEmptyData         = errors.New("data cannot be empty")
    ErrInvalidCiphertext = errors.New("ciphertext too short")
    ErrPassKeyNotSet     = errors.New("passkey not set")
)
Usage
service := encryptor.NewService()
_, err := service.Decrypt(data)

if errors.Is(err, encryptor.ErrPassKeyNotSet) {
    // Handle missing key
    service.GeneratePassKey()
} else if errors.Is(err, encryptor.ErrInvalidCiphertext) {
    // Handle corrupted data
    log.Println("Data appears to be corrupted")
} else if errors.Is(err, encryptor.ErrKeyTooShort) {
    // Handle key size validation errors
    log.Println("Key must be at least 16 bytes")
}

🀝 Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Development Setup
  1. Fork the repository
  2. Clone your fork: git clone https://github.com/YOUR_USERNAME/encryptor.git
  3. Create a feature branch: git checkout -b feature/amazing-feature
  4. Make your changes and add tests
  5. Run tests with race detection: go test -race -v
  6. Ensure coverage stays above 90%: go test -cover
  7. Validate performance with justification for any reduction: go test -bench=. -benchmem
  8. Commit your changes: git commit -m 'Add amazing feature'
  9. Push to the branch: git push origin feature/amazing-feature
  10. Open a Pull Request
Code Standards
  • Follow Effective Go guidelines
  • Add tests for new functionality (including race tests if concurrent)
  • Maintain test coverage above 90%
  • All new code must pass go test -race
  • Update documentation for API changes
  • Run go fmt and go vet before committing
  • Optimize for minimal lock duration in concurrent code
Testing Requirements

All pull requests must:

  • βœ… Pass go test -race without any race conditions
  • βœ… Maintain or improve test coverage
  • βœ… Include benchmarks for performance-critical changes
  • βœ… Pass all existing tests

πŸ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ™ Acknowledgments

πŸ“ž Support

⚠️ Security Considerations

When to Use This Library

βœ… Encrypting data at rest
βœ… Protecting sensitive configuration
βœ… Secure file storage
βœ… Application-level encryption
βœ… Multi-threaded/concurrent applications
βœ… Distributed systems requiring shared encryption keys

When NOT to Use This Library

❌ TLS/SSL connections (use crypto/tls)
❌ Password hashing (use bcrypt or argon2)
❌ Digital signatures (use crypto/rsa or crypto/ecdsa)
❌ End-to-end messaging (use specialized protocols)

Important Security Notes
  • Key Management: The security of your encrypted data depends entirely on the security of your keys. Never commit keys to version control.
  • Key Rotation: Implement regular key rotation for long-lived applications.
  • Compliance: Ensure your use case complies with relevant regulations (GDPR, HIPAA, etc.).
  • Threat Model: This library protects data at rest. It does not protect against attacks on the running process.
  • Thread Safety: All operations are safe for concurrent use, but key management operations will briefly block other operations.
  • Key Export: Exported keys should be handled with the same security precautions as passwords. Use secure channels for transmission and encrypted storage for persistence.
  • SetExportedKey vs SetNewPassKey: Never use SetExportedKey() with raw passwords. It's designed for pre-derived or exported keys only. For passwords, always use SetNewPassKeyFromPassword().

πŸ—ΊοΈ Roadmap

  • Add Argon2 support for password-based key derivation
  • Implement key rotation mechanism
  • Add streaming encryption for large files
  • Support for multiple encryption algorithms
  • Built-in key management service integration (AWS KMS, Vault)
  • Add context support for cancellable operations
  • Key export/import functionality for distributed systems
  • Provide CLI tool for file encryption
  • 100% thread-safe implementation with comprehensive race testing

πŸ“Š Version History

v1.1.0 (Current)
  • ✨ NEW: Key export/import functionality via ExportPassKey() and SetExportedKey()
  • πŸ”„ Support for sharing keys across distributed systems
  • πŸ”‘ Distinguish between derived keys (SetNewPassKey) and raw keys (SetExportedKey)
  • πŸ“ Added comprehensive examples for key export/import workflows
  • πŸ§ͺ Extended test coverage for key export/import scenarios
  • πŸ“š Updated documentation with key management best practices
  • πŸ› Added validation for minimum key size (16 bytes for AES-128)
v1.0.1
  • ✨ 100% thread-safe code with optimized locking strategy
  • πŸ§ͺ Comprehensive race condition test suite (15+ race-specific tests)
  • πŸ”’ Thread-safe configuration getters/setters
  • πŸš€ Maintains high performance (~1.6-1.9M ops/sec) despite thread-safety overhead
  • πŸ“ Updated documentation with testing guidelines
  • πŸ› Fixed race condition in key generation and file writing
  • βœ… All operations verified safe under Go race detector
v1.0.0
  • Initial release with AES-256-GCM encryption
  • PBKDF2 key derivation
  • File-based key management
  • Comprehensive test suite

Made with ❀️ by the Encryptor Team

If you find this library useful, please consider giving it a ⭐ on GitHub!

Documentation ΒΆ

Index ΒΆ

Constants ΒΆ

View Source
const (
	// KeyByteLength is the required length for AES-256 encryption keys
	KeyByteLength = 32

	// MinKeyByteLength is the minimum acceptable key length (AES-128)
	MinKeyByteLength = 16

	// DefaultPassKeyFileName is the default filename for storing encryption keys
	DefaultPassKeyFileName = "passkey.txt"

	// FilePermissions sets restrictive permissions (owner read/write only)
	FilePermissions = 0600

	// PBKDF2Iterations is the number of iterations for key derivation
	PBKDF2Iterations = 100000

	// SaltLength is the length of the salt used in key derivation
	SaltLength = 16
)

Variables ΒΆ

View Source
var (
	// ErrEmptyPassKey is returned when an empty passkey is provided
	ErrEmptyPassKey = errors.New("passkey cannot be empty")

	// ErrPassKeyTooLong is returned when passkey exceeds maximum length
	ErrPassKeyTooLong = errors.New("passkey exceeds maximum length of 32 bytes")

	// ErrKeyTooShort is returned when key is shorter than minimum length
	ErrKeyTooShort = errors.New("key must be at least 16 bytes for AES-128")

	// ErrEmptyData is returned when trying to encrypt/decrypt empty data
	ErrEmptyData = errors.New("data cannot be empty")

	// ErrInvalidCiphertext is returned when ciphertext is too short
	ErrInvalidCiphertext = errors.New("ciphertext too short")

	// ErrPassKeyNotSet is returned when operations require a passkey that hasn't been set
	ErrPassKeyNotSet = errors.New("passkey not set")
)

Functions ΒΆ

This section is empty.

Types ΒΆ

type Encryptor ΒΆ

type Encryptor interface {
	// SetExportedKey sets an existing key (caution dangerous)
	// This will override the existing key with incoming passKey - no key modification
	// Primary use case: decrypt data with exported key
	SetExportedKey(key []byte) error

	// SetNewPassKey sets the passkey and validates it
	SetNewPassKey(key []byte) error

	// SetNewPassKeyFromPassword derives a secure key from a password
	SetNewPassKeyFromPassword(password string, salt []byte) error

	// Encrypt encrypts data using AES-256-GCM
	Encrypt(data []byte) ([]byte, error)

	// Decrypt decrypts data using AES-256-GCM
	Decrypt(data []byte) ([]byte, error)

	// GeneratePassKey generates a cryptographically secure random passkey
	GeneratePassKey() error

	// ExportPassKey returns the passkey (use with caution)
	ExportPassKey() ([]byte, error)
}

Encryptor defines the interface for encryption operations

type Service ΒΆ

type Service struct {
	// contains filtered or unexported fields
}

Service implements the Encryptor interface

func LoadEncryptionServiceFromFile ΒΆ

func LoadEncryptionServiceFromFile(filePath string) (*Service, error)

LoadEncryptionServiceFromFile is a convenience function to load from file

func NewService ΒΆ

func NewService() *Service

NewService creates a new encryption service with default settings

func (*Service) ClearPassKey ΒΆ

func (s *Service) ClearPassKey()

ClearPassKey securely clears the passkey from memory

func (*Service) Decrypt ΒΆ

func (s *Service) Decrypt(data []byte) ([]byte, error)

Decrypt decrypts data using AES-256-GCM

func (*Service) Encrypt ΒΆ

func (s *Service) Encrypt(data []byte) ([]byte, error)

Encrypt encrypts data using AES-256-GCM

func (*Service) ExportPassKey ΒΆ

func (s *Service) ExportPassKey() ([]byte, error)

ExportPassKey returns a copy of the passkey (use with extreme caution)

func (*Service) GeneratePassKey ΒΆ

func (s *Service) GeneratePassKey() error

GeneratePassKey generates a cryptographically secure random passkey

func (*Service) GetEncryptionServiceFromFile ΒΆ

func (s *Service) GetEncryptionServiceFromFile(filePath string) (*Service, error)

GetEncryptionServiceFromFile creates a Service from a passkey file

func (*Service) GetKeyFilePath ΒΆ added in v1.0.1

func (s *Service) GetKeyFilePath() string

GetKeyFilePath returns the key file path (thread-safe)

func (*Service) GetWriteKeyToFile ΒΆ added in v1.0.1

func (s *Service) GetWriteKeyToFile() bool

GetWriteKeyToFile returns whether keys are written to file (thread-safe)

func (*Service) SetExportedKey ΒΆ added in v1.1.0

func (s *Service) SetExportedKey(passKey []byte) error

SetExportedKey sets an existing key (caution dangerous) This will override the existing key with incoming passKey - no key modification Primary use case: decrypt data with exported key

func (*Service) SetKeyFilePath ΒΆ added in v1.0.1

func (s *Service) SetKeyFilePath(path string)

SetKeyFilePath sets the key file path (thread-safe)

func (*Service) SetNewPassKey ΒΆ added in v1.1.0

func (s *Service) SetNewPassKey(passKey []byte) error

SetNewPassKey sets and validates the encryption passkey

func (*Service) SetNewPassKeyFromPassword ΒΆ added in v1.1.0

func (s *Service) SetNewPassKeyFromPassword(password string, salt []byte) error

SetNewPassKeyFromPassword derives a secure key from a user password using PBKDF2

func (*Service) SetWriteKeyToFile ΒΆ added in v1.0.1

func (s *Service) SetWriteKeyToFile(write bool)

SetWriteKeyToFile sets whether to write the key to file (thread-safe)

Directories ΒΆ

Path Synopsis
cmd
encryptor command

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL