Hiding Data in Plain Sight: Least Significant Bit Image Steganography in Python

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hello everyone today I'd like to talk about a really fun application in image processing called steganography so steganography not stenography uh steganography is the art of hiding information in plain sight so this actually derives from Greek words stonos means to hide or conceal and graphy means writing I guess the first known application of steganography was when um they shaved or or they they shave somebody's head tattooed a message on their head let the hair grow back and then sent them over somewhere and then they they shave the head this was in ancient Greece so you shave the head again you see the message so nobody knows it's there unless they know to shave the head so we're going to do something with images where unless you really know to look for this then you're not going to know it's there but um actually if you know to look for it it'll be pretty easy to to find it uh although there there's certainly ways to make it harder to find I'm just going to show a basic technique today called uh least significant bit image tonography but first let's review a little bit about image representations so actually I'm going to load in here a picture of my wonderful dog Theo and my cat Lea and so we see this this actually if I print the shape of this this is a actually a threedimensional array um 1,600 rows 12200 columns and then three three color channels and actually if I print out this and I get a little preview here you'll see that the actual data um appears to be integers and so why do we have three threedimensional array and and the data is integers well I have a little widget here to help show what's going on so each pixel is actually represented by three numbers between 0 and 255 and so if you know binary you know that um if these are integers between 0 and 25 they can be represented by 8 Bits so this is a standard encoding of images um each pixel gets eight bits for red green and blue so humans who can see color perceive color in three dimensions red green blue or RGB is a pretty standard way of representing those Dimensions although there are others but this is kind of the default way of doing it and so just to show you you know if I'm pretty red here then my red channel the first one is closer to 255 than the other ones U let's say I switch over simply more blue you can see now the blue Channel RGB B blue that is actually closer to 255 um if I mix everything together fully 255 255 255 I get pure white and if I go down and and destroy everything I get 00 Z which is pure black all right so that's how the data is represented every single pixel has three numbers now what I want to do is is take this original image and I am going to round every one of these numbers down to the nearest even number okay so some of these are even some of them are not if I if I look at this I see I got some even numbers 94 but some odd numbers 89 let me just go ahead and and round down all the odd ones so 89 would become an 88 for example 179 would become a 178 so I'll say I is equal to and and so one way to do this would be to say I is equal to um or maybe I'll say I round is equal to IUS I mod 2 okay so remember the mod 2 is taking the remainder when divided by two which is exactly one if it's odd or zero if it's even and so what we're going to do is if it's odd we're going to subtract off the one bring it down to the to the nearest even and if it's even we're going to subtract zero which which does nothing now if I plot these images side by side so the original image and what I get after doing that rounding down to the nearest St I would challenge you to tell me which one is which right so I know that that I happen to plot the one where I run it down to the right here but if I mix these up and gave one of them to you you probably wouldn't be able to tell which one is which so that that's really interesting um another way to kind of see this is if I look at um let's just look at maybe the red channel so I'll just this is just a red channel here let's look at what that is mod to so so that's going to be one whenever the thing is is odd and zero whenever it's even and you see overall it looks just just like noise so the the blues here are basically the even pixels the the yellows are the odd pixels there's a little bit of structure here but overall it's pretty random and so what that tells us is hey if it's random uh and you can't even see it you can treat it kind of like noise um you can also make it what you want and so what we're going to do is is treat uh a pixel that is even as a zero and binary and Tre to pixel that's odd is a one in binary all right so so that's where we're headed so we we can make changes to this that we would never even notice all right because because you can't even see it so if if I change what's what's even and what's odd you won't notice all right so basically what I'm going to do is create a random one of these images or or create um one of these images which has the data that I want and add back to ir and then that has is okay so we can hide any binary data that we want but I'm going to focus on hiding text so that we're consistent with that old school application so I'm going to focus specifically on something called asky representation of characters and so this is a way of representing some Western characters with binary and so specifically I look at this and I see okay um they're 8 bit codes although we're going to just focus on the seven bit codes here um and so what that means is is each character in aski is represented as a number between 0 and 127 so for example the letter lowercase letter a is represented as 97 in decimal or 61 in HEX now I could talk more about hex and biner and all that that's a bit beyond the scope of this video um but even if you're not that comfortable with that that's okay um Python's going to do a lot of this work for us so let me show you what I mean so that lowercase a if I say if I were to say O A gives me 97 just like I saw in the table a moment ago and you know but we're eventually we're trying to get down to the ones and zeros right because because I want to be able to to somehow unroll this into the image as a sequence of ones and zeros and so I can go one step further and say bin of that and that gives me the binary representation so no notice here that there are seven bits and so this is 1 1 01 now let's actually I don't remember I need to check here what happens if um I don't need seven bits to represent it so what if I were to put in like an exclamation mark or something I think it's going to not use all seven bits okay so you have to be a little bit mindful of this notice that this one only gives me back six bits because technically an exclamation mark doesn't need seven bits because that's just a the number 33 I can get away with six bits for that so let me just make a little helper method here real quick so maybe get binary representation of of a character and I'll say okay so my X is equal to Binary or D whatever that character is and actually you know it's giving me back this it's this bit string I'm going to chop off the first two the zero B there that's just telling me it's in binary um so so I'll chop that off get that uh let me also it'll be convenient if I convert these to ins so I'll say in X4 X in that actually before I put that in the method let me just check my work down here so so I'll say here C is equal to we'll say exclamation mark to start with um Oops I meant to say yeah I need to chop off those first two characters so I'm going to say two there okay there we go so so now I have an array of integers representing this but I want to make sure there're all seven uh bits just so I know exactly where the start and end of each character is even if I don't need all seven so what I'll do is I'll just pad this I'll say okay so here's my representation and I'll say x is equal to I'll just pad it on the left with zeros doesn't change change the number patter on the left with zeros um as many as there are the length of X um 7 minus that okay so if I only have six bits six on zeros I'm going to have to put an extra one there now this this should be everything okay good so so this is going to return a list of ones and zeros so return a list of ones and zeros um corresponding to the S key encoding of a character and you know let's let's make another method um maybe I'll call this one g biner of char but I'll make another one which is which is get binary string where I do this for every character so I'll say for C in s r plus equals get bin Char C return R so as an example let's just see see this in action if I were to say um x equals get vinr steganography and let's print that out so there's my hidden message in binary all right so now we got to put this inside of the image so what I'll do is let me make another little method here um basically I'm going to make this image and and add it back to the rounded down version okay so I'm going to do something like this to start with so let me call this method um in code so it'll it'll take in an image and then it'll take in a string so actually often like when I'm writing a method to to kind of call the shots up front so I'll say what this method is supposed to do so so this is supposed to hide the string s in its asky representation um inside of the least significant bit of I okay uh by the way the Reas the reason it's called the least significant bit just to show you um if I were to look at for example 67 finite representation versus 66 notice that they're exactly the same except for the last bit is different okay so so that's why you call it the least significant bit it's it's the bit in the one's place it's always going to be a zero if the number is even it's always going to be a one if the number is odd so we're going to round down first we're basically going to get rid of the least significant bit it's going to be zero everywhere but then we're going to add some ones back where we would have those in the binary representation okay so that's going to do so first parameter here is going to be an image with so many rows in columns is going to have red green blue Channel um this is often called the carrier in St free contract so I'll call it that so carrier image it's going to carry the hidden data um and then this is is the hidden message and so what I'll do first is is I'll just call this this little method I made so I'll say x isal to get binary string of s and actually I'm also going to convert that to a nonp array because what I'll do in a moment is is just add that into the image so this actually if we look at I want to double check this if I look at the type of this image here when I first loaded this yeah okay so this is an unsigned integer with 8 Bits u in 8 so let me make sure that that's the type that I'm working with here so I'll say step one is get the binary string that I want to hide okay now step two is is um round down the image the nearest even so I'll say I round is equal to IUS I 2 I've already done that um step three is uh put the binary data in some systematic order in the image so if I look at this this image I that I had let let me print out maybe the first row maybe first couple rows um so I'll say Okay row zero maybe 0 to one there still too many numbers there um okay that's fine but that's enough to kind of to see so if I were then print i. flatten um this is a way to to I guess they call it raveling let me see I think that's another name for it Ravel yeah uh this is it's the opposite of unv so what I'm going to do is is take this data and go basically inner row by Inner row and just lay it out in order so it's a way to take it's basically going row by row but yeah it's going row by Row in the image um it's also called raster order and then taking RGB RGB RGB RGB so it's going top to bottom row to row basically the order that we would read um English text and it's it's arranging all the pixels of the image in that order from top to bottom left to right so it's going to go boom boom boom boom boom and so that's what we'll do we we'll we'll we'll start encoding the first letter in the upper left and we'll keep going and keep going and keep going and so we we're certainly not going to need most of these rows most of the message probably is going to get hidden up here in the upper left but as long as we have some systematic order now if you're trying to be a little sneakier you could Shuffle it up right you could have some kind of Cipher key that um tells you which rows and columns which order to visit them so so you could actually mix this up a little bit but here I'll just do the systematic order from you know Left Right top down so what I'll say is okay um IR round equals ir. flatten Some people prefer Ravel the opposite of unraveling um and then what I'll do is I say I round okay fine I've put this down to one one dimension and then what I'll do is I'll say okay well the first you know however many elements there were however many bits there were in X um I will add those in and into those positions so again I'm probably not going to use the vast majority of the pixels and color channels that I have available to me but I'll use however many bits I need starting in the upper left I'll add this in and now I can unravel it I can go back and I can say Okay IR round is equal to mp. reshape IR round I shape actually just return that so this is is is adding in the hidden bits right so so I rounded everything down I've got a clean slate now I'm going to add on wherever there's supposed to be a one I'm going to add that on okay so let's let's just see if this message works or if this method runs and then then I have to make a decode method to to get my secret message back so I'll say I encode is equal to encode I steganography okay so it ran um let's just look at at it just to see so I don't really notice anything different but somehow there's a hidden message here okay we're just about ready to decode now but there's one more thing that we need to take into consideration and that's we need some way of marking when the string ends because when we decode this we need to know when we're done because we're going to be reading this seven values at a time one character for every seven but how do we know that that we're finished and and the way we do this is we add on something called a null Terminator so I'll say R plus = 0 * 7 add on null Terminator which you'll see this in the asking table to um this is a convention if the 7 bits are all zero it's representing the number zero this means the string is finished so that's called the N Terminator so just make sure that that I actually end my messages with seven zeros okay so so I just tacked on this Zer is there now I'll know what I'm actually done okay so let me start to mock up a decoder now I'm going to do the opposite of what I did I'm going to first Ravel this so I'm going to say okay my decoding is equal to the encoding flatten um and I don't you know I'm going to go until I see the null Terminator so I'll say uh still reading I'll use a while loop so say while I'm still reading um I'll go ahead and I'll take out so this index tells me the index where I'm starting looking at a particular character I know that I want to look at every seven bits so I'm going to take out every seven uh values out of this image so say I decod or yeah I decoded um I to I + 7 so so I'm going slice that out I'm also going to take uh the mod 2 here because I know that I'm looking at the least significant bit so if it's if it's odd I want to treat it as a one if it's even I want to treat it as a zero uh let me just look at maybe the first 10 of these so I print that out um and of course I need to increment I as well so I'm moving along okay so there is my first character there's my second character third character fourth Etc uh but I need to do just a little bit more work to convert these actually back to asking so the first thing I'll do is is actually recognize that you know it' be helpful to go back to a decimal representation first or or really um just a a regular int number out of this binary now there's probably a quicker way to do this the python but let me actually do it out a little bit just so we have a quick binary review but so in binary know this is the one's place this is the two's Place Four's Place Eight's place they always double 16 32 64 so I can make a little array of places so I'll say maybe 2 to the 6 - I for I in range 7 um let's let's check on that is that what I said it would be yeah okay so so on the left it's a 64's place 32s Place 168 so what I can do is okay I'll do this right before the loop and I'll say okay this this number I'm going to make as um I'm going to say that that is the sum of the places time C so those who know the lingo this is called a DOT product but I'm just saying okay look this one 64 * 1 plus 0 * 32 no 32 so 64 + 16 + 2 + 1 and so that ends up being so if I print this out back to back so I like to do this sort of stuff for debugging just to make sure I'm on the right track okay so the number 83 so 836 101 let's just remember that couple first couple 8316 so 83 my c s great um what I say 83 116 lowercase T that's great and guess what python will actually do that for us too so if I would to say print C CHR C there we go okay good so it looks like we're on the right track here I just need to go one step further and know and and realize what I'm done here so I'll say you know um if C is equal Zer then I will say still reading is false okay so now I can actually basically do the fully flesh thing okay there we go so steganography and then I have my null Terminator at the end okay so that so that's it um let me just put this inside of a method to encapsulate it oops um so I'm going to pass along some encoded message and what I'll do is is okay I don't need to print this stuff out for debugging what I'll do instead is I will um if it's not the null Terminator I will accumulate that character onto the end of some string that I'm building and so let's just check to see if that works if I said decode I encode there we go stick off for you so there you have it um and again we we can't see the the presence of that hidden message and maybe I'll try something a little bit longer here so let's say I'll make some other image here say I2 is equal to encode I let's see hello I hope you are enjoying this tutorial on steganography okay and so so again here's the image shouldn't really notice anything um and if I print decode I2 I get it back perfect so there's our our hidden message um if I go back and I try to decode the original image I just get gibberish because like I said it's it's basically I mean you look back at this basically random maybe a little bit more systematic in some places but basically random bits and then eventually you you know with with some chance you'll reach the N Terminator and stop okay there's just one more thing I want to show you about how to save your images with the hidden data so there's a couple things to be aware of here um I found I was getting some some kind of weird annoying things happening with M plot Libs Library so I'm going to use a library called Sid image instead so you can pip install this with umit image but anyway so let's say that I wanted to save my hidden thing um I'll call it maybe aspiration. jpg so there's a method called I am save so first parameter is the name of the file second parameter is the image I'm trying to save so my aspiration for everyone is that they enjoy this tutorial so I'll say skage doo. save aspiration jpg and I will save my I2 there so let's go ahead and try to load this back so if I were now to say I load equals SK sorry SK image. i. imre aspiration. HG and let's look at it certainly looks fine what about when I go to decode it well it's complete jib rush so what happened Well turns out jpeg is what's known as a lossy decoding Tech technique it's actually making small alterations to the pixels as well um that you won't notice so that it can use fewer bits to represent them hopefully I'll get to do a tutorial in jpeg at some point too um my first big project in college was was making a JPEG dco coder from scratch but for now we'll take that as a granted and we'll notice that okay jpeg is is doing its own little alterations that are interfering with our alterations um so we're not going to get back the message that that we tried to encode so I have to use a lossless encoding so PNG is an example of a lossless encoder so it can compress it but it's not going to change the data that's there so I want to do it like this I get the message back just fine so anyway I hope you enjoyed this video I will include a link to the code from my uh video page and um let me know if you have any questions enjoy it
Info
Channel: Chris Tralie
Views: 3,015
Rating: undefined out of 5
Keywords:
Id: kkmpP_7mY_I
Channel Id: undefined
Length: 25min 36sec (1536 seconds)
Published: Mon Mar 18 2024
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.