f5imagerecover

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: 15 Imported by: 0

README

f5imagerecover

Recovers the original DCT coefficients from an F5-embedded JPEG, given the password, reverting the F5 changes and re-encoding a recovered image. It recovers coefficients — it does not extract hidden messages.

Install

go get github.com/0verkilll/f5imagerecover

Command-line tool

Prebuilt binaries for every platform are attached to each release.

f5imagerecover -in stego.jpg -pass <password> [-out recovered.jpg]

Sponsor

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

Sponsor @0verkilll on GitHub

License

MIT

Documentation

Overview

Package f5imagerecover recovers original DCT coefficients from F5-embedded JPEG images.

This package REVERSES the F5 embedding process by incrementing coefficient values that were decremented during embedding. It does NOT extract hidden messages - use f5extract for message extraction.

Purpose

When the F5 algorithm embeds a hidden message, it modifies JPEG DCT coefficients by decrementing their absolute values. This package recovers the ORIGINAL coefficients by reversing those modifications.

The typical workflow is:

  1. Use f5extract to extract the hidden message from a stego image
  2. Use f5imagerecover with that message to recover original coefficients
  3. Compare original vs stego coefficients for forensic analysis

Recovery Algorithm

The recovery process retraces the embedding path:

  1. Initialize PRNG with password (same as f5embed)
  2. Generate coefficient permutation (same PRNG consumption as f5embed)
  3. XOR header bytes with PRNG (same sequence as f5embed)
  4. Parse k parameter from header
  5. Walk coefficients in permuted order, matching embedding path exactly
  6. For each code word, determine if/which coefficient was modified
  7. Reverse the modification (increment absolute value: 4->5, -4->-5)
  8. Handle shrinkage recovery (restore 0s to original +/-1 values)

F5 Algorithm Overview

The F5 algorithm uses several key techniques that this recovery reverses:

  • F4 Encoding: Positive coefficients encode even=0, odd=1; negative coefficients encode even=1, odd=0. Embedding DECREMENTS absolute values; recovery INCREMENTS.

  • Matrix Encoding: Uses (1, n, k) codes where n = 2^k - 1. Recovery uses the same hash function f(a) = XOR of (a_i * i) to determine which coefficient was modified at each code word.

  • Permutative Straddling: Shuffles coefficient processing order using a password-seeded PRNG. Recovery generates the same permutation.

  • Shrinkage: When embedding decrements +/-1 to 0, the bit must be re-embedded. Recovery detects these zero coefficients and restores them to +/-1.

Thread Safety

The package functions RecoverCoefficients and RecoverCoefficientsInPlace are safe for concurrent use. Each call creates fresh PRNG instances, ensuring no shared mutable state between concurrent operations.

Basic Usage

Recover original coefficients after extracting the message with f5extract:

import (
    "github.com/0verkilll/f5imagerecover"
    "github.com/0verkilll/f5extract"
)

// Step 1: Extract the hidden message using f5extract
extractor := f5extract.NewExtractor(hasher, permutator, randomFactory)
result, err := extractor.Extract(stegoCoefficients, password)
if err != nil {
    log.Fatalf("extraction failed: %v", err)
}

// Step 2: Recover original coefficients using f5imagerecover
originalCoeffs, err := f5imagerecover.RecoverCoefficients(
    stegoCoefficients,
    password,
    result.Data, // The extracted message
)
if err != nil {
    log.Fatalf("recovery failed: %v", err)
}

// originalCoeffs now contains the coefficients BEFORE embedding

Boolean Convenience

For simple success/failure checking without error details:

originalCoeffs, ok := f5imagerecover.TryRecoverCoefficients(
    stegoCoefficients,
    password,
    extractedMessage,
)
if !ok {
    // Recovery failed (wrong password, corrupted data, etc.)
    return
}
// Use recovered coefficients

In-Place Recovery

For memory efficiency when you own the coefficient array:

// Modifies stegoCoefficients directly
err := f5imagerecover.RecoverCoefficientsInPlace(
    stegoCoefficients,
    password,
    extractedMessage,
)

Configuration Options

Use functional options to customize recovery behavior:

// Limit maximum file size (default: 10MB)
originalCoeffs, err := f5imagerecover.RecoverCoefficients(
    stegoCoefficients, password, message,
    f5imagerecover.WithMaxFileSize(1_000_000), // 1MB limit
)

// Enable strict mode (fail on any inconsistency)
originalCoeffs, err := f5imagerecover.RecoverCoefficients(
    stegoCoefficients, password, message,
    f5imagerecover.WithStrictMode(true),
)

// Disable shrinkage recovery
originalCoeffs, err := f5imagerecover.RecoverCoefficients(
    stegoCoefficients, password, message,
    f5imagerecover.WithShrinkageRecovery(false),
)

// Enable debug logging for diagnostics
originalCoeffs, err := f5imagerecover.RecoverCoefficients(
    stegoCoefficients, password, message,
    f5imagerecover.WithLogger(myLogger),
)

