Loading imgutils/metadata/lsb/__init__.py +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 imgutils/metadata/lsb/read.py +61 −3 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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 Loading @@ -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 """ Loading @@ -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. Loading @@ -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 """ Loading @@ -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. Loading @@ -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 Loading @@ -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) Loading @@ -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) imgutils/metadata/lsb/write.py +76 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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): Loading Loading @@ -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) Loading @@ -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 [ Loading @@ -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') Loading @@ -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) Loading
imgutils/metadata/lsb/__init__.py +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
imgutils/metadata/lsb/read.py +61 −3 Original line number Diff line number Diff line Loading @@ -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. Loading Loading @@ -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 Loading @@ -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 """ Loading @@ -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. Loading @@ -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 """ Loading @@ -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. Loading @@ -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 Loading @@ -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) Loading @@ -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)
imgutils/metadata/lsb/write.py +76 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading @@ -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 Loading @@ -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 Loading @@ -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): Loading Loading @@ -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) Loading @@ -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 [ Loading @@ -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') Loading @@ -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)