Skip to content

SteganographyTxt

stegobox.codec.SteganographyTxt

Bases: BaseCodec

SteganographyTxt

This module implements simple LSB steganography in images.

  • To encode: The input message is converted into binary, and the message is embedded into the least significant bits (LSB) of the input image rgb. Once the message is encoded, a trailing delimiter is written. The resulting image is saved as a PNG file.
  • To decode: The least significant bits of the input image are read off until the decode bit number more than datalenth. After which the secret message would be recovered.

Originally implemented in raffg/steganography

Source code in stegobox/codec/steganography_txt.py
class SteganographyTxt(BaseCodec):
    """SteganographyTxt

    This module implements simple LSB steganography in images.

    * To encode: The input message is converted into binary, and the message is embedded
      into the least significant bits (LSB) of the input image rgb. Once the message is
      encoded, a trailing delimiter is written. The resulting image is saved as a PNG
      file.
    * To decode: The least significant bits of the input image are read off until the
      decode bit number more than datalenth. After which the secret message would be
      recovered.

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

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

    def encode(self, carrier: Image.Image, payload: str) -> Image.Image:
        self._check_empty_payload(payload)
        # Convert image to ndarrray
        img = np.array(carrier)
        # Convert payload to binary (removes leading '0b')
        byte_s = payload.encode("utf-8")
        hex_bin = byte_s.hex()
        oct_bin = oct(int(hex_bin, 16))
        binary_bin = bin(int(oct_bin, 8))[2:].zfill(len(hex_bin) * 4)
        # Convert the length of data to binary (removes leading '0b')
        lenth_bin = bin(len(binary_bin))[2:].zfill(12)
        binary_bin = lenth_bin + binary_bin
        data_lenth = len(binary_bin)
        h, w, s = img.shape
        # Embed the message in the image
        num = 0
        for i in range(h):
            for j in range(w):
                for k in range(s):
                    if img[i][j][k] % 2 == 1:
                        if binary_bin[num] == "0":
                            img[i][j][k] = img[i][j][k] - 1
                    else:
                        if binary_bin[num] == "1":
                            img[i][j][k] = img[i][j][k] + 1
                    num = num + 1
                    if num >= data_lenth:
                        break
                if num >= data_lenth:
                    break
            if num >= data_lenth:
                break
        new_img = Image.fromarray(img)
        return new_img

    def decode(self, carrier: Image.Image) -> str:
        extract = ""
        img = np.array(carrier)
        h, w, s = img.shape
        img = np.array(carrier)
        num = 0
        # initial the data_lenth
        data_lenth = 13
        for i in range(h):
            for j in range(w):
                for k in range(s):
                    if img[i][j][k] % 2 == 1:
                        extract += "1"
                    else:
                        extract += "0"
                    num = num + 1
                    if num == 12:
                        data_lenth = int(extract[:12], 2) + 12
                    if num >= data_lenth:
                        break
                if num >= data_lenth:
                    break
            if num >= data_lenth:
                break
        # Convert binary to payload
        return bytes.fromhex(hex(int(extract[12:], 2))[2:]).decode("utf-8")

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