JDpaste

Steganography image codec - string algorithm

Oct 23rd, 2021 (edited)
530
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. """A 24-bit bitmap uses 8 bits to represent each of the three color values (red, green, and blue) of each pixel.
  2. The red alone has 256 different levels of red intensity.
  3. The difference between 11111111 and 11111110 in the value for red intensity is likely to be undetectable by the human eye.
  4. Therefore, the least significant bit can be used more or less undetectably for something else other than color information.
  5.  
  6. If that is repeated for the green and the blue elements of each pixel as well,
  7. it is possible to encode one letter of ASCII text for every three pixels
  8. or .. each colour channel can carry a different message."""
  9.  
  10. from PIL import Image   # Import the image module from PIL
  11. #
  12. #------------
  13. def encStegan( srcImg = None, seq = "", colourChannel = 0 ):
  14.     """Morph image file with hidden bits .. eg. ASCII secret message !
  15. RETURN new version of Image ( or an error String )"""
  16.     if ( not srcImg or not seq  ):       return "Function REQUIRES an image and some text."
  17.     if ( colourChannel not in range(3)): return "Colour channel must be in (0,1,2) for R, G or B"
  18.    
  19.     srcImg  = srcImg.convert('RGB') # Convert the image to RGB
  20.     srcSize = srcImg.size
  21.     width, height  = srcSize # (unpack from tuple) the image's width and height
  22.     print('Image SIZE:', width, '*', height, '=', width * height, 'pixels\n' )
  23.     imgSeqLimit = (( width * height ) // 8 ) - 1  # less 1 to allow for \x0
  24.     if ( len( seq ) > imgSeqLimit ): return "NOT enough pixels to encode. Limit is "+ imgSeqLimit
  25.     #-----------------
  26.     newImg = Image.new('RGB', srcSize )  # create a grid for the output image
  27.  
  28.     if ( isinstance( seq, str )) :  # is seq a String ?
  29.        
  30.         intsList = [ ord(c) for c in seq ]  # map String to list of integer ordinals (codepoints)
  31.     else:
  32.         intsList = list( seq )              # else .. assume already a <sequence of integers> eg. tuple/bytes etc.
  33.     #
  34.     #--------------
  35.     intsList.append(0x0)  # add NULL on end
  36.  
  37.     # build binary string ..
  38.     binary_message = ""
  39.     for i in intsList:
  40.         bin_as_string  = '{0:08b}'.format( i )
  41.         binary_message += bin_as_string
  42.     #   --------------
  43.     lenBits   = len( binary_message )
  44.     bitIndex  = 0
  45.     copyPixel = False
  46.    
  47.     # nest loops to access each pixel within image ..
  48.     for row in range( height ):
  49.         for col in range( width ):
  50.             #
  51.             pixXY     = (col, row)
  52.             thisColour = srcImg.getpixel( pixXY ) # (r, g, b)
  53.            
  54.             if ( copyPixel ): # set True when txt is exhausted
  55.                 newImg.putpixel( pixXY, thisColour )
  56.             else:
  57.                 # I am working with pixel (R,G or B byte) sequences
  58.                 # .. using bit0 from each byte to make a hidden bit sequence
  59.  
  60.                 # Replace the low-order bit (bit0) in the 8-bit channelValue
  61.                 # ..to hold a bit value from the int being encoded
  62.                
  63.                 channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  64.                 channelStr   = '{0:b}'.format( channelValue )
  65.                 channelStr   = channelStr[:-1] + binary_message[ bitIndex ] # replace LSB
  66.                
  67.                 channelValue = int( channelStr, 2 ) # parse 'binary string' back to byte (int)
  68.                
  69.                 newColor = list( thisColour )
  70.                 newColor[ colourChannel ] = channelValue  # replace
  71.                 newImg.putpixel( pixXY, tuple( newColor ))
  72.                 #      ========
  73.                
  74.                 bitIndex += 1
  75.  
  76.                 if ( bitIndex >= lenBits ): #
  77.                     copyPixel  = True  # FINISHED encoding text. Just copy pixels from now on !
  78.     #
  79.     return newImg
  80. #----------------
  81.  
  82. #------------
  83. def decStegan( srcImg = None, colourChannel = 0):
  84.     """Scan image file for hidden bytes .. eg. Unicode (ASCII) secret message !"""
  85.    
  86.     srcImg = srcImg.convert('RGB') # Convert the image to RGB
  87.    
  88.     width, height = srcImg.size # Assign the image's width and height to variables
  89.     print('Size:', width, '*', height )
  90.    
  91.     pixelCount = 0 # wrap at 8
  92.    
  93.     output_bits = ""
  94.     #output_text = ""
  95.     byteList  = []
  96.     #
  97.     for row in range( height ):
  98.         for col in range( width ):
  99.             #
  100.             thisColour  = srcImg.getpixel( (col, row) ) # (r, g, b)
  101.             channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  102.             #
  103.             # I am working with 8 pixel (byte) sequences
  104.             # .. taking bit0 from each of the 8 channelValue bytes to make a hidden byte
  105.  
  106.             channelStr = '{0:b}'.format( channelValue )
  107.             output_bits += channelStr[ -1: ]  # last char is LSB binary digit
  108.            
  109.             pixelCount += 1
  110.             if ( pixelCount >= 8 ): # got to 8 ..
  111.                 #
  112.                 newByte = int( output_bits, 2 )
  113.                 #print( hex( newByte )) # DEBUG
  114.                 if ( newByte == 0 ): return byteList  #  0x00 so return RESULT !
  115.                 #                    ====== ========
  116.                 # else NOT 0x00 NULL terminator so ..
  117.                 # ..add byte to sequence, then reset for next 8 pixel sequence
  118.                 byteList.append( newByte )
  119.                
  120.                 # RESET for new byte ..
  121.                 pixelCount  = 0
  122.                 output_bits = ""
  123.                 #
  124.     #
  125.     return []  # "NO NULL terminator"
  126. #------------------
  127.  
  128. #-- ----------------
  129. def showIntSequence( ordSequence = [], assumeUTF8 = True ) :
  130.     print( 'OUTPUT follows ..')
  131.     print( '\nDECIMAL ..')
  132.     print( ordSequence )
  133.    
  134.     print( '\nBINARY .. showing a minimum of 8 bits ..')
  135.     resChars = [ '{0:08b}'.format(n) for n in ordSequence ]
  136.     print( ",".join(resChars))
  137.    
  138.     print( '\nHEX ..')
  139.     resChars = [ hex(n) for n in ordSequence ]
  140.     print( ",".join(resChars))
  141.    
  142.     print( '\nASCII ..')
  143.     resChars = [ ascii(chr(n)) for n in ordSequence ]
  144.     print( ",".join(resChars))
  145.    
  146.     print( '\nUnicode TEXT from codepoints .. show with non-printables excluded: < x20 | == x7F .. IF ANY')
  147.     print(   '------------')
  148.     resChars = []
  149.     for thisOrd in ordSequence :
  150.         if ( thisOrd >= 0x20 and thisOrd != 0x7F ) : resChars.append( chr( thisOrd ))
  151.     #
  152.     print( "".join(resChars))  # make a String of it
  153.  
  154.     if ( assumeUTF8 ):
  155.         print('\nUTF-8 Unicode ..')
  156.         print(  '-------------')
  157.        
  158.         by = bytes( bytesFound )
  159.         print(by.decode())
  160.     #
  161.     print("===============================================================")
  162. #
  163. #--------------
  164. # TEST CALLS ..
  165. startFile = "encoded_image.png"
  166.  
  167. print('\nReading file:', startFile, ' ...')
  168. print(  '************  -----------------')
  169. encImg = Image.open( startFile )
  170. #
  171. print('Scanning RED channel for Steganography text .. DECODING in RED 8-bit using bin digit chars !!')
  172. print('====================')
  173. bytesFound = decStegan( encImg, 0 )
  174. showIntSequence( bytesFound )
  175.  
  176. print('Scanning GREEN channel for Steganography text .. DECODING in GREEN 8-bit using bin digit chars !!')
  177. print('======================')
  178. bytesFound = decStegan( encImg, 1 )
  179. showIntSequence( bytesFound )
  180.  
  181. #-----------------------------------------------------
  182. inSeq = "\u262f\u262e" # '☯☮' #
  183. print('Encoding', inSeq, 'in BLUE  8-bit UTF-8 ... please wait ...')
  184. encImg  = encStegan( encImg, inSeq.encode(), 2 )  # 2 -> BLUE **!
  185.  
  186. print('DECODING in BLUE  8-bit')
  187. bytesFound = decStegan( encImg, 2 )
  188. showIntSequence( bytesFound )
  189.  
RAW Paste Data