Skip to content

Instantly share code, notes, and snippets.

@husobee
Created August 31, 2014 22:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save husobee/64b49e92a57e0d6d90d2 to your computer and use it in GitHub Desktop.
Save husobee/64b49e92a57e0d6d90d2 to your computer and use it in GitHub Desktop.
VERY simplified LSB stego example on PNGs (lossless)
// 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