Relationship to Other Packages

This package is part of the F5 steganography toolset:

  • f5embed: Embeds messages into JPEG coefficients (forward operation)
  • f5extract: Extracts embedded messages from coefficients
  • f5imagerecover: Recovers original coefficients (reverse of f5embed)

The recovery workflow requires f5extract's output as input:

JPEG Image -> f5extract -> Hidden Message
                              |
                              v
JPEG Image -> f5imagerecover -> Original Coefficients
                 (with message)

Reference

This implementation follows the algorithm described in:

Westfeld, A. (2001). "F5 - A Steganographic Algorithm: High Capacity
Despite Better Steganalysis." Information Hiding, LNCS 2137, pp. 289-302.

For Java compatibility, the PRNG matches java.security.SecureRandom's SHA1PRNG algorithm, enabling interoperability with PixelKnot and F5.jar.

Index

Constants

View Source
const (
	// DefaultMaxFileSize is the maximum allowed size for recovered data in bytes.
	// See f5core.DefaultMaxFileSize for documentation.
	DefaultMaxFileSize = f5core.DefaultMaxFileSize

	// MaxKParameter is the maximum valid k parameter value for matrix encoding.
	// See f5core.MaxKParameter for documentation.
	MaxKParameter = f5core.MaxKParameter

	// MinKParameter is the minimum valid k parameter value for matrix encoding.
	// See f5core.MinKParameter for documentation.
	MinKParameter = f5core.MinKParameter
)

Re-export constants from f5core for backward compatibility.

Variables

View Source
var (
	// ErrRecoveryFailedBase is the base error for recovery failures.
	ErrRecoveryFailedBase = errors.New("recovery failed")

	// ErrInvalidExtractedMessageBase is the base error for invalid extracted message.
	ErrInvalidExtractedMessageBase = errors.New("invalid extracted message")

	// ErrPermutationMismatchBase is the base error for permutation mismatch.
	ErrPermutationMismatchBase = errors.New("permutation mismatch")

	// ErrShrinkageRecoveryFailedBase is the base error for shrinkage recovery failure.
	ErrShrinkageRecoveryFailedBase = errors.New("shrinkage recovery failed")
)

Sentinel errors for error checking with errors.Is(). These provide a way to check error types without string matching.

View Source
var (
	ErrEmptyCodeWord        = f5matrix.ErrEmptyCodeWord
	ErrInvalidKParameter    = f5matrix.ErrInvalidKParameter
	ErrCodeWordSizeMismatch = f5matrix.ErrCodeWordSizeMismatch
	ErrMessageBitsExceedK   = f5matrix.ErrMessageBitsExceedK
)

Re-export matrix encoding errors from f5matrix for backward compatibility.

View Source
var DeZigZagTable = f5core.DeZigZag[:]

DeZigZagTable is an alias to f5core.DeZigZag for backward compatibility. See f5core.DeZigZag for full documentation.

Functions

func ApplyDeZigZag

func ApplyDeZigZag(shuffled int) int

ApplyDeZigZag converts a shuffled coefficient index to its de-zigzagged position. This is an alias to f5core.ApplyDeZigZag for backward compatibility. See f5core.ApplyDeZigZag for full documentation.

func ApplyHeaderXOR

func ApplyHeaderXOR(headerBits int, random RandomSource) int

ApplyHeaderXOR decrypts the header bits by XORing with 4 PRNG bytes.

The XOR bytes are consumed from the PRNG AFTER the permutation has been generated. This ordering is critical for Java F5 compatibility.

The XOR is applied in little-endian byte order:

  • Byte 0: XOR with bits 0-7 (no shift)
  • Byte 1: XOR with bits 8-15 (shift left 8)
  • Byte 2: XOR with bits 16-23 (shift left 16)
  • Byte 3: XOR with bits 24-31 (shift left 24)

Note: Each byte is converted to a signed value before XOR, which causes sign-extension for values >= 128. This matches Java's behavior.

Parameters:

headerBits - The raw header bits extracted from coefficients
random     - The RandomSource to read XOR bytes from

Returns:

The decrypted header value after XOR

Example:

// After extracting header bits and continuing from permutation
decryptedHeader := ApplyHeaderXOR(headerBits, random)
k := ExtractKParameter(decryptedHeader)
fileSize := ExtractFileSize(decryptedHeader)

func CodeWordLength

func CodeWordLength(k int) int

CodeWordLength returns the code word length n for a given k parameter. This is an alias to f5matrix.CodeWordLength for backward compatibility. See f5matrix.CodeWordLength for full documentation.

func ComputeCodeWordHash

func ComputeCodeWordHash(codeWord []int) int

ComputeCodeWordHash computes the hash function for matrix encoding. This is an alias to f5matrix.ComputeCodeWordHash for backward compatibility. See f5matrix.ComputeCodeWordHash for full documentation.

func DetectShrinkage

func DetectShrinkage(coefficient int16) bool

