yEncBodyEnc

package module
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Oct 27, 2025 License: MIT Imports: 10 Imported by: 0

README

Godoc

yEnc Body Encryption - Go Implementation

This is the reference Go implementation of the yEnc Body Encryption Standard, providing XChaCha20-Poly1305 authenticated encryption for binary data before yEnc encoding while maintaining full compatibility with existing yEnc parsers and protocols.

Overview

The yEnc Body Encryption Standard allows encryption of binary file data before yEnc encoding using XChaCha20-Poly1305 authenticated encryption with Argon2id key derivation and cryptographically secure random salt generation. The salt is managed within the Cipher instance and can be accessed via the Salt() method for sharing between encryption and decryption operations. Encrypted binary data maintains the same byte length as the original while providing both confidentiality and integrity protection, ensuring secure transmission over Usenet while remaining compatible with standard yEnc tools.

Features

  • XChaCha20-Poly1305: Industry-standard authenticated encryption for both confidentiality and integrity
  • Random Salt Generation: Cryptographically secure random salt per cipher instance using crypto/rand
  • Strong Security: Argon2id key derivation with time=1, memory=64MB, threads=4 parameters
  • Deterministic Nonces: Consistent nonce derivation from key and segment index prevents reuse
  • Segment Support: Different nonces for multi-part yEnc files with same cipher instance
  • Authentication Tags: 128-bit Poly1305 tags prevent tampering and ensure data integrity
  • Size Preservation: Encrypted data maintains exact byte length as original
  • High Performance: ~3 GB/s encryption throughput with optimized cipher caching
  • Comprehensive Testing: 93.4% test coverage with extensive validation
  • Full Compatibility: Works with existing yEnc parsers and protocols

Installation

go get github.com/Tensai75/go-yenc-body-encryption

Usage

Basic Example
package main

import (
    "fmt"
    "log"

    "github.com/Tensai75/go-yenc-body-encryption"
)

func main() {
    // Input binary data
    input := []byte("Hello World.txt\xff") // 16 bytes including binary data
    password := "test123"
    segmentIndex := uint32(1)

    // Create cipher with random salt generation (empty string = random)
    cipher, err := yEncBodyEnc.NewCipher(password, "")
    if err != nil {
        log.Fatal(err)
    }

    // Encrypt the binary data
    encrypted, authTag, err := cipher.Encrypt(input, segmentIndex)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Encrypted: %x\n", encrypted)
    fmt.Printf("Salt: %s\n", cipher.Salt())  // 32-char hex string (16 bytes)
    fmt.Printf("Auth Tag: %s\n", authTag)    // 32-char hex string (16 bytes)

    // For decryption, create new cipher with the same salt
    decryptCipher, err := yEncBodyEnc.NewCipher(password, cipher.Salt())
    if err != nil {
        log.Fatal(err)
    }

    // Decrypt back to original
    decrypted, err := decryptCipher.Decrypt(encrypted, authTag, segmentIndex)
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("Decrypted: %x\n", decrypted)

    // Verify round-trip
    fmt.Printf("Match: %t\n", string(input) == string(decrypted))
}
Fixed Salt Example
// Use a specific salt (for testing or compatibility)
fixedSalt := "0123456789abcdef0123456789abcdef" // 32 hex chars = 16 bytes
cipher, err := yEncBodyEnc.NewCipher("password", fixedSalt)
if err != nil {
    log.Fatal(err)
}

encrypted, authTag, err := cipher.Encrypt(data, 1)
// cipher.Salt() will return "0123456789abcdef0123456789abcdef" (same as input)
Multi-Part yEnc Files
// Create cipher with random salt
cipher, err := yEncBodyEnc.NewCipher("password", "")
if err != nil {
    log.Fatal(err)
}

// Encrypt multiple segments (same salt, different nonces per segment)
encrypted1, tag1, _ := cipher.Encrypt(fileData1, 1) // Segment 1
encrypted2, tag2, _ := cipher.Encrypt(fileData2, 2) // Segment 2 (different nonce)
encrypted3, tag3, _ := cipher.Encrypt(fileData3, 3) // Segment 3 (different nonce)

