Commit b00bf687 authored by narugo1992's avatar narugo1992
Browse files

dev(narugo): add docs for the migration

parent 89db45bf
Loading
Loading
Loading
Loading
+24 −0
Original line number Diff line number Diff line
"""
This module provides functionality for reading and writing metadata and data using
LSB (Least Significant Bit) steganography in images.

Imported from .read:

- ImageLsbDataExtractor: Class for extracting LSB data from images.
- LSBExtractor: Class for extracting LSB data from byte arrays.
- LSBReadError: Exception raised when there's an error reading LSB data.
- read_lsb_metadata: Function to read metadata embedded in an image using LSB.
- read_lsb_raw_bytes: Function to read raw bytes embedded in an image using LSB.

Imported from .write:

- serialize_pnginfo: Function to serialize PNG metadata.
- serialize_json: Function to serialize JSON-compatible data.
- inject_data: Function to inject data into an image using LSB.
- write_lsb_metadata: Function to write metadata into an image using LSB.
- write_lsb_raw_bytes: Function to write raw bytes into an image using LSB.

This module combines reading and writing capabilities for LSB steganography,
allowing users to embed and extract data or metadata from images seamlessly.
"""

from .read import ImageLsbDataExtractor, LSBExtractor, LSBReadError, read_lsb_metadata, read_lsb_raw_bytes
from .write import serialize_pnginfo, serialize_json, inject_data, write_lsb_metadata, write_lsb_raw_bytes
+61 −3
Original line number Diff line number Diff line
@@ -36,7 +36,7 @@ from imgutils.data import ImageTyping
from ...data import load_image


class LSBExtractor(object):
class LSBExtractor:
    """
    A class for extracting data hidden in the least significant bits of image pixels.

@@ -67,6 +67,8 @@ class LSBExtractor(object):

        This method updates the internal state of the extractor,
        moving to the next pixel as necessary.

        :raises IOError: If there are no more bits to extract.
        """
        if self.row < self.rows and self.col < self.cols:
            bit = self.data[self.row, self.col, self.dim - 1] & 1
@@ -84,6 +86,8 @@ class LSBExtractor(object):
        """
        Extract and return one byte of data.

        This method extracts 8 bits from the image data to form a single byte.

        :return: A single byte of extracted data.
        :rtype: bytearray
        """
@@ -98,6 +102,8 @@ class LSBExtractor(object):
        """
        Extract and return the next n bytes of data.

        This method extracts multiple bytes from the image data.

        :param n: The number of bytes to extract.
        :type n: int
        :return: The extracted bytes.
@@ -115,6 +121,8 @@ class LSBExtractor(object):
        """
        Extract and return a 32-bit integer from the image data.

        This method reads 4 bytes and interprets them as a big-endian 32-bit integer.

        :return: The extracted 32-bit integer, or None if not enough data is available.
        :rtype: int or None
        """
@@ -126,7 +134,7 @@ class LSBExtractor(object):
            return None


class ImageLsbDataExtractor(object):
class ImageLsbDataExtractor:
    """
    A class for extracting hidden JSON data from images using LSB steganography.

@@ -147,6 +155,18 @@ class ImageLsbDataExtractor(object):
        self._magic_bytes = magic.encode('utf-8')

    def extract_data(self, image: Image.Image) -> bytes:
        """
        Extract hidden data from the given image.

        This method checks for the magic number, reads the length of the hidden data,
        and then extracts the data.

        :param image: The image to extract data from.
        :type image: Image.Image
        :return: The extracted raw data.
        :rtype: bytes
        :raises ValueError: If the image is not in RGBA mode or if the magic number doesn't match.
        """
        if image.mode != 'RGBA':
            raise ValueError(f'Image should be in RGBA mode, but {image.mode!r} found.')
        # noinspection PyTypeChecker
@@ -167,12 +187,38 @@ class ImageLsbDataExtractor(object):


class LSBReadError(Exception):
    """
    Custom exception class for LSB reading errors.

    This exception is raised when there's an error during the LSB data extraction process.

    :param err: The original exception that caused the LSB read error.
    :type err: Exception
    """

    def __init__(self, err: Exception):
        """
        Initialize the LSBReadError with the original exception.

        :param err: The original exception that caused the LSB read error.
        :type err: Exception
        """
        Exception.__init__(self, (f'LSB Read Error - {err!r}', err))
        self.error = err


def read_lsb_raw_bytes(image: ImageTyping) -> bytes:
    """
    Read raw bytes of LSB-encoded data from an image.

    This function loads the image and uses ImageLsbDataExtractor to extract the hidden data.

    :param image: The image to extract data from. Can be a file path, URL, or Image object.
    :type image: ImageTyping
    :return: The extracted raw data.
    :rtype: bytes
    :raises LSBReadError: If there's an error during the extraction process.
    """
    image = load_image(image, mode=None, force_background=None)
    try:
        return ImageLsbDataExtractor().extract_data(image)
@@ -184,11 +230,23 @@ def read_lsb_raw_bytes(image: ImageTyping) -> bytes:


def read_lsb_metadata(image: ImageTyping):
    """
    Read and decode LSB-encoded metadata from an image.

    This function extracts the raw bytes, decompresses them using gzip,
    and then decodes the result as a JSON object.

    :param image: The image to extract metadata from. Can be a file path, URL, or Image object.
    :type image: ImageTyping
    :return: The decoded metadata as a Python object.
    :rtype: dict
    :raises LSBReadError: If there's an error during the extraction or decoding process.
    """
    try:
        raw_data = read_lsb_raw_bytes(image)
        return json.loads(gzip.decompress(raw_data).decode("utf-8"))
    except (json.JSONDecodeError, zlib.error, EOFError, UnicodeDecodeError) as err:
        # zlib.error: unable to decompress via zlib method
        # json.JSONDecodeError, EOFError: zot a json-formatted data
        # json.JSONDecodeError, EOFError: not a json-formatted data
        # UnicodeDecodeError: cannot decode as utf-8 text
        raise LSBReadError(err)
