Ask coding questions

← Back to all posts
My steganography program doesn't work. Please help!
h
DillonB07 (6)

Hi,
I've been working throught the replit tutorials and have come up to the Steganography one.
However, my code is not working.
Please can someone tell me what is going on?

Traceback (most recent call last):
  File "main.py", line 91, in <module>
    main()
  File "main.py", line 82, in main
    write_pixels_to_image(epixels, in_image + "-enc.png")
  File "main.py", line 51, in write_pixels_to_image
    png.from_array(pixels, 'RGB').save(fname)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/png.py", line 1283, in save
    w.write(fd, self.rows)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/png.py", line 670, in write
    nrows = self.write_passes(outfile, check_rows(rows))
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/png.py", line 704, in write_passes
    return self.write_packed(outfile, rows)
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/png.py", line 736, in write_packed
    for i, row in enumerate(rows):
  File "/opt/virtualenvs/python3/lib/python3.8/site-packages/png.py", line 660, in check_rows
    raise ProtocolError(
png.ProtocolError: ProtocolError: Expected 2559 values but got 2560 value, in row 0

Code:

import base64
import png

PROMPT = """
Welcome to basic steganography. Please choose:

1. To encode a message into an image
2. To decode an image into a message
q. To exit
"""

ENDOFMESSAGE = "0100100101010101010101100100111101010010010001010011100101000111010101000101010101010110010101000101010100110000010001100100100001010010010100110100010100111101"

def encode_message_as_bytestring(message):
    b64 = message.encode("utf8")
    bytes_ = base64.encodebytes(b64)
    bytestring = "".join(["{:08b}".format(x) for x in bytes_])
    bytestring += ENDOFMESSAGE
    return bytestring

def get_pixels_from_image(fname):
    img = png.Reader(fname).read()
    pixels = img[2]
    return pixels

def encode_pixels_with_message(pixels, bytestring):
    '''modifies pixels to encode the contents from bytestring'''

    enc_pixels = []
    string_i = 0
    for row in pixels:
        enc_row = []
        for i, char in enumerate(row):
            if string_i >= len(bytestring):
                pixel = row[i]
            else:
                if row[i] % 2 != int(bytestring[string_i]):
                    if row[i] == 0:
                        pixel = 1
                    else:
                        pixel = row[i] - 1
                else:
                    pixel = row[i]
            enc_row.append(pixel)
            string_i += 1

        enc_pixels.append(enc_row)
    return enc_pixels

def write_pixels_to_image(pixels, fname):
    png.from_array(pixels, 'RGB').save(fname)

def decode_message_from_bytestring(bytestring):
    bytestring = bytestring.split(ENDOFMESSAGE)[0]
    message = int(bytestring, 2).to_bytes(len(bytestring) // 8, byteorder='big')
    message = base64.decodebytes(message).decode("utf8")
    return message

def decode_pixels(pixels):
    bytestring = []
    for row in pixels:
        for c in row:
            bytestring.append(str(c % 2))
    bytestring = ''.join(bytestring)
    message = decode_message_from_bytestring(bytestring)
    return message

def main():
    print(PROMPT)
    user_inp = ""
    while user_inp not in ("1", "2", "q"):
        user_inp = input("Your choice: ")

    if user_inp == "1":
        in_image = input("Please enter filename of existing PNG image: ")
        in_message = input("Please enter the message to encode: ")

        print("-ENCODING-")
        pixels = get_pixels_from_image(in_image)
        bytestring = encode_message_as_bytestring(in_message)
        epixels = encode_pixels_with_message(pixels, bytestring)
        write_pixels_to_image(epixels, in_image + "-enc.png")

    elif user_inp == "2":
        in_image = input("Please enter the filename of an existing PNG image: ")
        print("-DECODING-")
        pixels = get_pixels_from_image(in_image)
        print(decode_pixels(pixels))

if __name__ == "__main__":
    main()