// All segments share the same salt but have unique nonces
// For decryption, use the same salt for all segments
decryptCipher, _ := yEncBodyEnc.NewCipher("password", cipher.Salt())
decoded1, _ := decryptCipher.Decrypt(encrypted1, tag1, 1)
decoded2, _ := decryptCipher.Decrypt(encrypted2, tag2, 2)
decoded3, _ := decryptCipher.Decrypt(encrypted3, tag3, 3)

API Reference

NewCipher(password string, salt string) (*Cipher, error)

Creates a new Cipher instance with a pre-derived key from the password and salt.

Parameters:

  • password: Password for Argon2id key derivation (cannot be empty)
  • salt: 32-character hex string (16 bytes) or empty string for random salt generation

Returns: Cipher instance for multiple encrypt/decrypt operations.

(c *Cipher) Salt() string

Returns the salt used by this cipher instance as a 32-character hex string.

Returns: Salt as hex string (16 bytes represented as 32 hex characters).

(c *Cipher) Encrypt(input []byte, segmentIndex uint32) ([]byte, string, error)

Encrypts binary data using the pre-derived key with XChaCha20-Poly1305 authenticated encryption.

Parameters:

  • input: Binary data to encrypt (cannot be empty)
  • segmentIndex: Segment number for multi-part files (must be >= 1, affects nonce derivation)

Returns: Encrypted binary data and authentication tag as hex string.

(c *Cipher) Decrypt(input []byte, authtag string, segmentIndex uint32) ([]byte, error)

Decrypts binary data using the pre-derived key.

Parameters:

  • input: Encrypted binary data (cannot be empty)
  • authtag: Authentication tag as hex string (must be 32 characters)
  • segmentIndex: Segment number used during encryption (must be >= 1 and match encryption)

Returns: Original plaintext binary data.

Security

Cryptographic Components
  • Key Derivation: Argon2id with time=1, memory=64MB, threads=4
  • Salt Generation: Cryptographically secure random 16-byte salt using crypto/rand
  • Encryption: XChaCha20-Poly1305 authenticated encryption (RFC 8439 Extended)
  • Key Size: 256-bit keys with 192-bit nonces
  • Authentication: 128-bit Poly1305 authentication tags
  • Nonce Derivation: HMAC-SHA256(key, "yenc-body nonce" || segmentIndex)[0:24]
Security Properties
  • Semantic Security: Unique nonces ensure identical data encrypts differently across segments
  • Random Salt: Each cipher instance uses a unique random salt preventing rainbow table attacks
  • Memory-Hard: Argon2id resists ASIC/GPU attacks with 64MB memory requirement
  • Authentication: Poly1305 MAC prevents tampering and ensures integrity
  • Nonce Uniqueness: Deterministic nonce derivation from key + segment index prevents reuse
  • Standards Compliance: Follows yEnc Body Encryption Standard v0.3 with random salt generation

Standards Compliance

This implementation follows the complete yEnc Body Encryption Standard specification available at:

https://github.com/Tensai75/yenc-encryption-standards

Testing

Run the comprehensive test suite:

# All tests
go test -v

# Specific test categories
go test -run TestEncryptDecrypt -v
go test -run TestNewCipherValidation -v
go test -run TestCipherEncryptValidation -v
go test -run TestCipherDecryptValidation -v
go test -run TestDifferentSegments -v
go test -run TestAuthenticationFailure -v
go test -run TestNewCipherHexSalt -v
Test Coverage

Overall Coverage: 93.5% - Run go test -cover to verify

Function Coverage Status
NewCipher() 89.5% Excellent coverage
GenerateSalt() 80.0% Good coverage
DeriveKey() 100.0% Perfect coverage
DeriveNonce() 100.0% Perfect coverage
Encrypt() 91.7% Excellent coverage
Decrypt() 100.0% Perfect coverage
Salt() 100.0% Perfect coverage

