Created
August 31, 2014 22:23
-
-
Save husobee/64b49e92a57e0d6d90d2 to your computer and use it in GitHub Desktop.
VERY simplified LSB stego example on PNGs (lossless)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// example of how to hide data in LSB of colors within a lossless png | |
package main | |
import ( | |
"errors" | |
"flag" | |
"fmt" | |
"image" | |
"image/color" | |
"image/png" | |
"io/ioutil" | |
"os" | |
) | |
// command line options | |
var input_filename = flag.String("image-input-file", "", "input image file") | |
var output_filename = flag.String("image-output-file", "", "output image file") | |
var message_filename = flag.String("message-input-file", "", "message input file") | |
var operation = flag.String("operation", "encode", "encode or decode") | |
// example encode usage: go run png-lsb-steg.go -operation encode -image-input-file test.png -image-output-file steg.png -message-input-file hide.txt | |
// example decode usage: go run main.go -operation decode -image-input-file steg.png | |
// the bitmask we will use (last two bits) | |
var lsb_mask uint32 = ^(uint32(3)) | |
//helper to dry error handling up | |
func panicOnError(e error) { | |
if e != nil { | |
panic(e) | |
} | |
} | |
// main, based on operation flag will encode data into image, or decode data from an image | |
func main() { | |
// parse the command line options | |
flag.Parse() | |
// operation switch | |
switch *operation { | |
case "encode": | |
fmt.Println("encoding!") | |
// read the input file | |
input_reader, input_err := os.Open(*input_filename) | |
// panic on an error | |
panicOnError(input_err) | |
// close the reader | |
defer input_reader.Close() | |
// read the input message file | |
message, input_message_err := ioutil.ReadFile(*message_filename) | |
// panic on an error | |
panicOnError(input_message_err) | |
// decode the image | |
img, _, image_decode_err := image.Decode(input_reader) | |
// panic if image isn't decoded | |
panicOnError(image_decode_err) | |
// get the bounds of the image | |
bounds := img.Bounds() | |
// create output image | |
output_image := image.NewNRGBA64(img.Bounds()) | |
// get the rows and columns of the image | |
var message_index int = 0 | |
// loop over rows | |
for y := bounds.Min.Y; y < bounds.Max.Y; y++ { | |
// loop over columns | |
for x := bounds.Min.X; x < bounds.Max.X; x++ { | |
// get the rgba values from the input image | |
r, g, b, a := img.At(x, y).RGBA() | |
// if we have bytes in message | |
if message_index < len(message) { | |
// first two bits | |
newr := uint32(message[message_index]>>6) + (r & lsb_mask) | |
// second two bits | |
newg := uint32(message[message_index]>>4) & ^lsb_mask + (g & lsb_mask) | |
// third two bits | |
newb := uint32(message[message_index]>>2) & ^lsb_mask + (b & lsb_mask) | |
// last two bits | |
newa := uint32(message[message_index]) & ^lsb_mask + (a & lsb_mask) | |
message_index++ | |
// set the color in the new output image | |
output_image.SetNRGBA64(x, y, color.NRGBA64{uint16(newr), uint16(newg), uint16(newb), uint16(newa)}) | |
} else if message_index == len(message) { | |
// if we are done with our message bytes | |
message_index++ | |
// set a null ascii char to know if we are done | |
output_image.SetNRGBA64(x, y, color.NRGBA64{uint16(0), uint16(0), uint16(0), uint16(0)}) | |
} else { | |
// otherwise, just put the exact values in the new image | |
output_image.SetNRGBA64(x, y, color.NRGBA64{uint16(r), uint16(g), uint16(b), uint16(a)}) | |
} | |
} | |
} | |
// we obviously have more data that won't fit in the image | |
if message_index < len(message) { | |
panicOnError(errors.New("out of space in input image")) | |
} | |
// write the new file out | |
output_writer, output_err := os.Create(*output_filename) | |
// panic if fails.. | |
panicOnError(output_err) | |
// close output file when donw | |
defer output_writer.Close() | |
// encode the png | |
png.Encode(output_writer, output_image) | |
// operation was decode that we passed in | |
case "decode": | |
fmt.Println("decoding!") | |
// read the input file | |
input_reader, input_err := os.Open(*input_filename) | |
// panic on an error | |
panicOnError(input_err) | |
// close the reader | |
defer input_reader.Close() | |
// decode the image | |
img, _, image_decode_err := image.Decode(input_reader) | |
// panic if image isn't decoded | |
panicOnError(image_decode_err) | |
// get the bounds of the image | |
bounds := img.Bounds() | |
// get the rows and columns of the image | |
// loop over rows we will break here if done reading message | |
OUTER: | |
for y := bounds.Min.Y; y < bounds.Max.Y; y++ { | |
// loop over columns | |
for x := bounds.Min.X; x < bounds.Max.X; x++ { | |
// get the rgba values from the input image | |
c := img.At(x, y).(color.NRGBA64) | |
r := uint32(c.R) | |
g := uint32(c.G) | |
b := uint32(c.B) | |
a := uint32(c.A) | |
// build the byte from the color lsbs | |
ch := (r & ^lsb_mask) << 6 | |
ch += (g & ^lsb_mask) << 4 | |
ch += (b & ^lsb_mask) << 2 | |
ch += (a & ^lsb_mask) | |
// if we come across a zero byte | |
if ch == 0 { | |
break OUTER | |
} | |
// if the char is valid ascii print it out | |
if ch >= 32 && ch <= 126 { | |
fmt.Printf("%c", ch) | |
} | |
} | |
} | |
// newline | |
fmt.Printf("\n") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment