Skip to content

BMPLsb

stegobox.codec.BMPLsb

Bases: BaseCodec

Hide str in bitmap type images using LSB steganography.

  • To encode: First we get the representation of the message as a list of bits. Next we get a cover vector of 128 pixels in which we are going to hide the message. Then, we modify the cover vector obtaining the stego vector modifying the LSB of each byte to embed the message bit. Finally, we save the image with the modified data.
  • To decode: The least significant bits of the encoded image are read to recover the secret message.

Originally implemented at daniellerch.me

Source code in stegobox/codec/bmp_lsb.py
class BMPLsb(BaseCodec):
    """Hide str in bitmap type images using LSB steganography.

    * To encode: First we get the representation of the message as a list of bits.
        Next we get a cover vector of 128 pixels in which we are going to hide the
        message. Then, we modify the cover vector obtaining the stego vector modifying
        the LSB of each byte to embed the message bit. Finally, we save the image with
        the modified data.
    * To decode: The least significant bits of the encoded image are read to recover
        the secret message.

    Originally implemented at
    [daniellerch.me](https://daniellerch.me/stego/lab/intro/lsb-en/#lsb-steganography-in-bitmap-images)
    """

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

    def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
        """Encode the string payload into the carrier image using LSB steganography.

        Args:
            carrier: Carrier image in format PNG. Read with `stegobox.io.image.read()`.
            payload: Payload (secret message) to be encoded.

        Returns:
            Encoded image in format PNG with the payload embeded.
        """
        self._check_empty_payload(payload)

        c = numpy.array(carrier)
        c.flags.writeable = True

        message_bits = []
        for m in payload:
            message_bits += [(ord(m) >> i) & 1 for i in range(8)]

        cover = c[:128, 0, 0]
        stego = cover.copy()

        for i in range(len(message_bits)):
            if cover[i] % 2 != message_bits[i]:
                if cover[i] == 255:
                    s = -1
                elif cover[i] == 0:
                    s = +1
                else:
                    s = random.choice([-1, +1])
                stego[i] = cover[i] + s

        c[:128, 0, 0] = stego
        secret_image = Image.fromarray(numpy.uint8(c))
        return secret_image

    def decode(self, carrier: Image.Image, payload_l: int = 10) -> str:
        """Decode the secret message from the carrier image.

        Args:
            carrier: The container image in format PNG with the secret message.
            payload_l: The length of the secret message to extracted, defaults to 10.

        Returns:
            str: The reveal_payload extracted from the container image.
        """
        carrier_np = numpy.array(carrier)

        stego = carrier_np[:128, 0, 0]

        message_bits = [s % 2 for s in stego]

        message_ex = []
        value = 0
        for i in range(len(message_bits)):
            if i % 8 == 0 and i != 0:
                message_ex.append(value)
                value = 0
            value |= message_bits[i] << i % 8

        depayload = "".join([chr(m) for m in message_ex])[:payload_l]
        return depayload

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

encode(carrier, payload)

Encode the string payload into the carrier image using LSB steganography.

Parameters:

Name Type Description Default
carrier Image

Carrier image in format PNG. Read with stegobox.io.image.read().

required
payload str

Payload (secret message) to be encoded.

required

Returns:

Type Description
Image

Encoded image in format PNG with the payload embeded.

Source code in stegobox/codec/bmp_lsb.py
def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
    """Encode the string payload into the carrier image using LSB steganography.

    Args:
        carrier: Carrier image in format PNG. Read with `stegobox.io.image.read()`.
        payload: Payload (secret message) to be encoded.

    Returns:
        Encoded image in format PNG with the payload embeded.
    """
    self._check_empty_payload(payload)

    c = numpy.array(carrier)
    c.flags.writeable = True

    message_bits = []
    for m in payload:
        message_bits += [(ord(m) >> i) & 1 for i in range(8)]

    cover = c[:128, 0, 0]
    stego = cover.copy()

    for i in range(len(message_bits)):
        if cover[i] % 2 != message_bits[i]:
            if cover[i] == 255:
                s = -1
            elif cover[i] == 0:
                s = +1
            else:
                s = random.choice([-1, +1])
            stego[i] = cover[i] + s

    c[:128, 0, 0] = stego
    secret_image = Image.fromarray(numpy.uint8(c))
    return secret_image

decode(carrier, payload_l=10)

Decode the secret message from the carrier image.

Parameters:

Name Type Description Default
carrier Image

The container image in format PNG with the secret message.

required
payload_l int

The length of the secret message to extracted, defaults to 10.

10

Returns:

Name Type Description
str str

The reveal_payload extracted from the container image.

Source code in stegobox/codec/bmp_lsb.py
def decode(self, carrier: Image.Image, payload_l: int = 10) -> str:
    """Decode the secret message from the carrier image.

    Args:
        carrier: The container image in format PNG with the secret message.
        payload_l: The length of the secret message to extracted, defaults to 10.

    Returns:
        str: The reveal_payload extracted from the container image.
    """
    carrier_np = numpy.array(carrier)

    stego = carrier_np[:128, 0, 0]

    message_bits = [s % 2 for s in stego]

    message_ex = []
    value = 0
    for i in range(len(message_bits)):
        if i % 8 == 0 and i != 0:
            message_ex.append(value)
            value = 0
        value |= message_bits[i] << i % 8

    depayload = "".join([chr(m) for m in message_ex])[:payload_l]
    return depayload