Test Categories:

  • Round-trip encryption/decryption with random salts
  • Cross-cipher compatibility (encrypt with one cipher, decrypt with another using same salt)
  • Input validation (empty passwords, data, invalid segment indices)
  • Different segment handling and nonce uniqueness
  • Authentication failure detection (tampered data/tags)
  • Invalid authentication tag formats and lengths
  • Hex salt validation (length, format, invalid characters)
  • Deterministic encryption with same cipher instance
  • Salt generation and reuse functionality
  • Complete API coverage for all public functions

Note: Uncovered code primarily consists of error handling paths for extremely rare conditions (crypto library failures, random number generation failures). All security-critical functionality has complete test coverage.

Performance

Benchmark Results

Performance on modern hardware (AMD Ryzen 7 5800X) with 1,536,000 bytes (representing an average Usenet article size):

# Run performance benchmarks
go test -bench=Benchmark -benchmem
Operation Time per Op Throughput Memory Allocations
NewCipher ~12.57ms N/A 67.1MB 56 allocs
Encryption ~0.52ms 2,955 MB/s 1.54MB 10 allocs
Decryption ~0.55ms 2,797 MB/s 1.54MB 10 allocs
Full Cycle ~1.05ms 1,464 MB/s 3.08MB 20 allocs
Performance Characteristics
  • Key Derivation Dominates: ~12.6ms for initialization (includes cipher pre-computation)
  • Encryption/Decryption are Very Fast: ~0.52-0.55ms each
  • Excellent Throughput: Nearly 3 GB/s for pure encryption/decryption operations
  • Memory Efficient: Only ~1.5MB allocation per 1.5MB processed
  • Optimized Implementation: Cached cipher instances reduce allocations and improve performance
  • Minimal Allocations: Just 10 allocations per encrypt/decrypt operation
  • Minimal Allocations: Just 10 allocations per operation
  • Predictable: Encryption time remains constant regardless of data size (stream cipher property)
  • Optimized for Reuse: Create cipher once, encrypt/decrypt many segments efficiently

Dependencies

  • golang.org/x/crypto/argon2 - Argon2id key derivation
  • golang.org/x/crypto/chacha20poly1305 - XChaCha20-Poly1305 authenticated encryption

Contributing

  1. Fork the repository
  2. Create a feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

Please ensure all tests pass and add tests for new functionality.

License

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

Author

Tensai75 - GitHub

Documentation

Overview

Package yEncBodyEnc provides the reference Go implementation of the yEnc Body Encryption Standard.

This package implements XChaCha20-Poly1305 authenticated encryption for binary data before yEnc encoding, maintaining full compatibility with existing yEnc parsers and protocols. It uses Argon2id key derivation with cryptographically secure random salt generation to provide both confidentiality and integrity protection.

The implementation follows the complete yEnc Body Encryption Standard specification available at: https://github.com/Tensai75/yenc-encryption-standards

Basic usage:

// Create cipher with random salt
cipher, err := yEncBodyEnc.NewCipher("password", "")
if err != nil {
	log.Fatal(err)
}

// Encrypt data
encrypted, salt, authTag, err := cipher.Encrypt(data, 1)
if err != nil {
	log.Fatal(err)
}

// Decrypt with new cipher using same salt
decryptCipher, err := yEncBodyEnc.NewCipher("password", salt)
if err != nil {
	log.Fatal(err)
}
decrypted, err := decryptCipher.Decrypt(encrypted, authTag, 1)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DeriveKey

func DeriveKey(password string, salt []byte) []byte

DeriveKey derives a 32-byte master key using Argon2id key derivation function.

Parameters:

  • password: Password string to derive key from
  • salt: 16-byte salt for domain separation

Returns:

  • []byte: 32-byte derived key suitable for XChaCha20-Poly1305

Note: This function is deterministic - same password and salt always produce the same key.

func DeriveNonce

func DeriveNonce(key []byte, segmentIndex uint32) []byte

