Advertisement
JDpaste

Steganography image codec - bit length control

Oct 13th, 2021
989
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 10.62 KB | None | 0 0
  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 blue alone has 256 different levels of blue intensity.
  3. The difference between 11111111 and 11111110 in the value for blue 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 red 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. def getMSB_bitmask( numBits = 8 ):
  13.     """ eg. (numBits == 8) => 0x80 == 0b10000000 """
  14.     if (numBits < 1 ): numBits = 1  # guard
  15.    
  16.     mask = 0x01
  17.     mask <<= ( numBits - 1 )
  18.     return mask
  19. #
  20. #------------
  21. def encStegan( srcImg = None, seq = "", colourChannel = 0, numBits = 8 ):
  22.     """Morph image file with hidden bits .. eg. ASCII secret message !
  23. RETURN new version of Image ( or an error String )"""
  24.     if ( not srcImg or not seq  ):       return "Function REQUIRES an image and some text."
  25.     if ( colourChannel not in range(3)): return "Colour channel must be in (0,1,2) for R, G or B"
  26.    
  27.     srcImg  = srcImg.convert('RGB') # Convert the image to RGB
  28.     srcSize = srcImg.size
  29.     width, height  = srcSize # (unpack from tuple) the image's width and height
  30.    
  31.     imgSeqLimit = (( width * height ) // numBits ) - 1  # less 1 to allow for \x0
  32.     if ( len( seq ) > imgSeqLimit ): return "NOT enough pixels to encode. Limit is "+ imgSeqLimit
  33.     #-----------------
  34.     newImg = Image.new('RGB', srcSize )  # create a grid for the output image
  35.  
  36.     if ( isinstance( seq, str )) :
  37.        
  38.         intsList = [ ord(c) for c in seq ]  # map String to list of integer ordinals (codepoints)
  39.     else:
  40.         intsList = list( seq )              # else .. assume already a <sequence of integers>
  41.     #
  42.     intsList.append(0x00)  # add NULL on end
  43.    
  44.     lenInts  = len( intsList )
  45.     MSB_mask = getMSB_bitmask( numBits ) # 8 bits in a byte =>  0x80 == 0b10000000
  46.     ########
  47.     intsIndex = 0
  48.     copyPixel = False
  49.    
  50.     bitPattern = intsList[0] # get the first int (binary sequence) to encode
  51.     bitMask    = MSB_mask    # alg shifts this single bit >> to test against a bit pattern
  52.    
  53.     # nest loops to modify each pixel within image
  54.     for row in range( height ):
  55.         for col in range( width ):
  56.             #
  57.             pixXY     = (col, row)
  58.             thisColour = srcImg.getpixel( pixXY ) # (r, g, b)
  59.            
  60.             if ( copyPixel ): # set True when txt is exhausted
  61.                 newImg.putpixel( pixXY, thisColour )
  62.             else:
  63.                 channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  64.                 #
  65.                 # I am working with pixel (R,G or B byte) sequences
  66.                 # .. using bit0 from each byte to make a hidden bit sequence
  67.  
  68.                 # tweak the low-order bit (bit0) in the 8-bit channelValue
  69.                 # ..to hold a bit value from the int being encoded
  70.                 if ( bitPattern & bitMask ): # test if bit set to 1 ?
  71.                     channelValue |= 0x01   #  set bit0 to 1 in colour channel
  72.                 else:
  73.                     channelValue &= 0xFE   #  set bit0 to 0 in colour channel .. 0xFE == 0b11111110
  74.                 #
  75.                 newColor = list( thisColour )
  76.                 newColor[ colourChannel ] = channelValue  # replace
  77.                 newImg.putpixel( pixXY, tuple( newColor ))
  78.                 #      ========
  79.                 bitMask >>= 1  # work down through bits in the int (byte) being encoded
  80.                
  81.                 if ( not bitMask ):  #  bitMask now 0x00 -> all bits mapped for bitPattern
  82.                     #
  83.                     intsIndex += 1
  84.                     if ( intsIndex >= lenInts ):
  85.                         copyPixel  = True  # FINISHED encoding text. Just copy pixels from now on !
  86.                     else:
  87.                         bitPattern = intsList[ intsIndex ]  # get next int to encode
  88.                         bitMask = MSB_mask
  89.     #
  90.     return newImg
  91. #----------------
  92.  
  93. #------------
  94. def decStegan( srcImg = None, colourChannel = 0, numBits = 8 ):
  95.     """Scan image file for hidden bytes .. eg. ASCII secret message !"""
  96.     srcImg = srcImg.convert('RGB') # Convert the image to RGB
  97.    
  98.     width, height = srcImg.size # Assign the image's width and height to variables
  99.    
  100.     MSB_mask  = getMSB_bitmask( numBits ) # 8 bits in a byte =>  0x80 == 0b10000000
  101.     ########
  102.     intsList  = []   # results ..
  103.     bitMask   = MSB_mask  # alg shifts this single bit >> to set bit in corresponding position
  104.     buildBits = 0x0  # .. set using binary OR | with bitMask
  105.    
  106.     for row in range( height ):
  107.         for col in range( width ):
  108.             #
  109.             thisColour  = srcImg.getpixel( (col, row) ) # (r, g, b)
  110.             channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  111.             #
  112.             # I am working with 8 pixel (byte) sequences
  113.             # .. taking bit0 from each of the 8 channelValue bytes to make a hidden byte
  114.             #print( channelValue, thisColour, hex(buildBits)) # DEBUG .. use to comprehend ..!
  115.            
  116.             # if bit0 is set on (1) in image data, set bit in bitMask position
  117.            
  118.             if ( channelValue & 0x01 ): buildBits |= bitMask  # .. set bit using binary OR |
  119.             bitMask >>= 1  # work down through bits in the int
  120.            
  121.             if ( not bitMask ): # all bits captured for this value ..
  122.                 #
  123.                 #print( hex( buildBits )) # DEBUG
  124.                 #
  125.                 if ( not buildBits ): return intsList  #  0x0 NULL terminator -> so return RESULT !
  126.                 #                     ====== ========
  127.                 # else NOT 0x0 NULL terminator so ..
  128.                
  129.                 # ..add int to list, then reset for next pixel sequence
  130.                 intsList.append( buildBits )
  131.                
  132.                 # RESET for new bits ..
  133.                 bitMask   = MSB_mask
  134.                 buildBits = 0x0
  135.     #
  136.     return []  # "NO NULL terminator"
  137. #------------------
  138. #------------
  139. def decStegan_8bit( srcImg = None, colourChannel = 0):
  140.     """Scan image file for hidden bytes .. eg. ASCII secret message !"""
  141.     """ NOTE: this function is RETIRED .. superseded by decStegan() """
  142.     """ ... it works fine for 8-bit. It uses << """
  143.    
  144.     srcImg = srcImg.convert('RGB') # Convert the image to RGB
  145.    
  146.     width, height = srcImg.size # Assign the image's width and height to variables
  147.  
  148.     buildByte = 0x00 # .. using | ( binary OR ) and << (left_shift) 1
  149.     byteList  = []
  150.     pixelCount = 0 # wrap at 8
  151.    
  152.     for row in range( height ):
  153.         for col in range( width ):
  154.             #
  155.             thisColour  = srcImg.getpixel( (col, row) ) # (r, g, b)
  156.             channelValue = thisColour[ colourChannel ]  # colourChannel 0,1,2 ==> r, g, b
  157.             #
  158.             # I am working with 8 pixel (byte) sequences
  159.             # .. taking bit0 from each of the 8 channelValue bytes to make a hidden byte
  160.            
  161.             buildByte <<= 1  # left shift puts a 0 at bit0 .. move ALL bits left, ready for next bit
  162.            
  163.             # if bit0 is set on (1) in image data, set (copy to) bit0 in buildByte
  164.             if ( channelValue & 0x01 ): buildByte |= 0x01 # AND (&) tests .. OR (|) sets bit(s) to 1
  165.            
  166.             # print( channelValue, thisColour, hex(buildByte)) # DEBUG .. use to comprehend ..!
  167.             pixelCount += 1
  168.             if ( pixelCount >= 8 ): # got to 8 ..
  169.                 #
  170.                 #print( hex( buildByte )) # DEBUG
  171.                 #
  172.                 if ( not buildByte ): return byteList  #  0x00 so return RESULT !
  173.                 #                     ====== ========
  174.                 # else NOT 0x00 NULL terminator so ..
  175.                 # ..add byte to sequence, then reset for next 8 pixel sequence
  176.                
  177.                 byteList.append( buildByte )
  178.                 # RESET for new byte ..
  179.                 pixelCount = 0
  180.                 buildByte  = 0x00  # 0
  181.             #
  182.         #
  183.     #
  184.     return []  # "NO NULL terminator"
  185. #------------------
  186.  
  187. #-- ----------------
  188. def showByteSequence( ordSequence ) :
  189.     print( 'OUTPUT follows ..')
  190.     print( 'DECIMAL ..')
  191.     print( ordSequence )
  192.    
  193.     print( 'BINARY ..')
  194.     resChars = [ bin(n) for n in ordSequence ]
  195.     print( ",".join(resChars))
  196.    
  197.     print( 'HEX ..')
  198.     resChars = [ hex(n) for n in ordSequence ]
  199.     print( ",".join(resChars))
  200.    
  201.     print( 'ASCII ..')
  202.     resChars = [ ascii(chr(n)) for n in ordSequence ]
  203.     print( ",".join(resChars))
  204.  
  205.     print( 'TEXT .. show only standard ASCII with non-printables excluded .. IF ANY')
  206.     resChars = []
  207.     for thisOrd in ordSequence :
  208.         if ( thisOrd >= 32 and thisOrd < 127 ) : resChars.append( chr( thisOrd ))
  209.     #
  210.     print( "".join(resChars))  # make a String of it
  211.     print("--------------------------------------------------------------------")
  212. #
  213. #--------------
  214. # TEST CALLS ..
  215. print('\nReading file "encoded_image.png" ...')
  216.  
  217. encImg = Image.open('encoded_image.png')
  218. #
  219. print('Scanning RED channel for Steganography text ..')
  220. print('====================')
  221. bytesFound = decStegan( encImg, 0 ) # 0 -> red
  222. showByteSequence( bytesFound )
  223.  
  224. print('Scanning GREEN channel for Steganography text ..')
  225. print('======================')
  226. bytesFound = decStegan( encImg, 1 ) # 1 -> green
  227. showByteSequence( bytesFound )
  228.    
  229. print('Scanning BLUE channel for Steganography text ..')
  230. print('=====================')
  231. bytesFound = decStegan( encImg, 2 ) # 2 -> blue
  232. showByteSequence( bytesFound )
  233. #---------------
  234. inSeq = (100, 101, 102, 103)
  235. print('Encoding', inSeq, 'in RED 7-bit')
  236. encBINimg  = encStegan(  encImg ,  inSeq, 0, 7 )  # 0 -> RED  7-bit **!
  237. bytesFound = decStegan( encBINimg, 0, 7 )
  238. showByteSequence( bytesFound )
  239.  
  240. inSeq = (200, 201, 202, 203)
  241. print('Encoding', inSeq, 'in GREEN 8-bit')
  242. encBINimg  = encStegan( encBINimg, inSeq, 1, 8 )  # 1 -> GREEN 8-bit **!
  243. bytesFound = decStegan( encBINimg, 1, 8 )
  244. showByteSequence( bytesFound )
  245.  
  246. inSeq = [511,510,509,508,507]
  247. print('Encoding', inSeq, 'in BLUE  9-bit')
  248. encBINimg  = encStegan( encBINimg, inSeq, 2, 9 )  # 2 -> BLUE  9-bit **!
  249. bytesFound = decStegan( encBINimg, 2, 9 )
  250. showByteSequence( bytesFound )
  251.  
  252. encBINimg.save("encoded_image_R7_G8_B9.png")
  253. # ** nb. can write/read 16-bit etc. **!
  254.  
  255.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement