f5messageextract

package module
v1.0.1 Latest Latest
Warning

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

Go to latest
Published: Jun 30, 2026 License: MIT Imports: 16 Imported by: 0

README

f5messageextract

Pure-Go F5 steganography: extract a hidden message from the DCT coefficients of a JPEG. Uses SHA1PRNG, which is deterministic and not cryptographically secure.

Install

go get github.com/0verkilll/f5messageextract

Command-line tool

Prebuilt binaries for every platform are attached to each release.

f5extract <image.jpg> <password>

Sponsor

If this project is useful to you, please consider supporting its development:

Sponsor @0verkilll on GitHub

License

MIT

Documentation

Overview

Package f5messageextract implements the F5 steganographic algorithm for extracting hidden messages from JPEG DCT coefficients.

The F5 algorithm was developed by Andreas Westfeld (2001) and is designed to resist both visual and statistical detection attacks while maintaining high embedding capacity. This implementation produces byte-identical output to the Java reference implementation for interoperability.

Algorithm Overview

F5 extraction reverses the embedding process:

  • Jsteg: LSB replacement (detectable via chi-square attack)
  • F3: Decrements instead of overwrites (detectable via even coefficient surplus)
  • F4: Sign-based encoding (resistant to statistical attacks)
  • F5: F4 + matrix encoding + permutative straddling (optimal efficiency)

The extraction process follows these steps:

  1. Seed PRNG with password bytes
  2. Generate Fisher-Yates permutation of coefficient indices
  3. Extract 32-bit header (k parameter and file size) from permuted coefficients
  4. XOR header with PRNG bytes to decode
  5. Extract message bits using matrix decoding with parameter k
  6. XOR message bytes with PRNG stream to recover original data

Key Features

  • Matrix Decoding: Uses (1, n, k) codes where n = 2^k - 1. Extracts k message bits from n coefficients by computing XOR hash of coefficient positions.
  • Permutative Straddling: Uses same Fisher-Yates permutation as embedding to access coefficients in the correct order for extraction.
  • Header Parsing: Extracts 32-bit header containing k parameter (8 bits) and file size (23 bits) for proper message recovery.
  • De-Zigzag Transformation: Applies JPEG de-zigzag table to access coefficients in their natural 8x8 block positions.

Security Warning

This package uses SHA1PRNG for pseudorandom number generation to maintain compatibility with the Java reference implementation. SHA1PRNG is NOT cryptographically secure by modern standards. The algorithm relies on SHA-1 which has known collision vulnerabilities.

DO NOT use this package for security-critical applications requiring cryptographic randomness. The PRNG is deterministic and predictable given the password/seed.

This package is intended for:

  • Digital forensics and steganography analysis
  • Compatibility with existing F5-encoded images (e.g., PixelKnot)
  • Research and educational purposes
  • Applications where statistical undetectability is prioritized over cryptographic security

Basic Usage

Extracting a message from JPEG DCT coefficients:

import (
    "github.com/0verkilll/f5messageextract"
    "github.com/0verkilll/fisheryates"
    "github.com/0verkilll/sha1"
)

// Create dependencies
hasher := sha1.NewSHA1(sha1.NewBigEndian())
permutator := fisheryates.NewFisherYates()

// Create extractor
extractor := f5messageextract.NewExtractor(hasher, permutator)

// Extract hidden message from JPEG DCT coefficients
result, err := extractor.Extract(coefficients, "password")
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Extracted %d bytes with k=%d\n", result.FileSize, result.K)
fmt.Println(string(result.Data))

Advanced Options

Using options for custom file size limits:

extractor := f5messageextract.NewExtractor(
    hasher,
    permutator,
    f5messageextract.WithMaxFileSize(50 * 1024 * 1024), // 50MB limit
)

Low-Level Functions

The package exports low-level functions for research purposes:

// Get steganographic bit value from coefficient
bit := f5messageextract.GetStegoBit(coefficient)

// Initialize PRNG with password
prng := f5messageextract.InitializePRNG("password")
defer prng.Clear()

// Generate coefficient permutation
perm, err := f5messageextract.GeneratePermutation(prng, len(coefficients))

// Apply de-zigzag transformation
zigzag := f5messageextract.ApplyDeZigZag(shuffledIndex)

Dependencies

This package depends on the following sibling packages:

  • securerandom: SHA1PRNG implementation for deterministic randomness
  • fisheryates: Fisher-Yates shuffle for coefficient permutation
  • sha1: SHA-1 hasher for PRNG seeding
  • f5core: Shared constants and de-zigzag table
  • f5coefficient: Coefficient bit extraction functions
  • logger: Optional structured logging support
  • i18n: Internationalized error messages

