Building a steganography algorithm with LSB replacement and XOR substitution

66 views Asked by At

I am working on an implementation of the algorithms in this paper. I have pretty much finished the entire thing but there are a few problems with it. First off, It works fine for embedding data into the image but struggles to take the data out. The data inside the image is just text. It takes out text, but the text is not a coherent word.

To start with pseudocode, I will include the pseudocode for the embedding:

1: Read Cover Image, CI
2: Determine: Height → h and width → w
3: Message Characters → msgB[N] where N=(1,2,...,n)
4: for i → h do
5: for j → w do
6: B(msgB[N] and p = 0
7: phw ← P (h, w)
8: Y 1 ← phw R7 ⊕ B[p++]
9: P (h, w) ← f(Y1,R) checkSUM p!=8 && ==N
10: Y 2 ← phw G7 ⊕ B[p++]
11: P (h, w) ← f(Y2,G) checkSUM p!=8 && ==N
12: Y 3 ← phw B7 ⊕ B[p++]
13: P (h, w) ← f(Y3,B) checkSUM p!=8 && ==N
14: end for
15: Rewrite CI → SCI
16: end for
17: Finishes and Return SC

And then the pseudocode for extracting the data:

1: Read Stego Object, SO
2: Determine: Height → h and width → w
3: Message Characters → msgB[N] where N=(1,2,...,n)
4: for i → h do
5: for j → w do
6: Get Lm & Create Binary[8]
7: if Lm then
8: phw ← P (h, w)
9: if phw R7 ⊕ 1 == phw R8 then
10: Y 1 ← 1
11: else
12: Y 1 ← 0
13: end if
14: Binary ← Y1 checkSUM p!=8
15: if phw G7 ⊕ 1 == phw G8 then
16: Y 2 ← 1
17: else
18: Y 2 ← 0
19: end if
20: Binary ← Y2 checkSUM p!=8
21: if phw B7 ⊕ 1 == phw B8 then
22: Y 3 ← 1
23: else
24: Y 3 ← 0
25: end if
26: Binary ← Y3 checkSUM p!=8
27: end if
28: end for
29: Insert → msgB[N]
30: end for
31: F inishes and Return msgB

I implemented the data embedding like this:

def embed_text(self, image_path, text, output_path):
    # Read Cover Image
    image = Image.open(image_path)

    # Determine Height (h) and Width (w)
    h, w = image.size

    # Message Characters
    binary_message = ''.join(format(ord(char), '08b') for char in text)

    # Embedding loop
    message_index = 0
    for i in range(h):
        for j in range(w):
            # B(msgB[N] and p = 0)
            B = int(binary_message[message_index]) if message_index < len(binary_message) else 0
            message_index += 1

            # phw ← P(h, w)
            phw = image.getpixel((j, i))

            # Y1 ← phw R7 ⊕ B[p++]
            Y1 = (phw[0] & 0xFE) | ((B >> 0) & 1)
            image.putpixel((j, i), (Y1, phw[1], phw[2]))

            # Y2 ← phw G7 ⊕ B[p++]
            B = int(binary_message[message_index]) if message_index < len(binary_message) else 0
            message_index += 1
            Y2 = (phw[1] & 0xFE) | ((B >> 0) & 1)
            image.putpixel((j, i), (phw[0], Y2, phw[2]))

            # Y3 ← phw B7 ⊕ B[p++]
            B = int(binary_message[message_index]) if message_index < len(binary_message) else 0
            message_index += 1
            Y3 = (phw[2] & 0xFE) | ((B >> 0) & 1)
            image.putpixel((j, i), (phw[0], phw[1], Y3))

            # P(h, w) ← f(Y1, R) checkSUM p!=8 && ==N
            # (Assuming f(Y1, R) is XOR operation with 7th bit of R component)
            # ... Repeat similar steps for G and B components ...

    # Rewrite CI → SCI
    image.save(output_path)

And here is the cod I used for extraction:

def extract_text(self, image_path):
    stego_image = Image.open(image_path)
    height, width = stego_image.size
    msgB = []  # Initialize an empty list to store the message characters
    binary_message = ""     
    for i in range(height):
        for j in range(width):
            # Get Lm & Create Binary[8]
            phw = stego_image.getpixel((j, i))
            Lm = self.get_Lm(phw)
            binary = "{:08b}".format(Lm)

            print(f"Lm: {Lm}, Binary: {binary}")

        # Process the pixel only if Lm is 1
            if Lm:
                # Process the RED component
                if phw[0] % 2 == int(binary[0]):
                    Y1 = 1
                else:
                    Y1 = 0

                # Process the GREEN component
                if phw[1] % 2 == int(binary[1]):
                    Y2 = 1
                else:
                    Y2 = 0

                # Process the BLUE component
                if phw[2] % 2 == int(binary[2]):
                    Y3 = 1
                else:
                    Y3 = 0

            # Append the processed pixel to msgB
            binary_message += f"{Y1}{Y2}{Y3}"
    msgB = self.binary_to_string(binary_message)
    print("msgB:", msgB)
    return msgB
def binary_to_string(self, binary_message):
    # Split the binary message into 8-bit substrings
    eight_bit_chunks = [binary_message[i:i + 8] for i in range(0, len(binary_message), 8)]

    # Convert each 8-bit substring to an integer and then to a character
    characters = [chr(int(chunk, 2)) for chunk in eight_bit_chunks]

    # Join the characters to form the final string
    return ''.join(characters)

The embedding procedure is very inconsistent. Sometimes it embeds (mostly for png images) but it fails with an out of range error when I add a jpeg image. As for the extraction method, I think it's right, but it fails to take out the right text. I get something funny like:

HådlÿÿÿI$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I$I...

I am unsure how to proceed because the steps in my implementation seem to be sound, but I am confused as to where this process went wrong. I'd really appreciate some guidance on how to go forward with this because I have to get this done and present it on 5th December, so my time is ticking away.

1

There are 1 answers

0
Odyhibit On

In your embed_text function, it looks like you

  1. get the pixel data.
  2. change the red LSB, and save the pixel.
  3. change the green LSB, and save the pixel.
  4. change the blue LSB, and save the pixel.

This will only keep the last change. You should write the pixel once after changing all three values.

image.putpixel((j, i), (Y1, Y2, Y3))

As far as trying to use LSB on JPEG images, that's not going to work. Jpeg is a lossy format, and you can't rely on the LSB to remain the same. Look into saving data in the DCT if you really want to make that happen.