Skip to content

SteganographyImg

stegobox.codec.SteganographyImg

Bases: BaseCodec

SteganographyImg

This module implements simple LSB steganography in images.

  • To encode: Convert the RGB values for each color channel to binary. Again for each channel, take the 4 most significant bits from the cover image and the 4 most significant bits from the hidden image. Shift the 4 bits from the hidden image to the positions of the least significant bits and combine them with the 4 bits from the cover image Combine the RGB channels together to create a new pixel, dominated by the 4 significant bits from the cover image but with the 4 most significant bits from the hidden image encoded within. The color will change slightly due to the information hidden in the least significant bits.
  • To decode: To decode the hidden image, take the 4 least significant bits from the new pixel and shift them back to the positions of the most significant bits. Fill in the vacated least significant bits with 0's (this is information we've forever lost due to the encoding).The hidden image will now appear, slightly different than the original due to the lost data in the least significant bits.

Originally implemented in raffg/steganography

Source code in stegobox/codec/steganography_img.py
class SteganographyImg(BaseCodec):
    """SteganographyImg

    This module implements simple LSB steganography in images.

    * To encode: Convert the RGB values for each color channel to binary. Again for each
      channel, take the 4 most significant bits from the cover image and the 4 most
      significant bits from the hidden image. Shift the 4 bits from the hidden image to
      the positions of the least significant bits and combine them with the 4 bits from
      the cover image Combine the RGB channels together to create a new pixel, dominated
      by the 4 significant bits from the cover image but with the 4 most significant
      bits from the hidden image encoded within. The color will change slightly due to
      the information hidden in the least significant bits.
    * To decode: To decode the hidden image, take the 4 least significant bits from the
      new pixel and shift them back to the positions of the most significant bits. Fill
      in the vacated least significant bits with 0's (this is information we've forever
      lost due to the encoding).The hidden image will now appear, slightly different
      than the original due to the lost data in the least significant bits.

    Originally implemented in
    [raffg/steganography](https://github.com/raffg/steganography)
    """

    def __init__(self) -> None:
        super().__init__()

    def encode(self, carrier: Image.Image, payload: Image.Image) -> Image.Image:
        img1 = np.array(carrier)
        img2 = np.array(payload)
        h1, w1, s1 = img1.shape
        h2, w2, s2 = img2.shape
        # Ensure image 1 is larger than image 2
        if h2 > h1 or w2 > w1:
            raise ValueError("Image 1 size is lower than image 2 size!")
        for i in range(h1):
            for j in range(w1):
                rgb1 = integer_to_binary(img1[i, j])
                # Get the pixel map of the two images
                rgb2 = integer_to_binary((0, 0, 0))
                if i < h2 and j < w2:
                    rgb2 = integer_to_binary(img2[i, j])
                # Merge the two pixels and convert it to a integer tuple
                rgb = merge_rgb(rgb1, rgb2)
                img1[i, j] = binary_to_integer(rgb)
        new_img = Image.fromarray(img1)
        return new_img

    def decode(self, carrier: Image.Image) -> Image.Image:
        img1 = np.array(carrier)
        h1, w1, s1 = img1.shape
        h2, w2 = h1, w1
        for i in range(h1):
            for j in range(w1):
                # Get the RGB (as a string tuple) from the current pixel
                r, g, b = integer_to_binary(img1[i, j])
                # Extract the last 4 bits (corresponding to the hidden image)
                # Concatenate 4 zero bits because we are working with 8 bit values
                rgb = (r[4:] + "0000", g[4:] + "0000", b[4:] + "0000")
                # Convert it to an integer tuple
                img1[i, j] = binary_to_integer(rgb)
                # If this is a 'valid' position, store it
                # as the last valid position
                if img1[i, j, 0] != 0 and img1[i, j, 1] != 0 and img1[i, j, 2] != 0:
                    h2 = i + 1
                    w2 = j + 1
        img2 = np.zeros((h2, w2, 3))
        for i in range(h2):
            for j in range(w2):
                for k in range(3):
                    img2[i, j, k] = img1[i, j, k]
        new_img = Image.fromarray(np.uint8(img2))
        return new_img

    def _check_empty_payload(self, payload: str) -> None:
        if not payload:
            raise Exception("Payload must not be empty.")