DetectShrinkage determines if modifying a coefficient will cause shrinkage.

This is a helper function that matches f5embed.DetectShrinkage for consistency. Shrinkage occurs when decrementing the absolute value of a coefficient produces zero. This happens only when the coefficient is 1 or -1.

Note: This function is used during the FORWARD direction (embedding). For recovery, use DetectShrinkageCandidate instead.

Parameters:

  • coefficient: A JPEG DCT coefficient value

Returns:

  • true if the coefficient is 1 or -1 (will shrink to 0 when modified)
  • false otherwise

Example:

DetectShrinkage(1)   // Returns true
DetectShrinkage(-1)  // Returns true
DetectShrinkage(2)   // Returns false
DetectShrinkage(0)   // Returns false

func DetectShrinkageCandidate

func DetectShrinkageCandidate(currentCoeff int16, expectedBit int) bool

DetectShrinkageCandidate determines if a coefficient is a shrinkage candidate.

During F5 embedding, shrinkage occurs when a +1 or -1 coefficient is modified, becoming 0. Since 0 cannot encode steganographic data, the bit must be re-embedded using the next coefficient. During recovery, we need to identify which 0 coefficients were originally +/-1.

A coefficient is a shrinkage candidate if:

  • It is currently 0

The expectedBit parameter is included for interface consistency and potential future validation, but the primary detection is based on the coefficient being 0. When we're walking the recovery path and encounter a 0 at a position that was supposed to be modified, we know it's a shrinkage case.

Parameters:

  • currentCoeff: The current coefficient value (after embedding)
  • expectedBit: The bit that was supposed to be encoded (0 or 1)

Returns:

  • true if the coefficient is 0 (potential shrinkage candidate)
  • false otherwise

Example:

DetectShrinkageCandidate(0, 1)   // Returns true (was +1 before shrinkage)
DetectShrinkageCandidate(0, 0)   // Returns true (was -1 before shrinkage)
DetectShrinkageCandidate(4, 0)   // Returns false (not a shrinkage case)
DetectShrinkageCandidate(-4, 1)  // Returns false (not a shrinkage case)

func DetermineChangePosition

func DetermineChangePosition(codeWord []int, messageBits, k int) (changePosition int, err error)

DetermineChangePosition determines which coefficient position was changed during embedding to encode the given message bits. This is an alias to f5matrix.DetermineChangePosition for backward compatibility. See f5matrix.DetermineChangePosition for full documentation.

func ErrEmptyCoefficients

func ErrEmptyCoefficients() error

ErrEmptyCoefficients returns an error indicating that no coefficients were provided.

func ErrEmptyExtractedMessage

func ErrEmptyExtractedMessage() error

ErrEmptyExtractedMessage returns an error indicating that the extracted message is empty.

Recovery requires the extracted message to determine which coefficients were modified. An empty message means there's nothing to recover.

func ErrEmptyPassword

func ErrEmptyPassword() error

ErrEmptyPassword returns an error indicating that the password was empty.

func ErrFileSizeZeroOrNegative

func ErrFileSizeZeroOrNegative(fileSize int) error

ErrFileSizeZeroOrNegative returns an error indicating that the file size is zero or negative.

func ErrInsufficientCoefficientsHeader

func ErrInsufficientCoefficientsHeader(bitsExtracted int) error

ErrInsufficientCoefficientsHeader returns an error indicating insufficient coefficients for header extraction.

func ErrInsufficientCoefficientsMessage

func ErrInsufficientCoefficientsMessage(bytesExtracted, expectedBytes int) error

ErrInsufficientCoefficientsMessage returns an error indicating insufficient coefficients for message extraction.

func ErrInvalidExtractedMessage

func ErrInvalidExtractedMessage(detail string) error

ErrInvalidExtractedMessage returns an error indicating that the extracted message doesn't match the coefficient state, suggesting wrong password or corrupted data.

This error occurs when the message bits XORed with the code word hash produce invalid change positions that don't align with the actual coefficient modifications.

Parameters:

  • detail: Specific detail about the mismatch

Example:

err := ErrInvalidExtractedMessage("change position exceeds code word length")

func ErrInvalidFileSize

func ErrInvalidFileSize(fileSize, maxFileSize int) error

ErrInvalidFileSize returns an error indicating that the file size exceeds the maximum allowed.

func ErrInvalidK

func ErrInvalidK(k int) error

ErrInvalidK returns an error indicating that the k parameter is invalid.

func ErrPermutationMismatch

func ErrPermutationMismatch() error

ErrPermutationMismatch returns an error indicating that the PRNG-generated permutation doesn't align with the coefficient state.

This typically indicates:

  • Wrong password was used
  • Coefficients were modified after embedding
  • The image was not embedded with F5 algorithm

Example:

err := ErrPermutationMismatch()
if errors.Is(err, ErrPermutationMismatchBase) { ... }

func ErrRecoveryFailed

func ErrRecoveryFailed(reason string) error

ErrRecoveryFailed returns an error indicating that recovery failed with a specific reason.

