1

I'm trying to hide messages within images by changing the least significant bit (LSB) then read those same bits to retrieve the hidden message.

When the image is not saved and the program just iterates through the array of RGB values, it works as expected but when I use:

res_img = Image.fromarray(im)
res_img.save("freshly_hidden_message.jpg")

The output array is mutated and it's fully unable to read the hidden message.

This is the part that's supposed to read the message:

while msg[-5:] != "^END^":
    bits = [bin(i)[-1] for i in im[index:index + 8]]
    print(bits)
    bits = "".join(bits)
    msg += chr(int(bits, 2))
    index += 8

print("Hidden message is:",msg[:-5])

While this part hides the message within LSB's:

for index, bit in enumerate(hidden_message_bits):
    val = im[index]
    val = bin(val)
    val = val[:-1] + bit
    im[index] = int(val, 2)

Sample output without saving:

['0', '1', '0', '1', '0', '1', '0', '0']
['0', '1', '1', '0', '0', '1', '0', '1']
['0', '1', '1', '1', '0', '0', '1', '1']
['0', '1', '1', '1', '0', '1', '0', '0']
['0', '1', '1', '0', '1', '0', '0', '1']
['0', '1', '1', '0', '1', '1', '1', '0']
['0', '1', '1', '0', '0', '1', '1', '1']
['0', '1', '0', '1', '1', '1', '1', '0']
['0', '1', '0', '0', '0', '1', '0', '1']
['0', '1', '0', '0', '1', '1', '1', '0']
['0', '1', '0', '0', '0', '1', '0', '0']
['0', '1', '0', '1', '1', '1', '1', '0']
Hidden message is: Testing

Sample output with saving:

['1', '0', '0', '1', '0', '0', '1', '0']
['0', '0', '1', '1', '0', '1', '1', '1']
['0', '0', '1', '0', '0', '1', '0', '0']
['1', '0', '0', '1', '0', '0', '1', '0']
['0', '1', '0', '0', '1', '0', '0', '1']
['0', '0', '1', '0', '0', '1', '0', '0']
['0', '0', '1', '0', '0', '1', '0', '0']
['1', '0', '0', '1', '0', '0', '1', '0']
['0', '1', '0', '0', '1', '0', '0', '1']
['0', '0', '1', '0', '0', '1', '0', '0']
['1', '0', '0', '1', '0', '0', '1', '0']
['0', '1', '0', '0', '1', '0', '0', '1']
Hidden message is: 7$I$$I$I$I$I$I$I$I$IÛm¶Ûm¶$IÛm¶Ûm¶$Iÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ$I$I$I$I$I$Iÿÿÿÿÿÿ
GrozniV
  • 13
  • 3
  • could you post both images as pictures, both unmutated and mutated ? – pippo1980 Aug 31 '22 at 14:11
  • res_img = Image.fromarray(im) ---> defaults to res_img = Image.fromarray(im, mode=None) and determine the mode from type This will not be used to convert the data after reading, but will be used to change how the data is read: – pippo1980 Aug 31 '22 at 14:22
  • 1
    There's no observable difference between the two images. They look exactly the same. I'll try playing around with different modes. – GrozniV Aug 31 '22 at 14:30
  • guess I'll have to read about the least significant bit (LSB) meaning – pippo1980 Aug 31 '22 at 14:32
  • It's fairly simple. You load the image and put its values in an array and change the values to binary. Then convert your message to binary and iterate through the binary RGB values while injecting your message (one bit at a time) into LSB's of the image. LSB is the farthest to the right digit, the one that holds the least value. LSB's are used to hide the message because the change in color is invisible to the human eye and can only be read by a decoder. – GrozniV Aug 31 '22 at 14:42
  • unless mode=1 : 1 (1-bit pixels, black and white, stored with one pixel per byte). Am I getting it right ? – pippo1980 Aug 31 '22 at 14:46
  • 1
    Yep, if the pixels are just 1 bit per pixel then changing them (0->1, 1->0) would completely change the image and the change would very noticeable, making the new image unrecognizable to the original. While in the above mentioned case (RGB 8 bits per pixel) we can change the LSB (00000000->00000001, 11011001 -> 11011000, etc.) without having any visible effect to the image. – GrozniV Aug 31 '22 at 15:14
  • The issue is that JPEG is **lossy** - use PNG. See https://stackoverflow.com/a/73528009/2836621 – Mark Setchell Aug 31 '22 at 21:47
  • Yep, that was the issue but using PNG's introduced another one that I had to resolve. – GrozniV Sep 02 '22 at 07:25

1 Answers1

0

The reason this is happening is because the jpg format compresses the data being saved. The original data cannot be recovered 100% because of the compression algorithm.

A work around to this problem is to save the image in an uncompressed format. Of course this could result in a really large file size. Another option would be to save the file in a format that uses run-length encoding which would help reduce the file size. Suggested formats supported by PIL include tiff, tga and sgi.

John Moore
  • 162
  • 1
  • 6
  • Yeah, using JPG's caused the issue but PNG's caused another error. Due to how the code was set up, loading the image completely distorted the colors. These two lines of code fixed the issue and now the program is working fully as intended. `im = Image.open("guyy.png").convert("RGBA") im = np.array(im).astype("uint8")` – GrozniV Sep 02 '22 at 07:27