+76 −1
Original line number Diff line number Diff line
@@ -33,6 +33,9 @@ def bit_shuffle(data_bytes, w, h):
    """
    Shuffle the bits of input data into a specific pattern based on image dimensions.

    This function reorganizes the input data bits into a 2D pattern that matches the image dimensions.
    It's used to spread the data across the image for more robust embedding.

    :param data_bytes: Input data bytes to be shuffled
    :type data_bytes: bytes
    :param w: Width of the image
@@ -74,6 +77,9 @@ def split_byte_ranges(data_bytes, n, w, h):
    """
    Split the input data bytes into chunks after shuffling.

    This function first shuffles the input data using the bit_shuffle function,
    then splits it into chunks of size n.

    :param data_bytes: Input data bytes
    :type data_bytes: bytes
    :param n: Size of each chunk
@@ -97,6 +103,8 @@ def pad(data_bytes):
    """
    Pad the input data bytes to a fixed length of 2019 bytes.

    This function ensures that all data chunks have a consistent length for error correction encoding.

    :param data_bytes: Input data bytes
    :type data_bytes: bytes
    :return: Padded data bytes
@@ -109,6 +117,9 @@ def fec_encode(data_bytes, w, h):
    """
    Perform Forward Error Correction (FEC) encoding on the input data.

    This function applies BCH error correction encoding to the input data after splitting and padding.
    It enhances the robustness of the embedded data against corruption.

    :param data_bytes: Input data bytes
    :type data_bytes: bytes
    :param w: Width of the image
@@ -127,6 +138,9 @@ def fec_encode(data_bytes, w, h):
class LSBInjector:
    """
    A class for injecting data into the least significant bits of image pixels.

    This class provides methods to prepare data for injection and embed it into an image's
    least significant bits, which is a form of steganography.
    """

    def __init__(self, data):
@@ -169,6 +183,8 @@ class LSBInjector:
    def finalize(self):
        """
        Finalize the injection process by embedding the buffer data into the image's least significant bits.

        This method actually performs the LSB injection, modifying the image data to include the prepared buffer.
        """
        buffer = np.frombuffer(self.buffer, dtype=np.uint8)
        buffer = np.unpackbits(buffer)
@@ -184,6 +200,17 @@ class LSBInjector:


def serialize_pnginfo(metadata: PngInfo) -> bytes:
    """
    Serialize PNG metadata into a compressed byte string.

    This function extracts metadata from a PngInfo object, converts it to JSON,
    and then compresses it using gzip.

    :param metadata: PNG metadata
    :type metadata: PngInfo
    :return: Compressed serialized metadata
    :rtype: bytes
    """
    data = {
        k: v
        for k, v in [
@@ -199,11 +226,34 @@ def serialize_pnginfo(metadata: PngInfo) -> bytes:


def serialize_json(metadata) -> bytes:
    """
    Serialize any JSON-serializable metadata into a compressed byte string.

    This function converts the input metadata to JSON and then compresses it using gzip.

    :param metadata: Metadata to be serialized
    :type metadata: Any
    :return: Compressed serialized metadata
    :rtype: bytes
    """
    data_encoded = json.dumps(metadata)
    return gzip.compress(bytes(data_encoded, "utf-8"))


def inject_data(image: Image.Image, data: Union[bytes, bytearray]) -> Image.Image:
    """
    Inject data into an image using LSB steganography and error correction.

    This function embeds the given data into the least significant bits of the image pixels,
    along with error correction information for robustness.

    :param image: Input image
    :type image: Image.Image
    :param data: Data to be injected
    :type data: Union[bytes, bytearray]
    :return: Image with injected data
    :rtype: Image.Image
    """
    # noinspection PyTypeChecker
    rgb = np.array(image.convert('RGB'))
    image = image.convert('RGBA')
@@ -222,15 +272,40 @@ def inject_data(image: Image.Image, data: Union[bytes, bytearray]) -> Image.Imag


def write_lsb_raw_bytes(image: ImageTyping, data: Union[bytes, bytearray]) -> Image.Image:
    """
    Write raw bytes into an image using LSB steganography.

    This function is a wrapper around inject_data that handles image loading.

    :param image: Input image or path to image
    :type image: ImageTyping
    :param data: Raw data to be written
    :type data: Union[bytes, bytearray]
    :return: Image with injected data
    :rtype: Image.Image
    """
    image = load_image(image, mode=None, force_background=None)
    return inject_data(image, data=data)


def write_lsb_metadata(image: ImageTyping, data: Any) -> Image.Image:
    """
    Write metadata into an image using LSB steganography.

    This function handles different types of metadata, serializing them appropriately
    before injection into the image.

    :param image: Input image or path to image
    :type image: ImageTyping
    :param data: Metadata to be written (can be raw bytes, PngInfo, or JSON-serializable data)
    :type data: Any
    :return: Image with injected metadata
    :rtype: Image.Image
    """
    if isinstance(data, (bytes, bytearray)):
        pass
    elif isinstance(data, PngInfo):
        data = serialize_pnginfo(data)
    else:
        data = serialize_json(data)
    return inject_data(image, data=data)
    return write_lsb_raw_bytes(image, data=data)