Use this for general recovery failures that don't fit more specific error types. The error wraps ErrRecoveryFailedBase for use with errors.Is().

Parameters:

  • reason: A description of why recovery failed

Example:

err := ErrRecoveryFailed("header validation failed")
if errors.Is(err, ErrRecoveryFailedBase) { ... }

func ErrShrinkageRecoveryFailed

func ErrShrinkageRecoveryFailed(position int) error

ErrShrinkageRecoveryFailed returns an error indicating that shrinkage recovery could not determine the original coefficient value.

During shrinkage recovery, we need to determine whether a zero coefficient was originally +1 or -1. This error occurs when that determination fails, usually due to insufficient context or corrupted data.

Parameters:

  • position: The position in the coefficient array where recovery failed

Example:

err := ErrShrinkageRecoveryFailed(1234)

func ExtractBit

func ExtractBit(coefficient int16) int

ExtractBit extracts a single bit from a DCT coefficient using F4 encoding. This is an alias to f5coefficient.GetStegoBit for backward compatibility. See f5coefficient.GetStegoBit for full documentation.

func ExtractFileSize

func ExtractFileSize(headerBits int) int

ExtractFileSize extracts the file size from the decrypted header.

The file size is stored in bits 0-22 of the header (23 bits). Maximum theoretical value: 0x7FFFFF = 8,388,607 bytes (~8MB).

Parameters:

headerBits - The decrypted header value (after XOR)

Returns:

The file size in bytes (may be invalid, use ValidateFileSize)

Example:

fileSize := ExtractFileSize(decryptedHeader)
if err := ValidateFileSize(fileSize, cfg.MaxFileSize); err != nil {
    return nil, err
}

func ExtractHeaderBits

func ExtractHeaderBits(coefficients []int16, permutation []int) (headerBits, nextIndex int, err error)

ExtractHeaderBits extracts 32 bits from the first 32 usable coefficients.

This function implements the F5 header extraction algorithm exactly as specified in the Java reference implementation. It processes coefficients in permuted order, skipping DC coefficients and zeros, and extracts one bit from each usable coefficient.

The extraction follows F4 encoding rules:

  • Positive coefficients: even=0, odd=1
  • Negative coefficients: even=1, odd=0

Parameters:

coefficients - The quantized DCT coefficients from the JPEG image
permutation  - The Fisher-Yates permutation determining coefficient order

Returns:

headerBits - The 32-bit header value (before XOR decryption)
nextIndex  - The permutation index to continue extraction from
err        - Error if insufficient usable coefficients for 32 bits

Example:

headerBits, nextIdx, err := ExtractHeaderBits(coefficients, permutation)
if err != nil {
    return nil, fmt.Errorf("header extraction failed: %w", err)
}
// Continue with XOR decryption and parameter extraction

func ExtractKParameter

func ExtractKParameter(headerBits int) int

ExtractKParameter extracts the k parameter from the decrypted header.

The k parameter is stored in bits 24-31 of the header. After extraction, it is taken modulo 32 with negative adjustment for Java compatibility.

The k parameter determines the matrix encoding strength:

  • n = 2^k - 1 (coefficients needed per extraction group)
  • Higher k = better embedding efficiency but needs more coefficients
  • k=1: Simple 1:1 mapping (1 coefficient per bit)
  • k=8: 255 coefficients extract 8 bits

Parameters:

headerBits - The decrypted header value (after XOR)

Returns:

The k parameter value (may be outside valid range 1-8, use ValidateKParameter)

Example:

k := ExtractKParameter(decryptedHeader)
if err := ValidateKParameter(k); err != nil {
    return nil, err
}

func ExtractStegoBits

func ExtractStegoBits(coefficients []int16) []int

ExtractStegoBits extracts the steganographic bit values from a slice of coefficients. This is an alias to f5matrix.ExtractStegoBits for backward compatibility. See f5matrix.ExtractStegoBits for full documentation.

func GetLogger

func GetLogger() logger.Logger

GetLogger returns the package logger, or NopLogger if not set. This is useful for inspecting the current logger or for testing.

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.

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.

func IsRecoveryError

func IsRecoveryError(err error) bool

IsRecoveryError checks if an error is a recovery-related error. This is a convenience function for checking multiple recovery error types.

func IsUsableCoefficient

func IsUsableCoefficient(shuffled, zigzag int, coefficient int16) bool

IsUsableCoefficient determines if a coefficient can be used for bit extraction.

This wrapper maintains backward compatibility with the 3-parameter signature. The zigzag parameter is unused but retained for API compatibility.

See f5coefficient.IsUsableCoefficient for full documentation.

func IsUsableCoefficientSimple

func IsUsableCoefficientSimple(index int, coefficient int16) bool

IsUsableCoefficientSimple determines if a coefficient can be used for steganographic data. This is an alias to f5coefficient.IsUsableCoefficient for backward compatibility. See f5coefficient.IsUsableCoefficient for full documentation.

