Skip to content

LSBJpeg

stegobox.codec.LSBJpeg

Bases: BaseCodec

LSBJpeg: Hide text into JPEG using LSB.

  • Created by: Jiayao Yang
  • Created time: 2022/10/10

Source code derived from: VasilisG/LSB-steganography

Source code in stegobox/codec/jpeg_lsb.py
class LSBJpeg(BaseCodec):
    """
    LSBJpeg: Hide text into JPEG using LSB.

    * Created by: Jiayao Yang
    * Created time: 2022/10/10

    Source code derived from:
    [VasilisG/LSB-steganography](https://github.com/VasilisG/LSB-steganography)
    """

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

    def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
        """Simple LSB steganography using JPEG images.

        Args:
            carrier: Input JPEG image.
            payload: Secret payload message.

        Raises:
            ValueError: If the payload is too large for the carrier.

        Returns:
            The encrypted steganography image. Note that the image must be saved as PNG
            format for decoding to work properly.
        """
        width, height = carrier.size
        image_capacity = width * height * BITS_PER_PIXEL
        message_capacity = (len(payload) * BITS_PER_CHAR) - (BITS_PER_CHAR + 2)
        if not image_capacity >= message_capacity:
            raise ValueError("Sorry, payload should be smaller than host image.")

        size = carrier.size

        # Create binary triple pairs
        binaries = list(
            "".join([bin(ord(i))[2:].rjust(BITS_PER_CHAR, "0") for i in payload])
            + "".join(["0"] * BITS_PER_CHAR)
        )
        binaries = binaries + ["0"] * (len(binaries) % BITS_PER_PIXEL)
        binaries = [
            binaries[i * BITS_PER_PIXEL : i * BITS_PER_PIXEL + BITS_PER_PIXEL]
            for i in range(0, int(len(binaries) / BITS_PER_PIXEL))
        ]
        binary_triple_pairs = binaries

        # Embed bits to pixels
        pixels = list(carrier.getdata())
        binary_pixels = [
            list(bin(p)[2:].rjust(BITS_PER_CHAR, "0") for p in pixel)
            for pixel in pixels
        ]
        for i in range(len(binary_triple_pairs)):
            for j in range(len(binary_triple_pairs[i])):
                binary_pixels[i][j] = list(binary_pixels[i][j])  # type: ignore
                binary_pixels[i][j][-1] = binary_triple_pairs[i][j]  # type: ignore
                binary_pixels[i][j] = "".join(binary_pixels[i][j])

        new_pixels = [tuple(int(p, 2) for p in pixel) for pixel in binary_pixels]

        new_img = Image.new("RGB", size)
        new_img.putdata(new_pixels)  # type: ignore
        return new_img

    def decode(self, carrier: Image.Image) -> str:
        """Decode the secret payload from the carrier image

        Args:
            carrier: Carrier image in format png.

        Returns:
            str: The decoded payload (secret message).
        """
        self._check_png(carrier)  # Must be a PNG image for decoding to work.

        pixels = list(carrier.getdata())
        binary_pixels = [
            list(bin(p)[2:].rjust(BITS_PER_CHAR, "0") for p in pixel)
            for pixel in pixels
        ]

        # Get LSBs from pixels
        bin_list = self._get_lsbs_from_pixels(binary_pixels)
        payload = "".join(
            [
                chr(int("".join(bin_list[i : i + BITS_PER_CHAR]), 2))
                for i in range(0, len(bin_list) - BITS_PER_CHAR, BITS_PER_CHAR)
            ]
        )

        return payload

    def _get_lsbs_from_pixels(self, binary_pixels: list) -> list:
        total_zeros = 0
        bin_list = []
        for binary_pixel in binary_pixels:
            for p in binary_pixel:
                if p[-1] == "0":
                    total_zeros = total_zeros + 1
                else:
                    total_zeros = 0
                bin_list.append(p[-1])
                if total_zeros == BITS_PER_CHAR:
                    return bin_list
        raise ValueError("LSB not found.")

    def _check_png(self, image: Image.Image) -> None:
        if image.format != "PNG":
            raise Exception("PNG images are required for decoding to work normally.")

encode(carrier, payload)

Simple LSB steganography using JPEG images.

Parameters:

Name Type Description Default
carrier Image

Input JPEG image.

required
payload str

Secret payload message.

required

Raises:

Type Description
ValueError

If the payload is too large for the carrier.

Returns:

Type Description
Image

The encrypted steganography image. Note that the image must be saved as PNG

Image

format for decoding to work properly.

Source code in stegobox/codec/jpeg_lsb.py
def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
    """Simple LSB steganography using JPEG images.

    Args:
        carrier: Input JPEG image.
        payload: Secret payload message.

    Raises:
        ValueError: If the payload is too large for the carrier.

    Returns:
        The encrypted steganography image. Note that the image must be saved as PNG
        format for decoding to work properly.
    """
    width, height = carrier.size
    image_capacity = width * height * BITS_PER_PIXEL
    message_capacity = (len(payload) * BITS_PER_CHAR) - (BITS_PER_CHAR + 2)
    if not image_capacity >= message_capacity:
        raise ValueError("Sorry, payload should be smaller than host image.")

    size = carrier.size

    # Create binary triple pairs
    binaries = list(
        "".join([bin(ord(i))[2:].rjust(BITS_PER_CHAR, "0") for i in payload])
        + "".join(["0"] * BITS_PER_CHAR)
    )
    binaries = binaries + ["0"] * (len(binaries) % BITS_PER_PIXEL)
    binaries = [
        binaries[i * BITS_PER_PIXEL : i * BITS_PER_PIXEL + BITS_PER_PIXEL]
        for i in range(0, int(len(binaries) / BITS_PER_PIXEL))
    ]
    binary_triple_pairs = binaries

    # Embed bits to pixels
    pixels = list(carrier.getdata())
    binary_pixels = [
        list(bin(p)[2:].rjust(BITS_PER_CHAR, "0") for p in pixel)
        for pixel in pixels
    ]
    for i in range(len(binary_triple_pairs)):
        for j in range(len(binary_triple_pairs[i])):
            binary_pixels[i][j] = list(binary_pixels[i][j])  # type: ignore
            binary_pixels[i][j][-1] = binary_triple_pairs[i][j]  # type: ignore
            binary_pixels[i][j] = "".join(binary_pixels[i][j])

    new_pixels = [tuple(int(p, 2) for p in pixel) for pixel in binary_pixels]

    new_img = Image.new("RGB", size)
    new_img.putdata(new_pixels)  # type: ignore
    return new_img

decode(carrier)

Decode the secret payload from the carrier image

Parameters:

Name Type Description Default
carrier Image

Carrier image in format png.

required

Returns:

Name Type Description
str str

The decoded payload (secret message).

Source code in stegobox/codec/jpeg_lsb.py
def decode(self, carrier: Image.Image) -> str:
    """Decode the secret payload from the carrier image

    Args:
        carrier: Carrier image in format png.

    Returns:
        str: The decoded payload (secret message).
    """
    self._check_png(carrier)  # Must be a PNG image for decoding to work.

    pixels = list(carrier.getdata())
    binary_pixels = [
        list(bin(p)[2:].rjust(BITS_PER_CHAR, "0") for p in pixel)
        for pixel in pixels
    ]

    # Get LSBs from pixels
    bin_list = self._get_lsbs_from_pixels(binary_pixels)
    payload = "".join(
        [
            chr(int("".join(bin_list[i : i + BITS_PER_CHAR]), 2))
            for i in range(0, len(bin_list) - BITS_PER_CHAR, BITS_PER_CHAR)
        ]
    )

    return payload