diff --git a/README.md b/README.md index 6e8010a..7e697eb 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,20 @@ Hides a string inside the least significant bit (LSB) of all colors of each pixel. +## Build + +``` +go build -o stego stego.go +``` + ## Hide ``` -./stego -in testdata/lenna.png -out testdata/stego.png -secret "hello world!" +./stego -in testdata/lenna.png -secret testdata/lenna_256.png -out testdata/stego.png ``` ## Reveal ``` -./stego -in testdata/stego.png +./stego -in testdata/stego.png -out lenna_256.png ``` diff --git a/stego.go b/stego.go index b586d1a..c45df43 100644 --- a/stego.go +++ b/stego.go @@ -3,6 +3,7 @@ package main import ( + "encoding/binary" "errors" "flag" "fmt" @@ -11,23 +12,49 @@ import ( _ "image/gif" _ "image/jpeg" "image/png" + "io/ioutil" "os" ) +const offset = 4 + +func encodeLen(l int) []byte { + b := make([]byte, offset) + binary.LittleEndian.PutUint32(b, uint32(l)) + return b +} + +func getLen(p image.Image) int { + z := make([]byte, offset) + for i := 0; i < offset; i++ { + pix := color.NRGBAModel.Convert(p.At(i, 0)).(color.NRGBA) + z[i] = (pix.R << 6) + z[i] |= (pix.G << 6) >> 2 + z[i] |= (pix.B << 6) >> 4 + z[i] |= (pix.A << 6) >> 6 + } + return int(binary.LittleEndian.Uint32(z)) +} + func unseal(p image.Image) (b []byte) { rect := p.Bounds() + l := getLen(p) + c := 0 for i := 0; i < rect.Max.Y; i++ { for j := 0; j < rect.Max.X; j++ { + if i == 0 && j == 0 { + j = offset + } pix := color.NRGBAModel.Convert(p.At(j, i)).(color.NRGBA) z := (pix.R << 6) z |= (pix.G << 6) >> 2 z |= (pix.B << 6) >> 4 z |= (pix.A << 6) >> 6 - if z == byte(0) { - b = append(b, '\n') + if c == l { return } b = append(b, z) + c++ } } return @@ -37,28 +64,22 @@ func seal(p image.Image, s []byte) (image.Image, error) { rect := p.Bounds() n := image.NewNRGBA(rect) c := 0 - max := (rect.Max.Y * rect.Max.X) - 1 - if len(s) > max { + arr := append(encodeLen(len(s)), s...) + l := len(arr) + max := (rect.Max.Y * rect.Max.X) + if l > max { return nil, errors.New("not enough space in image") } for i := 0; i < rect.Max.Y; i++ { for j := 0; j < rect.Max.X; j++ { pix := color.NRGBAModel.Convert(p.At(j, i)).(color.NRGBA) - if c < len(s) { - r := (pix.R &^ 3) | (s[c] >> 6) - g := (pix.G &^ 3) | (s[c]<<2)>>6 - b := (pix.B &^ 3) | (s[c]<<4)>>6 - a := (pix.A &^ 3) | (s[c]<<6)>>6 + if c < l { + r := (pix.R &^ 3) | (arr[c] >> 6) + g := (pix.G &^ 3) | (arr[c]<<2)>>6 + b := (pix.B &^ 3) | (arr[c]<<4)>>6 + a := (pix.A &^ 3) | (arr[c]<<6)>>6 n.Set(j, i, color.NRGBA{r, g, b, a}) c++ - } else if c == len(s) { - n.Set(j, i, color.NRGBA{ - pix.R &^ 3, - pix.G &^ 3, - pix.B &^ 3, - pix.A &^ 3, - }) - c++ } else { n.Set(j, i, pix) } @@ -81,7 +102,7 @@ func errf(err error) { func main() { flag.StringVar(&inFile, "in", "-", "input file (default: stdin)") flag.StringVar(&outFile, "out", "-", "output file, always png (default: stdout)") - flag.StringVar(&secret, "secret", "", "secret to hide") + flag.StringVar(&secret, "secret", "", "secret file to hide") flag.Parse() var ( err error @@ -94,12 +115,22 @@ func main() { errf(err) } } + if outFile != "-" { + out, err = os.OpenFile(outFile, os.O_RDWR|os.O_CREATE, 0644) + if err != nil { + errf(err) + } + } p, _, err := image.Decode(in) if err != nil { errf(err) } if secret != "" { - n, err := seal(p, []byte(secret)) + b, err := ioutil.ReadFile(secret) + if err != nil { + errf(err) + } + n, err := seal(p, b) if err != nil { errf(err) } diff --git a/testdata/lenna_256.png b/testdata/lenna_256.png new file mode 100644 index 0000000..1b3f750 Binary files /dev/null and b/testdata/lenna_256.png differ diff --git a/testdata/stego.png b/testdata/stego.png index c10bb28..da42f05 100644 Binary files a/testdata/stego.png and b/testdata/stego.png differ