func NewF5PRNGFactory

func NewF5PRNGFactory() f5prng.PRNGFactory

NewF5PRNGFactory creates a new f5prng.PRNGFactory for direct use with f5prng types. This is the recommended way to create PRNG instances for new code.

Example:

factory := NewF5PRNGFactory()
prng := factory.NewPRNG()
prng.Seed([]byte("password"))
defer prng.Clear()

func NextSignedByte

func NextSignedByte(random RandomSource) int

NextSignedByte gets the next byte from the PRNG as a Java-compatible signed integer.

In Java, bytes are signed (-128 to 127). When used for XOR operations, the signed byte is sign-extended to an int. This function replicates that behavior exactly for compatibility with Java F5 implementations.

The conversion process:

  1. Read one unsigned byte from PRNG (0-255)
  2. Interpret as Java signed byte (-128 to 127)
  3. Sign-extend to Go int for XOR operations

Parameters:

random - The RandomSource providing PRNG bytes

Returns:

A signed integer in range [-128, 127], sign-extended for XOR

Example:

// PRNG byte 0xFF becomes -1 (sign-extended to 0xFFFFFFFF for 32-bit XOR)
// PRNG byte 0x80 becomes -128 (sign-extended to 0xFFFFFF80)
// PRNG byte 0x7F becomes 127 (no sign extension needed)
signedByte := NextSignedByte(random)

func RecoverCoefficients

func RecoverCoefficients(stegoCoeffs []int16, password string, extractedMessage []byte, opts ...Option) ([]int16, error)

RecoverCoefficients recovers the original DCT coefficients from steganographic coefficients.

This function reverses the F5 embedding process by:

  1. Initializing PRNG with password (same as f5embed)
  2. Generating permutation (same PRNG consumption as f5embed)
  3. Processing header coefficients (may have been modified)
  4. Walking coefficients in permuted order, matching embedding path exactly
  5. Reversing modifications by incrementing absolute values
  6. Handling shrinkage recovery by restoring 0s to +/-1

The function creates a COPY of coefficients and does not modify the original.

Parameters:

  • stegoCoeffs: The steganographic DCT coefficients to recover from
  • password: The password used during embedding
  • extractedMessage: The message extracted using f5extract
  • opts: Optional configuration options (WithMaxFileSize, WithLogger, WithStrictMode, WithShrinkageRecovery)

Returns:

  • []int16: The recovered original coefficients
  • error: If recovery fails

func RecoverCoefficientsInPlace

func RecoverCoefficientsInPlace(stegoCoeffs []int16, password string, extractedMessage []byte, opts ...Option) error

RecoverCoefficientsInPlace recovers the original DCT coefficients in place.

This is the memory-efficient variant of RecoverCoefficients that modifies the input slice directly. Use this when you own the coefficient array and want to avoid an extra copy.

Parameters:

  • stegoCoeffs: The steganographic DCT coefficients to recover (modified in place)
  • password: The password used during embedding
  • extractedMessage: The message extracted using f5extract
  • opts: Optional configuration options

Returns:

  • error: If recovery fails

func RestoreShrunkCoefficient

func RestoreShrunkCoefficient(expectedBit int) int16

RestoreShrunkCoefficient restores a shrunk coefficient to its original +/-1 value.

When a coefficient becomes 0 due to shrinkage during embedding, we can determine its original value based on what bit it was supposed to encode:

  • If expectedBit is 1: original was +1 (positive odd encodes 1 in F4)
  • If expectedBit is 0: original was -1 (negative odd encodes 0 in F4)

This follows from the F4 encoding rules:

  • Positive odd coefficients encode 1: e.g., +1 encodes 1
  • Negative odd coefficients encode 0: e.g., -1 encodes 0

The reasoning is: before shrinkage, the coefficient was +1 or -1. When modified by F5 embedding (decrement absolute value), it became 0. We can infer the original sign from the bit that was being encoded at that position.

Parameters:

  • expectedBit: The bit that was encoded at this position (0 or 1)

Returns:

  • +1 if expectedBit is 1 (positive odd encodes 1)
  • -1 if expectedBit is 0 (negative odd encodes 0)

Example:

RestoreShrunkCoefficient(1) // Returns +1
RestoreShrunkCoefficient(0) // Returns -1

func ReverseModification

func ReverseModification(coefficient int16) int16

ReverseModification reverses an F5 embedding modification by incrementing the absolute value of a coefficient. This is an alias to f5coefficient.ReverseModification for backward compatibility. See f5coefficient.ReverseModification for full documentation.

func SetLogger

func SetLogger(l logger.Logger)

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

When a logger is configured, the package will log diagnostic information at various stages of the extraction process:

  • Permutation generation (size, timing)
  • Header extraction (k parameter, file size)
  • Matrix encoding progress (coefficients processed, bytes extracted)
  • Error conditions with context

Example:

// Enable debug logging
f5imagerecover.SetLogger(myDebugLogger)