Reference

Westfeld, A. (2001). F5 - A Steganographic Algorithm: High Capacity Despite Better Steganalysis. Lecture Notes in Computer Science, 2137, 289-302.

Package f5messageextract provides F5 steganographic extraction functionality for JPEG images following Andreas Westfeld's 2001 F5 algorithm.

This package implements the extraction (decoding) side of the F5 algorithm, which extracts hidden messages from JPEG DCT coefficients using matrix encoding and permutative straddling.

The package follows SOLID principles with dependency injection for:

  • Hasher: SHA-1 computation for PRNG seeding
  • Permutator: Fisher-Yates permutation generation
  • PRNGFactory: Optional factory for creating PRNG instances

PRNG Interface Migration

As of this version, RandomSource and PRNGFactory are type aliases to f5prng.RandomSource and f5prng.PRNGFactory respectively. This provides a single source of truth for PRNG interfaces across F5 steganography packages.

For new code, import and use f5prng types directly:

import "github.com/0verkilll/f5prng"
factory := f5prng.NewDefaultFactory()

Example usage:

extractor := f5messageextract.NewExtractor(hasher, permutator)
result, err := extractor.Extract(coefficients, password)
if err != nil {
    log.Fatal(err)
}
fmt.Printf("Extracted %d bytes with k=%d\n", result.FileSize, result.K)

Package f5messageextract provides F5 steganographic extraction functionality. This file contains PRNG initialization, permutation generation, and de-zigzag functions.

PRNG Interface Migration

RandomSource and PRNGFactory are now type aliases to f5prng.RandomSource and f5prng.PRNGFactory. For new code, use f5prng types directly:

import "github.com/0verkilll/f5prng"
factory := f5prng.NewDefaultFactory()
prng := factory.NewPRNG()
prng.Seed([]byte(password))

Index

Constants

View Source
const (
	// MaxFileSize is the maximum allowed size for extracted files.
	// This prevents memory exhaustion attacks from malformed headers.
	// See f5core.DefaultMaxFileSize for documentation.
	MaxFileSize = f5core.DefaultMaxFileSize

	// MaxCoefficientArraySize is the maximum allowed size for coefficient arrays.
	// This prevents memory exhaustion when processing untrusted input.
	MaxCoefficientArraySize = 100_000_000 // 100 million coefficients

	// MinKParameter is the minimum valid k value.
	// k=1 uses simple (1,1,1) codes: 1 coefficient per bit (no matrix encoding).
	// See f5core.MinKParameter for documentation.
	MinKParameter = f5core.MinKParameter

	// MaxKParameter is the maximum k value the extractor will accept from a
	// header. It is intentionally wider than the embedder's range: the encoder
	// only ever produces k in [1, 7] (see f5messageembed), but the decoder
	// accepts k up to 31 so it can read payloads written by other F5 tools
	// that use a larger k. The header stores k in 8 bits; values above 31 are
	// rejected, and impractically large k fails the capacity guard rather than
	// the range gate. k=31 implies n=2^31-1 coefficients per codeword.
	MaxKParameter = 31

	// HeaderSize is the number of bits in the F5 message header.
	// The header contains the k parameter (8 bits) and file size (23 bits).
	// See f5core.HeaderSize for documentation.
	HeaderSize = f5core.HeaderSize

	// MaxMessageSize is the maximum message size in bytes that can be embedded/extracted.
	// This is limited by the 23-bit file size field in the F5 header (2^23 - 1 = 8,388,607 bytes).
	// See f5core.MaxMessageSize for documentation.
	MaxMessageSize = f5core.MaxMessageSize
)

Re-export constants from f5core for backward compatibility and convenience. These constants define the limits and parameters for the F5 extraction algorithm.

View Source
const (
	ErrKeyEmptyCoefficients     = "f5messageextract.error.empty_coefficients" //nolint:gosec // G101: translation key, not credential
	ErrKeyEmptyPassword         = "f5messageextract.error.empty_password"     //nolint:gosec // G101: translation key, not credential
	ErrKeyInvalidCoefficientVal = "f5messageextract.error.invalid_coefficient_range"
	ErrKeyInvalidKParameter     = "f5messageextract.error.invalid_k_parameter"
	ErrKeyInvalidFileSize       = "f5messageextract.error.invalid_file_size"
	ErrKeyFileSizeExceedsMax    = "f5messageextract.error.file_size_exceeds_max"
	ErrKeyInsufficientCoeffs    = "f5messageextract.error.insufficient_coefficients"
	ErrKeyIncompleteExtraction  = "f5messageextract.error.incomplete_extraction"
	ErrKeyPermutationFailed     = "f5messageextract.error.permutation_failed"
	ErrKeySeedingFailed         = "f5messageextract.error.seeding_failed" //nolint:gosec // G101: translation key, not credential
)