DeriveNonce derives a 24-byte nonce for XChaCha20-Poly1305 using HMAC-SHA256.

Parameters:

  • key: 32-byte derived key from DeriveKey function
  • segmentIndex: Segment number (must be >= 1, affects nonce derivation)

Returns:

  • []byte: 24-byte nonce suitable for XChaCha20-Poly1305

Note: This function is deterministic and does not use random number generation.

func GenerateSalt

func GenerateSalt() ([]byte, error)

GenerateSalt generates a cryptographically secure random 16-byte salt using crypto/rand.

Returns:

  • []byte: 16-byte cryptographically secure random salt
  • error: Error if the system's random number generator fails

Note: This function is called automatically by NewCipher when an empty salt is provided.

Types

type Cipher

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

Cipher holds the pre-derived key and salt for encrypting/decrypting multiple segments.

The key is derived once during NewCipher creation using Argon2id and reused for all encrypt/decrypt operations with the same cipher instance. Different segment indices produce unique nonces to ensure semantic security across segments.

func NewCipher

func NewCipher(password string, salt string) (*Cipher, error)

NewCipher creates a new Cipher instance with a pre-derived key from the password and salt.

Parameters:

  • password: Password for Argon2id key derivation (cannot be empty)
  • salt: 32-character hex string (16 bytes) or empty string for random salt generation

Returns:

  • *Cipher: Cipher instance with accessible Salt field for multiple operations
  • error: Error if password is empty, salt format is invalid, or random generation fails

Example:

// Random salt generation
cipher, err := NewCipher("password", "")
if err != nil {
	log.Fatal(err)
}
// Salt is now accessible: cipher.Salt()

// Use existing salt for decryption
decryptCipher, err := NewCipher("password", cipher.Salt())

func (*Cipher) Decrypt

func (c *Cipher) Decrypt(input []byte, authtag string, segmentIndex uint32) ([]byte, error)

Decrypt decrypts binary data using XChaCha20-Poly1305 authenticated decryption.

Parameters:

  • input: Encrypted binary data (cannot be empty)
  • authtag: Authentication tag as 32-character hex string (16 bytes)
  • segmentIndex: Segment number used during encryption (must be >= 1 and match)

Returns:

  • []byte: Original plaintext binary data
  • error: Error if validation fails, authentication fails, or decryption fails

Example:

// Create decrypt cipher with same password and salt from encryption
decryptCipher, err := NewCipher("password", salt)
if err != nil {
	log.Fatal(err)
}
decrypted, err := decryptCipher.Decrypt(encrypted, authTag, 1)
if err != nil {
	log.Fatal("Authentication failed or data corrupted:", err)
}

func (*Cipher) Encrypt

func (c *Cipher) Encrypt(input []byte, segmentIndex uint32) ([]byte, string, error)

Encrypt encrypts binary data using XChaCha20-Poly1305 authenticated encryption.

Parameters:

  • input: Binary data to encrypt (cannot be empty)
  • segmentIndex: Segment number for multi-part files (must be >= 1, affects nonce derivation)

Returns:

  • []byte: Encrypted binary data (same length as input)
  • string: Authentication tag as 32-character hex string (16 bytes)
  • error: Error if input is empty, segmentIndex is 0, or encryption fails

Example:

encrypted, authTag, err := cipher.Encrypt(fileData, 1)
if err != nil {
	log.Fatal(err)
}
// encrypted data is ready for yEnc encoding
// cipher.Salt and authTag must be stored/transmitted for decryption

func (*Cipher) Salt

func (c *Cipher) Salt() string

Salt returns the salt used by this cipher instance as a 32-character hex string.

Returns:

  • string: Salt as 32-character hex string (16 bytes represented as lowercase hex)

Example:

cipher, _ := NewCipher("password", "")  // Random salt
salt := cipher.Salt()                   // Get the salt for later use

// For decryption, use the same salt
decryptCipher, _ := NewCipher("password", salt)

Jump to

Keyboard shortcuts

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