// Disable logging
f5imagerecover.SetLogger(nil)

func SetTranslator

func SetTranslator(translator TranslatorProvider)

SetTranslator sets the global translator for this package. Pass nil to disable translations and use default English messages. The translator is shared across all goroutines and is thread-safe.

func TryRecoverCoefficients

func TryRecoverCoefficients(stegoCoeffs []int16, password string, extractedMessage []byte, opts ...Option) ([]int16, bool)

TryRecoverCoefficients attempts to recover coefficients, returning false instead of an error.

This is a convenience wrapper that returns (nil, false) on any error.

func TryRecoverCoefficientsInPlace

func TryRecoverCoefficientsInPlace(stegoCoeffs []int16, password string, extractedMessage []byte, opts ...Option) bool

TryRecoverCoefficientsInPlace attempts to recover coefficients in place, returning false instead of an error.

This is a convenience wrapper that returns false on any error.

func ValidateFileSize

func ValidateFileSize(fileSize, maxFileSize int) error

ValidateFileSize checks if the file size is valid and within limits.

The file size must be:

  • Greater than 0 (empty files are invalid)
  • Less than or equal to maxFileSize (to prevent memory exhaustion)

Parameters:

fileSize    - The file size extracted from the header
maxFileSize - The configured maximum file size limit

Returns:

nil if valid, error describing the validation failure otherwise

Example:

if err := ValidateFileSize(fileSize, cfg.MaxFileSize); err != nil {
    return nil, fmt.Errorf("invalid F5 header: %w", err)
}

func ValidateKParameter

func ValidateKParameter(k int) error

ValidateKParameter checks if the k parameter is within the valid range.

For F5 matrix encoding, k must be in the range [1, 8]:

  • k < 1: Invalid (no meaningful extraction)
  • k > 8: Invalid (would require excessive coefficients)

Parameters:

k - The k parameter extracted from the header

Returns:

nil if valid, error describing the validation failure otherwise

Example:

if err := ValidateKParameter(k); err != nil {
    return nil, fmt.Errorf("invalid F5 header: %w", err)
}

func WrapError

func WrapError(err error, message string) error

WrapError wraps an error with additional context using fmt.Errorf. This helper maintains proper error wrapping while supporting i18n.

Types

type Config

type Config struct {
	// Logger is an optional logger for debug output during recovery.
	// When nil, no logging occurs (silent operation).
	//
	// Default: nil (no logging)
	Logger logger.Logger

	// MaxFileSize is the maximum allowed size for recovered data in bytes.
	// If the extracted header indicates a larger file, Recover returns an error.
	//
	// Default: DefaultMaxFileSize (10MB)
	MaxFileSize int

	// StrictMode controls how inconsistencies are handled during recovery.
	// When true, any mismatch between expected and actual coefficient state
	// causes the recovery to fail with an error.
	// When false, recovery continues on a best-effort basis, attempting to
	// recover as many coefficients as possible despite inconsistencies.
	//
	// Default: false (best-effort recovery)
	StrictMode bool

	// ShrinkageRecovery controls whether shrinkage recovery is attempted.
	// When true, the recovery process attempts to identify coefficients that
	// were +1 or -1 before embedding and became 0 (shrinkage).
	// When false, zero coefficients are left as-is.
	//
	// Default: true (enable shrinkage recovery)
	ShrinkageRecovery bool
}

Config holds the resolved configuration for a Recover operation. This struct is populated by applying Option functions to default values.

Fields are exported to allow inspection in tests, but should generally be accessed through the functional options pattern.

func ApplyOptions

func ApplyOptions(opts ...Option) *Config

ApplyOptions applies the given options to a default Config and returns the result. This is an internal helper used by Recover and TryRecover.

func DefaultConfig

func DefaultConfig() *Config

DefaultConfig returns a Config with default values. This is used as the starting point before applying options.

Default values:

  • MaxFileSize: DefaultMaxFileSize (10MB)
  • Logger: nil (no logging)
  • StrictMode: false (best-effort recovery)
  • ShrinkageRecovery: true (enabled)

type DefaultRandomSourceFactory

type DefaultRandomSourceFactory struct{}

DefaultRandomSourceFactory creates the default RandomSource implementation. This factory uses f5prng internally to create SecureRandom instances compatible with Java's SHA1PRNG.

func NewDefaultRandomSourceFactory

func NewDefaultRandomSourceFactory() *DefaultRandomSourceFactory

NewDefaultRandomSourceFactory creates a new DefaultRandomSourceFactory.

func (*DefaultRandomSourceFactory) New

New creates a new RandomSource instance. The hasher parameter is accepted for interface compatibility but the factory uses f5prng internally which creates its own SHA-1 hasher.

type Hasher

type Hasher interface {
	// Sum computes and returns the hash of the provided data.
	// The returned slice is the final hash value (e.g., 20 bytes for SHA-1).
	// Multiple calls to Sum with the same data must return identical results.
	//
	// Parameters:
	//   data - The input bytes to hash
	//
	// Returns:
	//   The computed hash as a byte slice
	Sum(data []byte) []byte
}