Error keys for i18n translation. These keys correspond to entries in locales/*.json. #nosec G101 -- These are translation keys, not hardcoded credentials

View Source
const (
	// CoefficientMin is the minimum valid JPEG quantized DCT coefficient value.
	// See f5core.CoefficientMin for documentation.
	CoefficientMin = f5core.CoefficientMin

	// CoefficientMax is the maximum valid JPEG quantized DCT coefficient value.
	// See f5core.CoefficientMax for documentation.
	CoefficientMax = f5core.CoefficientMax
)

Coefficient range constants re-exported from f5core for validation.

Variables

View Source
var (
	// ErrNoCoefficients is returned when the coefficient array is empty.
	// F5 extraction requires at least enough coefficients to extract the header.
	ErrNoCoefficients = errors.New("f5messageextract: coefficient array is empty")

	// ErrEmptyPassword is returned when the password string is empty.
	// The password is required for PRNG seeding to generate the coefficient permutation.
	ErrEmptyPassword = errors.New("f5messageextract: password cannot be empty")

	// ErrInsufficientCoefficients is returned when there are not enough
	// usable coefficients (non-zero, non-DC) to extract the header or message.
	// This typically indicates the image is too small or has too few non-zero coefficients.
	ErrInsufficientCoefficients = errors.New("f5messageextract: insufficient usable coefficients for extraction")

	// ErrInvalidKParameter is returned when the k parameter from the header
	// is outside the valid range [1, 31].
	// k=0 is invalid as it would mean no bits per group.
	// k>31 cannot occur in a well-formed header (k is read from 8 bits but the
	// decoder caps the accepted range at 31).
	ErrInvalidKParameter = errors.New("f5messageextract: k parameter outside valid range [1, 31]")

	// ErrInvalidFileSize is returned when the file size from the header is invalid.
	// This includes:
	//   - Zero file size (nothing to extract)
	//   - Negative file size (corrupted header)
	//   - File size exceeding MaxFileSize limit (potential attack or corruption)
	ErrInvalidFileSize = errors.New("f5messageextract: invalid file size in header")

	// ErrIncompleteExtraction is returned when extraction terminates before
	// all expected bytes have been extracted.
	// This typically indicates coefficient exhaustion or data corruption.
	ErrIncompleteExtraction = errors.New("f5messageextract: extraction incomplete, not all bytes extracted")

	// ErrSeedingFailed is returned when the PRNG cannot be initialized with
	// the derived key (password-hash combination). This is rare in practice —
	// SHA-1 seeding cannot fail for any input the Extract surface accepts —
	// but consumers should still propagate it rather than silently swallowing.
	ErrSeedingFailed = errors.New("f5messageextract: PRNG seeding failed")
)

Sentinel errors for F5 extraction failures. These errors can be checked using errors.Is() and wrapped with additional context using wrapError() or fmt.Errorf() with %w verb.

Example:

result, err := extractor.Extract(coeffs, password)
if errors.Is(err, f5messageextract.ErrNoCoefficients) {
    // Handle empty coefficient array
}

Functions

func ApplyDeZigZag

func ApplyDeZigZag(shuffledIndex int) int

ApplyDeZigZag transforms a shuffled coefficient index using the JPEG de-zigzag table.

JPEG stores DCT coefficients in zigzag order to improve compression by grouping zero coefficients together. The F5 algorithm applies this transformation after permutation to access coefficients in their natural 8x8 block positions.

Formula: zigzag = shuffled - shuffled%64 + deZigZag[shuffled%64]

This can be understood as:

  • (shuffled / 64) * 64: The base offset of the 8x8 block
  • deZigZag[shuffled % 64]: The de-zigzag position within the block

Parameters:

  • shuffledIndex: The permuted coefficient index

Returns:

  • The de-zigzagged index for accessing the coefficient array

Example:

shuffled := permutation[i]
zigzag := ApplyDeZigZag(shuffled)
coefficient := coefficients[zigzag]

func DetectLocale

func DetectLocale() string

DetectLocale automatically detects the system locale from environment variables.

It checks the following environment variables in order:

  • LC_ALL
  • LC_MESSAGES
  • LANG

The detected locale is normalized to the format used by this package (e.g., "en-US"). Common system formats like "en_US.UTF-8" are converted to "en-US".

If the detected locale is not in the list of supported locales returned by GetSupportedLocales(), or if no locale can be detected, it falls back to "en-US".

Example:

// Auto-detect and create translator
locale := f5messageextract.DetectLocale()
translator, err := f5messageextract.NewTranslator(locale)
if err != nil {
    log.Fatal(err)
}
f5messageextract.SetTranslator(translator)

Returns:

  • string: The detected locale code (e.g., "en-US", "es-ES") or "en-US" as fallback

func GeneratePermutation

func GeneratePermutation(random f5prng.RandomSource, size int) ([]int, error)

GeneratePermutation generates a Fisher-Yates permutation of indices.

The permutation is used for "permutative straddling" in F5, which shuffles coefficient indices before extraction to match the embedding pattern. This ensures the same password produces the same permutation for both embedding and extraction.

The function uses the fisheryates package's GenerateInto method for zero allocation when reusing buffers. The permutation is deterministic based on the RandomSource state.

PRNG Consumption: This function consumes PRNG state proportional to the size parameter. After calling GeneratePermutation, the PRNG state will have advanced exactly as the Java F5 implementation expects.

Parameters:

  • random: A seeded RandomSource (typically from InitializePRNG or f5prng.NewDefaultFactory())
  • size: The number of elements in the permutation (coefficient count)

Returns:

  • A slice containing a permutation of [0, 1, ..., size-1]
  • An error if size is negative or exceeds fisheryates.MaxPermutationSize

Example:

prng := InitializePRNG("password")
defer prng.Clear()
perm, err := GeneratePermutation(prng, len(coefficients))
if err != nil {
    return err
}
// perm now contains shuffled indices for coefficient access

func GetLogger

func GetLogger() logger.Logger

GetLogger returns the package logger, or NopLogger if not set. This function is thread-safe and can be called from any goroutine.

func GetStegoBit

func GetStegoBit(coefficient int16) int

GetStegoBit extracts the steganographic bit value from a DCT coefficient. This is an alias to f5coefficient.GetStegoBit for backward compatibility. See f5coefficient.GetStegoBit for full documentation.

The F5 algorithm uses sign-based encoding where:

  • Positive even coefficients = 0
  • Positive odd coefficients = 1
  • Negative even coefficients = 1 (inverted)
  • Negative odd coefficients = 0 (inverted)

Parameters:

  • coefficient: The DCT coefficient value

Returns:

  • 0 or 1 representing the steganographic bit

func GetSupportedLocales

func GetSupportedLocales() []string

GetSupportedLocales returns the list of locales supported by this package.

It reads the list of embedded locale files and returns their locale codes.

The returned array is sorted alphabetically for consistency.

If the embedded filesystem cannot be read (which should never happen in normal operation), this function returns a fallback array containing only "en-US" to ensure graceful degradation.

Example:

locales := f5messageextract.GetSupportedLocales()
fmt.Println(locales)
// Output: [en-US]

Returns:

  • []string: Sorted array of supported locale codes

func InitializePRNG deprecated

func InitializePRNG(password string) f5prng.RandomSource

InitializePRNG creates and initializes a RandomSource instance for F5 extraction.

Deprecated: Use f5prng.NewDefaultFactory() instead. This function is retained for backward compatibility but will be removed in a future major version. For new code, use f5prng directly:

factory := f5prng.NewDefaultFactory()
prng := factory.NewPRNG()
defer prng.Clear()
prng.Seed([]byte(password))

Or inject a PRNGFactory into the extractor for better dependency management:

factory := f5prng.NewDefaultFactory()
extractor := f5messageextract.NewExtractor(hasher, permutator,
    f5messageextract.WithPRNGFactory(factory))

The PRNG is seeded with the password bytes using SHA-1 hashing, matching Java's SecureRandom SHA1PRNG algorithm exactly. This ensures byte-identical output when using the same password as the Java F5 reference implementation.

The returned RandomSource should be cleared when no longer needed to prevent sensitive password-derived state from remaining in memory:

prng := InitializePRNG(password)
defer prng.Clear()
// ... use prng

Parameters:

  • password: The password string used to seed the PRNG

Returns:

  • A seeded f5prng.RandomSource ready for permutation generation

func NewTranslator

func NewTranslator(locale ...string) (*i18n.Translator, error)

NewTranslator creates an i18n translator with embedded locale translations.

It loads translation files that are embedded in the package binary, providing a batteries-included translation experience.

The created translator:

  • Loads from embedded locale files (no external files needed)
  • Uses "en-US" as the default/fallback locale
  • Sets the requested locale as the current locale
  • Supports all locales returned by GetSupportedLocales()

After creating the translator, pass it to SetTranslator() to enable automatic translation of all error messages in this package.

If the requested locale is not supported, an error is returned. Use GetSupportedLocales() to see the list of available locales.

Auto-Detection:

Call without arguments or with an empty string to auto-detect the locale from system environment variables (LC_ALL, LC_MESSAGES, LANG). If detection fails or the detected locale is not supported, it falls back to "en-US".

Example - Auto-Detect Locale (Recommended):

// Auto-detect locale from system environment
translator, err := f5messageextract.NewTranslator()
if err != nil {
    log.Fatal(err)
}
f5messageextract.SetTranslator(translator)

Example - Specific Language:

// Enable English translations
translator, err := f5messageextract.NewTranslator("en-US")
if err != nil {
    log.Fatal(err)
}
f5messageextract.SetTranslator(translator)

Parameters:

  • locale: Optional locale code (e.g., "en-US"). Omit or pass empty string for auto-detect

Returns:

  • *i18n.Translator: Configured translator instance
  • error: Error if locale is not supported or initialization fails

func SetLogger

func SetLogger(l logger.Logger)

SetLogger sets the logger for the f5messageextract package. Pass nil to disable logging and reset to the default NopLogger. The logger is shared across all goroutines and is thread-safe.

By default, the package uses NopLogger which discards all log messages. Set a logger to enable debug output for extraction operations.

Example:

// Enable logging with a custom logger
f5messageextract.SetLogger(myLogger)

// Disable logging
f5messageextract.SetLogger(nil)

func SetTranslator

func SetTranslator(translator TranslatorProvider)

SetTranslator sets the global translator for this package. This allows the application to provide translations for error messages and other user-facing strings.

Pass nil to disable translations and use English defaults.

This function is thread-safe and can be called from multiple goroutines.

Example:

translator, _ := i18n.New(
    i18n.WithFileSystemLoader("locales"),
    i18n.WithDefaultLocale("en-US"),
)
f5messageextract.SetTranslator(translator)

func Translate

func Translate(key string) string

Translate returns a translated message for the given key. If no translator is set or the key is not found, returns the default English message. This is the public translation helper function for i18n support.

func ValidateCoefficients

func ValidateCoefficients(coefficients []int16) error

ValidateCoefficients validates that the coefficient slice is valid for extraction. It checks that:

  • The slice is not empty
  • All coefficient values are within the valid JPEG DCT range [-2048, 2047]

Returns nil if validation passes, or a ValidationError if it fails.

func ValidatePassword

func ValidatePassword(password string) error

ValidatePassword validates that the password is valid for extraction. It checks that the password is not an empty string.

Returns nil if validation passes, or a ValidationError if it fails.

Types

type ExtractionResult

type ExtractionResult struct {
	// Data contains the extracted message bytes after XOR decoding
	// with the PRNG stream.
	Data []byte

	// K is the k parameter from the F5 header, indicating the matrix
	// encoding scheme used. Valid range is [1, 8].
	// With k bits per n=2^k-1 coefficients, higher k values provide
	// better embedding efficiency but require more coefficients.
	K int

	// FileSize is the original file size from the F5 header.
	// This indicates how many bytes were embedded (before any padding).
	FileSize int
}

ExtractionResult contains the result of an F5 extraction operation. This structured result type provides access to both the extracted data and the extraction parameters used.

type Extractor

type Extractor interface {
	// Extract extracts a hidden message from JPEG DCT coefficients.
	// The extraction uses the provided password to seed the PRNG for
	// generating the coefficient permutation.
	//
	// The method follows the F5 algorithm:
	//   1. Seed PRNG with password bytes
	//   2. Generate Fisher-Yates permutation of coefficient indices
	//   3. Extract 32 header bits from permuted coefficients
	//   4. Parse k parameter and file size from header
	//   5. Extract message using matrix encoding
	//
	// Parameters:
	//   coefficients - JPEG DCT coefficients as int16 array
	//   password     - Password used to seed the PRNG
	//
	// Returns:
	//   *ExtractionResult containing the extracted data, k parameter, and file size
	//   error if extraction fails (empty coefficients, invalid header, etc.)
	//
	// Errors:
	//   - ErrNoCoefficients: coefficients slice is empty
	//   - ErrEmptyPassword: password string is empty
	//   - ErrInsufficientCoefficients: not enough coefficients for extraction
	//   - ErrInvalidKParameter: k parameter outside valid range (1-31)
	//   - ErrInvalidFileSize: file size is invalid (zero, negative, or too large)
	//   - ErrIncompleteExtraction: extraction did not complete successfully
	Extract(coefficients []int16, password string) (*ExtractionResult, error)
}

Extractor defines the interface for F5 steganographic extraction. This exportable interface enables mock injection in user tests and follows the Dependency Inversion Principle by providing an abstraction for extraction.

The interface is designed for use in forensic analysis and security research applications where hidden messages need to be extracted from JPEG images.

type F5Extractor

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

F5Extractor implements the F5 steganographic extraction algorithm.

This implementation follows the SOLID Adapter pattern, using dependency injection for all cryptographic and algorithmic components. The extractor coordinates the F5 algorithm's steps:

  1. Convert password to seed and initialize PRNG
  2. Generate Fisher-Yates permutation of coefficients
  3. Extract 32-bit header (k parameter and file size)
  4. XOR header with PRNG bytes
  5. Extract message using matrix encoding
  6. XOR message bytes with PRNG stream

The algorithm matches the Java F5 implementation exactly, including:

  • Signed arithmetic for PRNG and XOR operations
  • DC coefficient skipping (multiples of 64)
  • De-zigzag transformation for JPEG blocks
  • Matrix encoding with Hamming codes

Dependencies are injected to enable testing and portability to other languages (TypeScript, Rust, JavaScript, WebAssembly).

Thread Safety: Each extraction creates a fresh PRNG instance, eliminating shared mutable state and making the extractor fully thread-safe. When WithPermutationPool is enabled, the shared sync.Pool is the only cross-call mutable state and it is safe for concurrent use by design.

func NewExtractor

func NewExtractor(hasher Hasher, permutator Permutator, opts ...Option) *F5Extractor

NewExtractor creates a new F5 message extractor with injected dependencies.

Parameters:

  • hasher: SHA1 hasher for PRNG seeding (used when no PRNGFactory is provided)
  • permutator: Fisher-Yates permutation generator
  • opts: Optional configuration options (e.g., WithMaxFileSize, WithPRNGFactory)

Returns an *F5Extractor that implements the Extractor interface.

Note: This extractor is fully thread-safe. Each password test creates a fresh PRNG instance, eliminating shared mutable state.

Example:

extractor := f5messageextract.NewExtractor(sha1Hasher, fisherYates)
result, err := extractor.Extract(coefficients, "password")

// With PRNGFactory for dependency injection (recommended):
factory := f5prng.NewDefaultFactory()
extractor := f5messageextract.NewExtractor(sha1Hasher, fisherYates,
    f5messageextract.WithPRNGFactory(factory))

func (*F5Extractor) Extract

func (f *F5Extractor) Extract(coefficients []int16, password string) (*ExtractionResult, error)

Extract extracts a hidden message from JPEG DCT coefficients using F5 steganography.

The method performs the complete F5 extraction algorithm:

  1. Seeds PRNG with password
  2. Generates coefficient permutation
  3. Extracts and decodes 32-bit header
  4. Extracts message using matrix encoding
  5. XORs message with PRNG stream

PRNG Source: If a PRNGFactory was provided via WithPRNGFactory, the factory-created PRNG is used. Otherwise, an internal securerandom.SecureRandom is created using the hasher for backward compatibility.

Parameters:

  • coefficients: Quantized DCT coefficients from JPEG
  • password: Password used for extraction (matches embedding password)

Returns:

  • *ExtractionResult: Contains extracted data, k parameter, and file size
  • error: Any extraction errors (invalid header, insufficient coefficients, etc.)

Errors:

  • ErrNoCoefficients: coefficients slice is empty
  • ErrEmptyPassword: password string is empty
  • ErrInsufficientCoefficients: not enough coefficients for extraction
  • ErrInvalidKParameter: k parameter outside valid range (1-31)
  • ErrInvalidFileSize: file size is invalid (zero, negative, or too large)
  • ErrIncompleteExtraction: extraction did not complete successfully

func (*F5Extractor) ExtractBytes

func (f *F5Extractor) ExtractBytes(coefficients []int16, password string) ([]byte, error)

ExtractBytes is a convenience method that extracts only the message bytes. This is useful for simple use cases where only the extracted data is needed.

Parameters:

  • coefficients: Quantized DCT coefficients from JPEG
  • password: Password used for extraction

Returns:

  • []byte: The extracted message bytes
  • error: Any extraction errors

Example:

data, err := extractor.ExtractBytes(coefficients, "password")
if err != nil {
    log.Fatal(err)
}
fmt.Println(string(data))

type Hasher

type Hasher interface {
	// Sum computes the cryptographic hash of the input data.
	// For F5 extraction, this should be SHA-1 (returning 20 bytes).
	//
	// Parameters:
	//   data - The input bytes to hash
	//
	// Returns:
	//   The hash digest as a byte slice
	Sum(data []byte) []byte
}

Hasher defines the interface for cryptographic hash computation. This abstraction enables different hash implementations while maintaining the Interface Segregation Principle by providing only the essential method.

The interface is designed to support SHA-1 hashing for PRNG seeding in the F5 steganographic algorithm.

type Option

type Option func(*F5Extractor)

Option is a functional option for configuring F5Extractor.

func WithMaxFileSize

func WithMaxFileSize(size int) Option

WithMaxFileSize sets the maximum allowed file size for extraction. If the extracted file size exceeds this limit, Extract returns ErrInvalidFileSize.

Default: MaxFileSize (10MB)

Example:

extractor := f5messageextract.NewExtractor(hasher, permutator,
    f5messageextract.WithMaxFileSize(5 * 1024 * 1024)) // 5MB limit

func WithPRNGFactory

func WithPRNGFactory(factory PRNGFactory) Option

WithPRNGFactory sets a factory for creating PRNG instances during extraction. When a PRNGFactory is provided, the Extract method will use factory-created PRNGs instead of the internal securerandom implementation.

The factory parameter accepts f5prng.PRNGFactory (or the type alias PRNGFactory) to enable seamless integration with the unified f5prng package.

This enables:

  • Custom PRNG implementations for testing with mock PRNGs
  • Consistent PRNG behavior across embed/extract operations
  • Dependency injection from higher-level packages (e.g., f5stegokit)

If no PRNGFactory is provided (or nil is passed), the extractor falls back to creating securerandom.SecureRandom instances internally for backward compatibility.

The factory-created PRNG will be seeded with the password bytes during extraction. The factory should return fresh, uninitialized PRNG instances.

Example with f5prng:

factory := f5prng.NewDefaultFactory()
extractor := f5messageextract.NewExtractor(hasher, permutator,
    f5messageextract.WithPRNGFactory(factory))
result, err := extractor.Extract(coefficients, password)

func WithPermutationPool

func WithPermutationPool() Option

WithPermutationPool enables a sync.Pool of permutation buffers shared by this extractor. This eliminates the per-call []int allocation for the Fisher-Yates permutation, which for a 256K-coefficient image is a 2 MB allocation PER Extract call.

Recommended for:

  • Bruteforce / dictionary-attack workloads that call Extract in a tight loop against the same image (same coefficient count).
  • Any batch extraction scenario where the same *F5Extractor processes many images of similar size.

Not recommended for one-shot extractions: the pool's bookkeeping overhead does not pay off if only a handful of calls are made.

The pool is safe for concurrent use. Each Extract call Gets a buffer, passes it to the permutator (which may reuse or grow it), and Puts the resulting buffer back on return. Buffers of different sizes may end up in the pool; GenerateInto handles growing as needed.

Example:

extractor := f5messageextract.NewExtractor(hasher, permutator,
    f5messageextract.WithPermutationPool())
for _, candidate := range candidates {
    result, err := extractor.Extract(coefficients, candidate)
    // ...
}

type PRNGFactory deprecated

type PRNGFactory = f5prng.PRNGFactory

PRNGFactory is a type alias to f5prng.PRNGFactory for backward compatibility.

Deprecated: Use f5prng.PRNGFactory directly instead. This type alias is provided for backward compatibility and will be removed in a future major version. For new code, import and use f5prng.PRNGFactory:

import "github.com/0verkilll/f5prng"
factory := f5prng.NewDefaultFactory()

The interface defines a factory pattern for creating RandomSource instances:

  • NewPRNG() RandomSource: Create a new, uninitialized RandomSource

When a PRNGFactory is provided to NewExtractor via WithPRNGFactory, the Extract method will use factory-created PRNGs instead of the internal securerandom implementation. This enables:

  • Custom PRNG implementations for testing with mock PRNGs
  • Consistent PRNG behavior across embed/extract operations
  • Dependency injection from higher-level packages (e.g., f5stegokit)

If no PRNGFactory is provided, the extractor falls back to creating securerandom.SecureRandom instances internally for backward compatibility.

Example usage with f5prng:

factory := f5prng.NewDefaultFactory()
extractor := f5messageextract.NewExtractor(hasher, permutator,
    f5messageextract.WithPRNGFactory(factory))
result, err := extractor.Extract(coefficients, password)

type Permutator

type Permutator interface {
	// Generate creates and returns a permutation of integers from 0 to size-1.
	// The permutation is determined by the provided RandomSource, which must
	// be properly seeded before calling this method.
	//
	// The returned slice contains each integer from 0 to size-1 exactly once,
	// arranged in a pseudo-random order determined by the RandomSource.
	// The same RandomSource state must always produce the same permutation.
	//
	// Parameters:
	//   size   - The number of elements in the permutation (must be >= 0)
	//   random - The RandomSource used to generate the permutation order
	//
	// Returns:
	//   A slice of size integers containing a permutation of [0, size-1]
	//   An error if size is negative or exceeds maximum limits
	//
	// Example:
	//   Generate(5, seededRandom) might return [2, 4, 0, 3, 1], nil
	Generate(size int, random RandomSource) ([]int, error)

	// GenerateInto generates a permutation into the provided buffer.
	// This is a zero-allocation variant that reuses an existing buffer.
	// The buffer will be resized if needed to fit 'size' elements.
	//
	// Parameters:
	//   buf    - Reusable buffer (will be resized if needed)
	//   size   - The number of elements in the permutation (must be >= 0)
	//   random - The RandomSource used to generate the permutation order
	//
	// Returns:
	//   The buffer containing the permutation (may be reallocated if too small)
	//   An error if size is negative or exceeds maximum limits
	GenerateInto(buf []int, size int, random RandomSource) ([]int, error)
}

Permutator defines the interface for generating permutations of integers. This abstraction follows the Single Responsibility Principle by focusing solely on permutation generation, and the Dependency Inversion Principle by depending on the RandomSource abstraction rather than concrete implementations.

The interface supports both allocating and zero-allocation variants to enable performance optimization in hot paths.

type RandomSource deprecated

type RandomSource = f5prng.RandomSource

RandomSource is a type alias to f5prng.RandomSource for backward compatibility.

Deprecated: Use f5prng.RandomSource directly instead. This type alias is provided for backward compatibility and will be removed in a future major version. For new code, import and use f5prng.RandomSource:

import "github.com/0verkilll/f5prng"
var rs f5prng.RandomSource = factory.NewPRNG()

The interface defines pseudo-random number generation with:

  • Seed([]byte): Initialize the random state
  • NextBytes(n int) []byte: Generate n random bytes
  • NextInt() int32: Generate random 32-bit integer
  • Clear(): Securely zero internal state

For Java SecureRandom SHA1PRNG compatibility, use f5prng.NewDefaultFactory() to create RandomSource instances.

type TranslatorProvider

type TranslatorProvider interface {
	// Translate looks up a translation key in the current locale.
	// If the key is not found, it tries the fallback chain.
	// Returns the key itself if not found in any locale.
	Translate(key string) string

	// TranslateWithArgs looks up a translation key and formats it with arguments.
	// Uses fmt.Sprintf formatting. If the key is not found, returns the key itself.
	TranslateWithArgs(key string, args ...interface{}) string

	// HasKey checks if a translation key exists in the current locale or fallback chain.
	HasKey(key string) bool

	// SetLocale changes the current locale for translation lookups.
	SetLocale(locale string)

	// GetLocale returns the current locale being used for translations.
	GetLocale() string
}

TranslatorProvider allows optional translation support. This interface matches github.com/0verkilll/i18n.TranslatorProvider but is defined here to avoid a hard dependency on the i18n package.

Packages using this pattern allow application developers to optionally provide translations without forcing the i18n package on all users.

Example usage:

import "github.com/0verkilll/i18n"

translator, _ := i18n.New(
    i18n.WithFileSystemLoader("locales"),
    i18n.WithDefaultLocale("en-US"),
)
f5messageextract.SetTranslator(translator)

Now all f5messageextract error messages will be translated according to the current locale setting in the translator.

func GetTranslator

func GetTranslator() TranslatorProvider

GetTranslator returns the currently configured translator, or nil if none is set.

This function is thread-safe and can be called from multiple goroutines.

type ValidationError

type ValidationError struct {
	// Wrapped is the underlying sentinel error for errors.Is() support
	Wrapped error
	// Key is the i18n key for the error message
	Key string
	// Message is the translated or fallback error message
	Message string
}

ValidationError represents an input validation error with i18n support.

func NewValidationErrorWithSentinel

func NewValidationErrorWithSentinel(key string, sentinel error, args ...interface{}) *ValidationError

NewValidationErrorWithSentinel creates a validation error that wraps a sentinel error. This allows both i18n translation and errors.Is() checking.

Example:

err := NewValidationErrorWithSentinel(ErrKeyInvalidKParameter, ErrInvalidKParameter, k)
// errors.Is(err, ErrInvalidKParameter) returns true
// err.Error() returns translated message

func ValidationErrorf

func ValidationErrorf(key string, args ...interface{}) *ValidationError

ValidationErrorf creates a validation error with a formatted message. This is useful for validation errors that need dynamic content. The key should be an i18n key, and args are passed to fmt.Sprintf after translation if the translator supports TranslateWithArgs.

func (*ValidationError) Error

func (e *ValidationError) Error() string

Error implements the error interface.

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

Unwrap returns the underlying sentinel error for errors.Is() support.

Jump to

Keyboard shortcuts

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