Advertisement
JDpaste

Steganography image codec

Oct 11th, 2021
1,128
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.08 KB | None | 0 0
  1. from PIL import Image   # Import the image module from PIL  
  2.  
  3. #------------
  4. def encStegan( srcImg = None, txt = "", colourChannel = 0 ):
  5.     """Morph image file with hidden bytes .. eg. ASCII secret message !
  6. RETURN new version of Image ( or an error String )"""
  7.    
  8.     if ( not srcImg or not txt  ):       return "Function REQUIRES an image and some text."
  9.     if ( colourChannel not in range(3)): return "Colour channel must be in (0,1,2) for R, G or B"
  10.    
  11.     srcImg  = srcImg.convert('RGB') # Convert the image to RGB
  12.     srcSize = srcImg.size
  13.     width, height  = srcSize # (unpack from tuple) the image's width and height
  14.    
  15.     imgTxtLimit = (( width * height ) // 8 ) - 1  # less 1 to allow for \x00
  16.     if ( len( txt ) > imgTxtLimit ): return "NOT enough pixels to encode text. Text limit is "+ imgTxtLimit +" chars"
  17.     #-----------------
  18.     newImg = Image.new('RGB', srcSize )  # create a grid for the output image
  19.     bytesList  = [ ord(c) for c in txt ] # map text to bytes (ints)
  20.     bytesList.append(0x00)    # add NULL on end
  21.    
  22.     lenBytes = len( bytesList )
  23.     bytesIndex = 0
  24.     copyPixel  = False
  25.    
  26.     bitMask  = 0x80         # 0x80 == 0b10000000
  27.     thisByte = bytesList[0] # get 1st byte to encode
  28.    
  29.     #Set up loops to modify each pixel within the image
  30.    
  31.     for row in range( height ):
  32.         for col in range( width ):
  33.             #
  34.             pixXY     = (col, row)
  35.             thisColour = srcImg.getpixel( pixXY ) # (r, g, b)
  36.            
  37.             if ( copyPixel ): # set True when txt is exhausted
  38.                 newImg.putpixel( pixXY, thisColour )
  39.             else:
  40.                 channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  41.                 #
  42.                 # I am working with 8 pixel (byte) sequences
  43.                 # .. using bit0 from each byte to make a hidden byte
  44.  
  45.                 # tweak the low-order bit (bit0) in the channelValue to hold a bit value from the byte being encoded
  46.                 if ( thisByte & bitMask ): # bit set to 1 ?
  47.                     channelValue |= 0x01   #  set bit0 to 1 in colour channel
  48.                 else:
  49.                     channelValue &= 0xFE   #  set bit0 to 0 in colour channel .. 0xFE == 0b11111110
  50.                 #
  51.                 newColor = list( thisColour )
  52.                 newColor[ colourChannel ] = channelValue  # replace
  53.                 newImg.putpixel( pixXY, tuple( newColor ))
  54.  
  55.                 bitMask >>= 1  # work down through bits in the byte being encoded
  56.                
  57.                 if ( not bitMask ):  #  bitMask now 0x00 -> all bits mapped for thisByte
  58.                     #
  59.                     bytesIndex += 1
  60.                     if ( bytesIndex >= lenBytes ):
  61.                         copyPixel  = True  # FINISHED encoding text. Just copy pixels from now on !
  62.                     else:
  63.                         thisByte = bytesList[ bytesIndex ]  # get next byte to encode
  64.                         bitMask  = 0x80
  65.             #
  66.         #
  67.     #
  68.     return newImg
  69. #----------------
  70.  
  71. #------------
  72. def decStegan( srcImg = None, colourChannel = 0):
  73.     """Scan image file for hidden bytes .. eg. ASCII secret message !"""
  74.     srcImg = srcImg.convert('RGB') # Convert the image to RGB
  75.    
  76.     width, height = srcImg.size # Assign the image's width and height to variables
  77.  
  78.     buildByte = 0x00 # .. using | ( binary OR ) and << (left_shift) 1
  79.     byteList  = []
  80.     pixelCount = 0 # wrap at 8
  81.    
  82.     for row in range( height ):
  83.         for col in range( width ):
  84.             #
  85.             thisColour  = srcImg.getpixel( (col, row) ) # (r, g, b)
  86.             channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  87.             #
  88.             # I am working with 8 pixel (byte) sequences
  89.             # .. taking bit0 from each of the 8 channelValue bytes to make a hidden byte
  90.            
  91.             buildByte <<= 1  # left shift puts a 0 at bit0 .. move ALL bits left, ready for next bit
  92.            
  93.             # if bit0 is set on (1) in image data, set (copy to) bit0 in buildByte
  94.             if ( channelValue & 0x01 ): buildByte |= 0x01 # AND (&) tests .. OR (|) sets bit(s) to 1
  95.            
  96.             # print( channelValue, thisColour, hex(buildByte)) # DEBUG .. use to comprehend ..!
  97.             pixelCount += 1
  98.             if ( pixelCount >= 8 ): # got to 8 ..
  99.                 #
  100.                 #print( hex( buildByte )) # DEBUG
  101.                 #
  102.                 if ( not buildByte ): return byteList  #  0x00 so return RESULT !
  103.                 #                     ====== ========
  104.                 # else NOT 0x00 NULL terminator so ..
  105.                 # ..add byte to sequence, then reset for next 8 pixel sequence
  106.                
  107.                 byteList.append( buildByte )
  108.                 # RESET for new byte ..
  109.                 pixelCount = 0
  110.                 buildByte  = 0x00  # 0
  111.             #
  112.         #
  113.     #
  114.     return []  # "NO NULL terminator"
  115. #------------------
  116.  
  117. #-- ----------------
  118. def showByteSequence( ordSequence ) :
  119.     print( 'OUTPUT follows ..')
  120.     print( 'DECIMAL ..')
  121.     print( ordSequence )
  122.    
  123.     print( 'BINARY ..')
  124.     resChars = [ bin(n) for n in ordSequence ]
  125.     print( ",".join(resChars))
  126.    
  127.     print( 'HEX ..')
  128.     resChars = [ hex(n) for n in ordSequence ]
  129.     print( ",".join(resChars))
  130.    
  131.     print( 'ASCII ..')
  132.     resChars = [ ascii(chr(n)) for n in ordSequence ]
  133.     print( ",".join(resChars))
  134.  
  135.     print( 'TEXT .. show only standard ASCII with non-printables excluded .. IF ANY')
  136.     resChars = []
  137.     for thisOrd in ordSequence :
  138.         if ( thisOrd >= 32 and thisOrd < 127 ) : resChars.append( chr( thisOrd ))
  139.     #
  140.     print( "".join(resChars))  # make a String of it
  141.     print("--------------------------------------------------------------------")
  142. #--------------
  143. # TEST CALLS ..
  144. print('\nReading file "encoded_image.png" ...')
  145. encImg = Image.open('encoded_image.png')
  146. #
  147. print('Scanning RED channel for Steganography text ..')
  148. print('====================')
  149. bytesFound = decStegan( encImg, 0 ) # 0 -> red
  150. showByteSequence( bytesFound )
  151.  
  152. print('Scanning GREEN channel for Steganography text ..')
  153. print('======================')
  154. bytesFound = decStegan( encImg, 1 ) # 1 -> green
  155. showByteSequence( bytesFound )
  156.    
  157. print('Scanning BLUE channel for Steganography text ..')
  158. print('=====================')
  159. bytesFound = decStegan( encImg, 2 ) # 2 -> blue
  160. showByteSequence( bytesFound )
  161.  
  162. #---------------
  163. # OK, now encode a message in the BLUE channel !
  164. print('ENCODING file "encoded_image2.png" in the BLUE channel ...')
  165.  
  166. theMessage = "FutureLearn rocks !! I love Python & my Raspberry Pi 4"
  167. encBLUEimg = encStegan( encImg, theMessage, 2 )  # 2 -> blue
  168.  
  169. encBLUEimg.save("encoded_image2.png")
  170. print('--------------------------------------------------------------------')
  171. print('NOW scan new image file "encoded_image2.png" in the BLUE channel ...')
  172. print('Scanning BLUE channel for Steganography text ..')
  173. print('=====================')
  174. bytesFound = decStegan( encBLUEimg, 2 ) # 2 -> blue
  175. showByteSequence( bytesFound )
  176.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement