diff --git a/examples/stego.go b/examples/stego.go index 982c907..d4b9ca4 100644 --- a/examples/stego.go +++ b/examples/stego.go @@ -2,6 +2,7 @@ package main import ( "bufio" + "bytes" "flag" "fmt" "image" @@ -37,6 +38,21 @@ func init() { flag.Parse() } +// OpenImageFromPath returns a image.Image from a file path. A helper function to deal with decoding the image into a usable format. This method is optional. +func OpenImageFromPath(filename string) (image.Image, error) { + inFile, err := os.Open(filename) + if err != nil { + return nil, err + } + defer inFile.Close() + reader := bufio.NewReader(inFile) + img, _, err := image.Decode(reader) + if err != nil { + return nil, err + } + return img, nil +} + func main() { if encode { message, err := ioutil.ReadFile(messageInputFile) // Read the message from the message file (alternative to os.Open ) @@ -56,8 +72,12 @@ func main() { if err != nil { log.Fatalf("Error opening file %v", err) } - encodedImg := steganography.EncodeString(message, img) // Calls library and Encodes the message into a new buffer - outFile, err := os.Create(pictureOutputFile) // Creates file to write the message into + encodedImg := new(bytes.Buffer) + err = steganography.Encode(encodedImg, img, message) // Calls library and Encodes the message into a new buffer + if err != nil { + log.Fatalf("Error encoding message into file %v", err) + } + outFile, err := os.Create(pictureOutputFile) // Creates file to write the message into if err != nil { log.Fatalf("Error creating file %s: %v", pictureOutputFile, err) } @@ -77,9 +97,9 @@ func main() { log.Fatal("error decoding file", img) } - sizeOfMessage := steganography.GetSizeOfMessageFromImage(img) // Uses the library to check the message size + sizeOfMessage := steganography.GetMessageSizeFromImage(img) // Uses the library to check the message size - msg := steganography.DecodeMessageFromPicture(4, sizeOfMessage, img) // Read the message from the picture file + msg := steganography.Decode(4, sizeOfMessage, img) // Read the message from the picture file // if the user specifies a location to write the message to... if messageOutputFile != "" { diff --git a/steganography.go b/steganography.go index e9cdcd0..f047503 100644 --- a/steganography.go +++ b/steganography.go @@ -2,40 +2,37 @@ package steganography import ( - "bufio" "bytes" + "errors" + "fmt" "image" "image/color" "image/draw" "image/png" - "os" ) -// EncodeString encodes a given string into the input image using least significant bit encryption (LSB steganography) +// EncodeRGBA encodes a given string into the input image using least significant bit encryption (LSB steganography) +// The minnimum image size is 24 pixels for one byte. For each additional byte, it is necessary 3 more pixels. /* Input: + writeBuffer *bytes.Buffer : the destination of the encoded image bytes + pictureInputFile image.RGBA : image data used in encoding message []byte : byte slice of the message to be encoded - pictureInputFile image.Image : image data used in encoding Output: bytes buffer ( io.writter ) to create file, or send data. */ -func EncodeString(message []byte, pictureInputFile image.Image) bytes.Buffer { - w := new(bytes.Buffer) - - rgbIm := imageToRGBA(pictureInputFile) +func EncodeRGBA(writeBuffer *bytes.Buffer, rgbImage *image.RGBA, message []byte) error { - var messageLength uint32 = uint32(len(message)) + var messageLength = uint32(len(message)) - var width int = rgbIm.Bounds().Dx() - var height int = rgbIm.Bounds().Dy() + var width = rgbImage.Bounds().Dx() + var height = rgbImage.Bounds().Dy() var c color.RGBA var bit byte var ok bool //var encodedImage image.Image - - if MaxEncodeSize(rgbIm) < messageLength+4 { - print("Error! The message you are trying to encode is too large.") - return *w + if MaxEncodeSize(rgbImage) < messageLength+4 { + return errors.New("message too large for image") } one, two, three, four := splitToBytes(messageLength) @@ -52,61 +49,79 @@ func EncodeString(message []byte, pictureInputFile image.Image) bytes.Buffer { for x := 0; x < width; x++ { for y := 0; y < height; y++ { - c = rgbIm.RGBAAt(x, y) // get the color at this pixel + c = rgbImage.RGBAAt(x, y) // get the color at this pixel /* RED */ bit, ok = <-ch if !ok { // if we don't have any more bits left in our message - rgbIm.SetRGBA(x, y, c) - png.Encode(w, rgbIm) - return *w + rgbImage.SetRGBA(x, y, c) + png.Encode(writeBuffer, rgbImage) + // return *writeBuffer, nil } setLSB(&c.R, bit) /* GREEN */ bit, ok = <-ch if !ok { - rgbIm.SetRGBA(x, y, c) - png.Encode(w, rgbIm) - return *w + rgbImage.SetRGBA(x, y, c) + png.Encode(writeBuffer, rgbImage) + return nil } setLSB(&c.G, bit) /* BLUE */ bit, ok = <-ch if !ok { - rgbIm.SetRGBA(x, y, c) - png.Encode(w, rgbIm) - return *w + rgbImage.SetRGBA(x, y, c) + png.Encode(writeBuffer, rgbImage) + return nil } setLSB(&c.B, bit) - rgbIm.SetRGBA(x, y, c) + rgbImage.SetRGBA(x, y, c) } } - png.Encode(w, rgbIm) - return *w + err := png.Encode(writeBuffer, rgbImage) + fmt.Println("err") + return err } -// DecodeMessageFromPicture using LSB steganography, decode the message from the picture and return it as a sequence of bytes +// Encode encodes a given string into the input image using least significant bit encryption (LSB steganography) +// The minnimum image size is 23 pixels +// It wraps EncodeRGBA making the conversion from image.Image to image.RGBA +/* + Input: + writeBuffer *bytes.Buffer : the destination of the encoded image bytes + message []byte : byte slice of the message to be encoded + pictureInputFile image.Image : image data used in encoding + Output: + bytes buffer ( io.writter ) to create file, or send data. +*/ +func Encode(writeBuffer *bytes.Buffer, pictureInputFile image.Image, message []byte) error { + + rgbImage := imageToRGBA(pictureInputFile) + + return EncodeRGBA(writeBuffer, rgbImage, message) + +} + +// DecodeRGBA gets messages from pictures using LSB steganography, decode the message from the picture and return it as a sequence of bytes /* Input: startOffset uint32 : number of bytes used to declare size of message msgLen uint32 : size of the message to be decoded - pictureInputFile image.Image : image data used in decoding + pictureInputFile image.RGBA : image data used in decoding Output: message []byte decoded from image */ -func DecodeMessageFromPicture(startOffset uint32, msgLen uint32, pictureInputFile image.Image) (message []byte) { +func DecodeRGBA(startOffset uint32, msgLen uint32, rgbImage *image.RGBA) (message []byte) { var byteIndex uint32 var bitIndex uint32 - rgbIm := imageToRGBA(pictureInputFile) - - width := rgbIm.Bounds().Dx() - height := rgbIm.Bounds().Dy() + width := rgbImage.Bounds().Dx() + height := rgbImage.Bounds().Dy() var c color.RGBA var lsb byte @@ -117,7 +132,7 @@ func DecodeMessageFromPicture(startOffset uint32, msgLen uint32, pictureInputFil for x := 0; x < width; x++ { for y := 0; y < height; y++ { - c = rgbIm.RGBAAt(x, y) // get the color of the pixel + c = rgbImage.RGBAAt(x, y) // get the color of the pixel /* RED */ lsb = getLSB(c.R) // get the least significant bit from the red component of this pixel @@ -172,17 +187,40 @@ func DecodeMessageFromPicture(startOffset uint32, msgLen uint32, pictureInputFil return } +// Decode gets messages from pictures using LSB steganography, decode the message from the picture and return it as a sequence of bytes +// It wraps EncodeRGBA making the conversion from image.Image to image.RGBA +/* + Input: + startOffset uint32 : number of bytes used to declare size of message + msgLen uint32 : size of the message to be decoded + pictureInputFile image.Image : image data used in decoding + Output: + message []byte decoded from image +*/ +func Decode(startOffset uint32, msgLen uint32, pictureInputFile image.Image) (message []byte) { + + rgbImage := imageToRGBA(pictureInputFile) + return DecodeRGBA(startOffset, msgLen, rgbImage) + +} + // MaxEncodeSize given an image will find how many bytes can be stored in that image using least significant bit encoding +// ((width * height * 3) / 8 ) - 4 +// The result must be at least 4, func MaxEncodeSize(img image.Image) uint32 { width := img.Bounds().Dx() height := img.Bounds().Dy() - return uint32(((width * height * 3) / 8)) - 4 + eval := ((width * height * 3) / 8) - 4 + if eval < 4 { + eval = 0 + } + return uint32(eval) } -// GetSizeOfMessageFromImage gets the size of the message from the first four bytes encoded in the image -func GetSizeOfMessageFromImage(pictureInputFile image.Image) (size uint32) { +// GetMessageSizeFromImage gets the size of the message from the first four bytes encoded in the image +func GetMessageSizeFromImage(pictureInputFile image.Image) (size uint32) { - sizeAsByteArray := DecodeMessageFromPicture(0, 4, pictureInputFile) + sizeAsByteArray := Decode(0, 4, pictureInputFile) size = combineToInt(sizeAsByteArray[0], sizeAsByteArray[1], sizeAsByteArray[2], sizeAsByteArray[3]) return } @@ -237,7 +275,7 @@ func getBitFromByte(b byte, indexInByte int) byte { b = b << uint(indexInByte) var mask byte = 0x80 - var bit byte = mask & b + var bit = mask & b if bit == 128 { return 1 @@ -282,22 +320,7 @@ func splitToBytes(x uint32) (one, two, three, four byte) { return } -// OpenImageFromPath returns a image.Image from a file path. A helper function to deal with decoding the image into a usable format. This method is optional. -func OpenImageFromPath(filename string) (image.Image, error) { - inFile, err := os.Open(filename) - if err != nil { - return nil, err - } - defer inFile.Close() - reader := bufio.NewReader(inFile) - img, _, err := image.Decode(reader) - if err != nil { - return nil, err - } - return img, nil -} - -// imageToRGBA convert given image to RGBA image +// imageToRGBA converts image.Image to image.RGBA func imageToRGBA(src image.Image) *image.RGBA { bounds := src.Bounds() diff --git a/steganography_test.go b/steganography_test.go index 21ccdba..c54b94d 100644 --- a/steganography_test.go +++ b/steganography_test.go @@ -4,6 +4,7 @@ import ( "bufio" "bytes" "image" + "image/color" "log" "os" "testing" @@ -14,7 +15,7 @@ var encodedInputFile = "./examples/encoded_stegosaurus.png" var bitmessage = []uint8{84, 104, 101, 113, 117, 97, 100, 114, 117, 112, 101, 100, 97, 108, 83, 116, 101, 103, 111, 115, 97, 117, 114, 117, 115, 105, 115, 111, 110, 101, 111, 102, 116, 104, 101, 109, 111, 115, 116, 101, 97, 115, 105, 108, 121, 105, 100, 101, 110, 116, 105, 102, 105, 97, 98, 108, 101, 100, 105, 110, 111, 115, 97, 117, 114, 103, 101, 110, 101, 114, 97, 44, 100, 117, 101, 116, 111, 116, 104, 101, 100, 105, 115, 116, 105, 110, 99, 116, 105, 118, 101, 100, 111, 117, 98, 108, 101, 114, 111, 119, 111, 102, 107, 105, 116, 101, 45, 115, 104, 97, 112, 101, 100, 112, 108, 97, 116, 101, 115, 114, 105, 115, 105, 110, 103, 118, 101, 114, 116, 105, 99, 97, 108, 108, 121, 97, 108, 111, 110, 103, 116, 104, 101, 114, 111, 117, 110, 100, 101, 100, 98, 97, 99, 107, 97, 110, 100, 116, 104, 101, 116, 119, 111, 112, 97, 105, 114, 115, 111, 102, 108, 111, 110, 103, 115, 112, 105, 107, 101, 115, 101, 120, 116, 101, 110, 100, 105, 110, 103, 104, 111, 114, 105, 122, 111, 110, 116, 97, 108, 108, 121, 110, 101, 97, 114, 116, 104, 101, 101, 110, 100, 111, 102, 116, 104, 101, 116, 97, 105, 108, 46, 65, 108, 116, 104, 111, 117, 103, 104, 108, 97, 114, 103, 101, 105, 110, 100, 105, 118, 105, 100, 117, 97, 108, 115, 99, 111, 117, 108, 100, 103, 114, 111, 119, 117, 112, 116, 111, 57, 109, 40, 50, 57, 46, 53, 102, 116, 41, 105, 110, 108, 101, 110, 103, 116, 104, 91, 52, 93, 97, 110, 100, 53, 46, 51, 116, 111, 55, 109, 101, 116, 114, 105, 99, 116, 111, 110, 115, 40, 53, 46, 56, 116, 111, 55, 46, 55, 115, 104, 111, 114, 116, 116, 111, 110, 115, 41, 105, 110, 119, 101, 105, 103, 104, 116, 44, 91, 53, 93, 91, 54, 93, 116, 104, 101, 118, 97, 114, 105, 111, 117, 115, 115, 112, 101, 99, 105, 101, 115, 111, 102, 83, 116, 101, 103, 111, 115, 97, 117, 114, 117, 115, 119, 101, 114, 101, 100, 119, 97, 114, 102, 101, 100, 98, 121, 99, 111, 110, 116, 101, 109, 112, 111, 114, 97, 114, 105, 101, 115, 44, 116, 104, 101, 103, 105, 97, 110, 116, 115, 97, 117, 114, 111, 112, 111, 100, 115, 46, 83, 111, 109, 101, 102, 111, 114, 109, 111, 102, 97, 114, 109, 111, 114, 97, 112, 112, 101, 97, 114, 115, 116, 111, 104, 97, 118, 101, 98, 101, 101, 110, 110, 101, 99, 101, 115, 115, 97, 114, 121, 44, 97, 115, 83, 116, 101, 103, 111, 115, 97, 117, 114, 117, 115, 115, 112, 101, 99, 105, 101, 115, 99, 111, 101, 120, 105, 115, 116, 101, 100, 119, 105, 116, 104, 108, 97, 114, 103, 101, 112, 114, 101, 100, 97, 116, 111, 114, 121, 116, 104, 101, 114, 111, 112, 111, 100, 100, 105, 110, 111, 115, 97, 117, 114, 115, 44, 115, 117, 99, 104, 97, 115, 65, 108, 108, 111, 115, 97, 117, 114, 117, 115, 97, 110, 100, 67, 101, 114, 97, 116, 111, 115, 97, 117, 114, 117, 115, 46} -func TestEncode(t *testing.T) { +func TestEncodeFromFile(t *testing.T) { inFile, err := os.Open(rawInputFile) if err != nil { @@ -30,18 +31,24 @@ func TestEncode(t *testing.T) { log.Printf("Error decoding. %v", err) t.FailNow() } - encodedImg := EncodeString(bitmessage, img) // Encode the message into the image file + w := new(bytes.Buffer) + err = Encode(w, img, bitmessage) // Encode the message into the image file + if err != nil { + log.Printf("Error Encoding file %v", err) + t.FailNow() + + } outFile, err := os.Create(encodedInputFile) if err != nil { log.Printf("Error creating file %s: %v", encodedInputFile, err) t.FailNow() } - w := bufio.NewWriter(outFile) - w.Write(encodedImg.Bytes()) + w.WriteTo(outFile) + defer outFile.Close() } -func TestDecode(t *testing.T) { +func TestDecodeFromFile(t *testing.T) { inFile, err := os.Open(encodedInputFile) if err != nil { log.Printf("Error opening file %s: %v", encodedInputFile, err) @@ -56,46 +63,85 @@ func TestDecode(t *testing.T) { t.FailNow() } - sizeOfMessage := GetSizeOfMessageFromImage(img) + sizeOfMessage := GetMessageSizeFromImage(img) - msg := DecodeMessageFromPicture(4, sizeOfMessage, img) // Read the message from the picture file - - // otherwise, print the message to STDOUT + msg := Decode(4, sizeOfMessage, img) // Read the message from the picture file if !bytes.Equal(msg, bitmessage) { + log.Println(string(msg)) log.Print("messages dont match") t.FailNow() } } -func TestEncodeDecode(t *testing.T) { - img, err := OpenImageFromPath(rawInputFile) +func TestEncodeDecodeGeneratedSmallImage(t *testing.T) { + // Creating image + width := 30 + height := 1 + + upLeft := image.Point{0, 0} + lowRight := image.Point{width, height} + + newimg := image.NewRGBA(image.Rectangle{upLeft, lowRight}) + + // Set color for each pixel. + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + newimg.Set(x, y, color.White) + } + } + + w := new(bytes.Buffer) + err := EncodeRGBA(w, newimg, []uint8{84, 84, 84}) // Encode the message into the image file if err != nil { - log.Printf("Error opening or Decoding file %s: %v", rawInputFile, err) + log.Printf("Error Encoding file %v", err) t.FailNow() - } - encodedImg := EncodeString(bitmessage, img) // Encode the message into the image file - img, _, err = image.Decode(bytes.NewReader(encodedImg.Bytes())) + } + decodeImg, _, err := image.Decode(w) if err != nil { - log.Print("Error decoding file") + log.Println("Failed to Decode Image") t.FailNow() } - sizeOfMessage := GetSizeOfMessageFromImage(img) + sizeOfMessage := GetMessageSizeFromImage(decodeImg) - msg := DecodeMessageFromPicture(4, sizeOfMessage, img) // Read the message from the picture file + msg := Decode(4, sizeOfMessage, decodeImg) // Read the message from the picture file - if !bytes.Equal(msg, bitmessage) { + // otherwise, print the message to STDOUT + + if !bytes.Equal(msg, []uint8{84, 84, 84}) { + log.Println(string(msg)) log.Print("messages dont match") t.FailNow() } } +func TestSmalImage(t *testing.T) { + + miniImage := image.Image(image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{18, 1}})) + log.Print(MaxEncodeSize(miniImage)) + if MaxEncodeSize(miniImage) > 0 { + log.Printf("Uncaught small image size") + t.FailNow() + } + + miniImage = image.Image(image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{23, 1}})) + + if MaxEncodeSize(miniImage) != 4 { + log.Printf("Uncaught minimal image size") + t.FailNow() + } +} -func TestEmptyPathHelperFunction(t *testing.T) { - _, err := OpenImageFromPath(" ") +func TestMessageTooLarge(t *testing.T) { + + miniImage := image.Image(image.NewRGBA(image.Rectangle{image.Point{0, 0}, image.Point{24, 1}})) + w := new(bytes.Buffer) + err := Encode(w, miniImage, bitmessage) // Encode the message into the image file if err == nil { - log.Print("Empty path given, err could not be nil.") + log.Printf("Uncaught error: message too large for image") t.FailNow() + } + }