Hasher defines the interface for cryptographic hash functions. This abstraction follows the Single Responsibility Principle by focusing solely on hashing operations, and the Dependency Inversion Principle by allowing different hash implementations (SHA-1, SHA-256, etc.) to be used interchangeably.

For F5 steganography compatibility, implementations should use SHA-1 to match Java's SecureRandom SHA1PRNG algorithm.

Note: This interface differs from f5prng.Hasher in method signature. f5prng.Hasher.Sum returns ([]byte, error) while this Hasher.Sum returns []byte. This local interface is maintained for backward compatibility with existing f5imagerecover code that depends on the simpler signature.

Thread Safety: Implementations need not be thread-safe. Create separate instances for concurrent use.

type HeaderResult

type HeaderResult struct {
	// K is the matrix encoding parameter (1-8).
	K int

	// FileSize is the expected message size in bytes.
	FileSize int

	// NextIndex is the permutation index to continue extraction from.
	NextIndex int
}

HeaderResult contains the extracted and validated header information. This struct encapsulates the result of successful header extraction and validation, providing all values needed for message extraction.

func ExtractHeader

func ExtractHeader(
	coefficients []int16,
	permutation []int,
	random RandomSource,
	maxFileSize int,
) (*HeaderResult, error)

ExtractHeader performs complete header extraction, XOR decryption, and validation.

This is the high-level function that combines all header extraction steps:

  1. Extract 32 bits from first 32 usable coefficients
  2. Apply XOR decryption with 4 PRNG bytes
  3. Extract k parameter and file size
  4. Validate both parameters

Parameters:

coefficients - The quantized DCT coefficients from the JPEG image
permutation  - The Fisher-Yates permutation determining coefficient order
random       - The RandomSource (state should be after permutation generation)
maxFileSize  - The maximum allowed file size for validation

Returns:

*HeaderResult containing k, fileSize, and nextIndex on success
error if extraction or validation fails

Example:

result, err := ExtractHeader(coefficients, permutation, random, cfg.MaxFileSize)
if err != nil {
    return nil, fmt.Errorf("header extraction failed: %w", err)
}
// Use result.K, result.FileSize, result.NextIndex for message extraction

type Option

type Option func(*Config)

Option is a functional option for configuring Recover and TryRecover calls. Options follow the functional options pattern for clean, extensible configuration.

Example usage:

data, err := RecoverCoefficients(
    coefficients,
    password,
    extractedMessage,
    WithMaxFileSize(5_000_000),      // 5MB limit
    WithLogger(myLogger),             // Enable debug logging
    WithStrictMode(true),             // Fail on any inconsistency
    WithShrinkageRecovery(true),      // Enable shrinkage recovery
)

func WithLogger

func WithLogger(l logger.Logger) Option

WithLogger sets a logger for debug output during recovery. When a logger is configured, Recover will log diagnostic information at various stages of the recovery process.

Logged information includes:

  • Permutation generation (size, timing)
  • Header extraction (k parameter, file size)
  • Recovery progress (coefficients processed, modifications reversed)
  • Shrinkage recovery events (zeros restored to +/-1)
  • Error conditions with context

The logger is used for the duration of a single Recover call and does not affect the global package logger or other concurrent Recover calls.

Parameters:

l - The logger to use (nil disables logging for this call)

Example:

// Enable debug logging for a specific recovery
data, err := RecoverCoefficients(coefficients, password, msg, WithLogger(myDebugLogger))

// Disable logging (explicit)
data, err := RecoverCoefficients(coefficients, password, msg, WithLogger(nil))

func WithMaxFileSize

func WithMaxFileSize(bytes int) Option

WithMaxFileSize sets the maximum allowed file size for recovered data. If the extracted F5 header indicates a file size larger than this limit, Recover will return an error before attempting to extract the full message.

This option provides protection against:

  • Memory exhaustion from malformed headers
  • Long-running extractions on corrupted data
  • Denial-of-service attacks via crafted images

Parameters:

bytes - Maximum file size in bytes (must be positive)

If bytes <= 0, the default limit (DefaultMaxFileSize) is used.

Example:

// Limit extraction to 1MB
data, err := RecoverCoefficients(coefficients, password, msg, WithMaxFileSize(1_000_000))

// Use a larger limit for known large files
data, err := RecoverCoefficients(coefficients, password, msg, WithMaxFileSize(50_000_000))

func WithShrinkageRecovery

func WithShrinkageRecovery(enable bool) Option

WithShrinkageRecovery sets whether shrinkage recovery should be attempted.

During F5 embedding, shrinkage occurs when modifying a +1 or -1 coefficient causes it to become 0. These zeros no longer carry steganographic data. Shrinkage recovery attempts to restore these coefficients to their original +/-1 values based on the message bits that were being encoded.

When shrinkage recovery is enabled (true, default):

  • Zeros at change positions are analyzed to determine if they were shrunk
  • Original +/-1 values are restored based on expected bit values
  • Provides more accurate coefficient recovery

When shrinkage recovery is disabled (false):

  • Zero coefficients are left unchanged
  • Faster recovery but less accurate for images with shrinkage
  • Useful for quick analysis where shrinkage details don't matter

Parameters:

enable - true to enable shrinkage recovery, false to skip it

Example:

// Enable shrinkage recovery (default)
data, err := RecoverCoefficients(coefficients, password, msg, WithShrinkageRecovery(true))

// Disable shrinkage recovery for faster processing
data, err := RecoverCoefficients(coefficients, password, msg, WithShrinkageRecovery(false))

func WithStrictMode

func WithStrictMode(strict bool) Option

WithStrictMode sets whether recovery should fail on any inconsistency.

When strict mode is enabled (true):

  • Any mismatch between expected and actual coefficient state causes an error
  • Helps detect wrong passwords or corrupted data early
  • Provides more reliable error messages about what went wrong

When strict mode is disabled (false, default):

  • Recovery continues on a best-effort basis
  • Attempts to recover as many coefficients as possible
  • Useful when some data corruption is acceptable
  • May produce partially correct results with wrong password

Parameters:

strict - true to enable strict mode, false for best-effort recovery

Example:

// Fail immediately on any inconsistency
data, err := RecoverCoefficients(coefficients, password, msg, WithStrictMode(true))

// Best-effort recovery (default)
data, err := RecoverCoefficients(coefficients, password, msg, WithStrictMode(false))

type PRNGFactory

type PRNGFactory = f5prng.PRNGFactory

PRNGFactory is a type alias to f5prng.PRNGFactory for convenience. This provides access to the unified PRNG factory interface.

Example usage:

factory := f5prng.NewDefaultFactory()
prng := factory.NewPRNG()
prng.Seed([]byte("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 implementation 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 implementation 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.

For F5 steganography, this generates the Fisher-Yates shuffle that determines the order in which DCT coefficients are processed during extraction.

Thread Safety: Implementations need not be thread-safe. Create separate instances or use external synchronization for concurrent use.

type RandomSource deprecated

type RandomSource = f5prng.RandomSource

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

Deprecated: Use f5prng.RandomSource directly. This type alias is provided for backward compatibility and may be removed in a future major version. The f5prng package provides the unified PRNG interface used across all F5 steganography packages.

Migration: Replace f5imagerecover.RandomSource with f5prng.RandomSource in your code. The interface signature is identical.

type RandomSourceFactory

type RandomSourceFactory interface {
	// New creates and returns a new RandomSource instance.
	// Each call should return a fresh, independent instance that can be
	// safely used without affecting other instances.
	//
	// Parameters:
	//   hasher - The Hasher to use for the RandomSource (for SHA1PRNG compatibility)
	//
	// Returns:
	//   A new RandomSource instance ready to be seeded
	New(hasher Hasher) RandomSource
}

RandomSourceFactory creates new RandomSource instances. This factory pattern enables the Recoverer to create fresh PRNG instances per Recover() call for thread safety, without being coupled to a specific RandomSource implementation.

Thread Safety: Implementations must be safe for concurrent use. The factory itself should be stateless or use proper synchronization.

type Recoverer

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

Recoverer handles the recovery of original DCT coefficients from F5-embedded images. It uses dependency injection for PRNG, hashing, and permutation generation to enable testing and ensure compatibility with the F5 algorithm.

func NewRecoverer

func NewRecoverer(hasher Hasher, randomFactory RandomSourceFactory, permutator Permutator) *Recoverer

NewRecoverer creates a new Recoverer with the provided dependencies. All dependencies must be non-nil.

Parameters:

  • hasher: Hash function for PRNG seeding (typically SHA-1)
  • randomFactory: Factory for creating RandomSource instances
  • permutator: Permutation generator (Fisher-Yates)

Returns:

  • A configured Recoverer instance

type TranslatorProvider

type TranslatorProvider interface {
	// Translate returns the translated string for the given key.
	Translate(key string) string

	// TranslateWithArgs returns the translated string with format arguments.
	TranslateWithArgs(key string, args ...interface{}) string

	// HasKey returns true if the translation key exists.
	HasKey(key string) bool

	// SetLocale changes the current locale.
	SetLocale(locale string)

	// GetLocale returns the current locale.
	GetLocale() string
}

TranslatorProvider defines the interface for translation providers. This allows loose coupling - the package works with or without i18n. The interface matches github.com/0verkilll/i18n.Translator.

func GetTranslator

func GetTranslator() TranslatorProvider

GetTranslator returns the global translator, or nil if not set.

func NewTranslator

func NewTranslator(locale string) (TranslatorProvider, error)

NewTranslator creates a new translator configured for the f5imagerecover package. If locale is empty, it will auto-detect from environment variables. The translator uses embedded locale files and the i18n package for translation.

Jump to

Keyboard shortcuts

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