├── go.mod ├── examples ├── stegosaurus.jpg ├── stegosaurus.png ├── encoded_stegosaurus.png ├── encoded_stegosaurus_from_jpg.png ├── message.txt ├── README.md └── stego.go ├── LICENSE ├── .github └── workflows │ └── test.yaml ├── README.md ├── steganography_test.go └── steganography.go /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/auyer/steganography 2 | 3 | go 1.12 4 | -------------------------------------------------------------------------------- /examples/stegosaurus.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auyer/steganography/HEAD/examples/stegosaurus.jpg -------------------------------------------------------------------------------- /examples/stegosaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auyer/steganography/HEAD/examples/stegosaurus.png -------------------------------------------------------------------------------- /examples/encoded_stegosaurus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auyer/steganography/HEAD/examples/encoded_stegosaurus.png -------------------------------------------------------------------------------- /examples/encoded_stegosaurus_from_jpg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/auyer/steganography/HEAD/examples/encoded_stegosaurus_from_jpg.png -------------------------------------------------------------------------------- /examples/message.txt: -------------------------------------------------------------------------------- 1 | The quadrupedal Stegosaurus is one of the most easily identifiable dinosaur genera, due to the distinctive double row of kite-shaped plates rising vertically along the rounded back and the two pairs of long spikes extending horizontally near the end of the tail. Although large individuals could grow up to 9 m (29.5 ft) in length[4] and 5.3 to 7 metric tons (5.8 to 7.7 short tons) in weight,[5][6] the various species of Stegosaurus were dwarfed by contemporaries, the giant sauropods. Some form of armor appears to have been necessary, as Stegosaurus species coexisted with large predatory theropod dinosaurs, such as Allosaurus and Ceratosaurus. -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Example usage: 2 | 3 | Decoding message: go run stego.go -d -i encoded_stegosaurus.png 4 | 5 | Encoding message: go run stego.go -e -i stegosaurus.png -mi message.txt -o encoded_stegosaurus.png 6 | 7 | Usage stego.go 8 | 9 | -help Will show this message below 10 | 11 | -d Specifies if you would like to decode a message from a given PNG file 12 | 13 | -e Specifies if you would like to encode a message to a given PNG file 14 | 15 | -i string Path to the the input image 16 | 17 | -mi string Path to the message input file 18 | 19 | -mo string Path to the message output file 20 | 21 | -o string Path to the the output image (default "encoded.png") -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Rafael Passos (@Auyer) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Unit Tests & Coverage 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | go: [ 13 | 'stable', 14 | '^1.24', 15 | '^1.23', 16 | '^1.22', 17 | '^1.21', 18 | '^1.20', 19 | '^1.19', 20 | '~1.18', 21 | '~1.17', 22 | '~1.16', 23 | '~1.15', 24 | '~1.14', 25 | '~1.13', 26 | '~1.12' 27 | ] 28 | name: Go ${{ matrix.go }} sample 29 | steps: 30 | - name: Checkout repo 31 | uses: actions/checkout@v4 32 | 33 | - name: Install Go 34 | uses: actions/setup-go@v5 35 | with: 36 | go-version: ${{ matrix.go }} 37 | 38 | - name: Run Race tests 39 | run: | 40 | go test --race 41 | 42 | coverage: 43 | runs-on: ubuntu-latest 44 | steps: 45 | - name: Checkout repo 46 | uses: actions/checkout@v4 47 | 48 | - name: Install Go 49 | uses: actions/setup-go@v5 50 | with: 51 | go-version: "stable" 52 | 53 | - name: Run tests with Coverage report 54 | run: | 55 | go test -coverprofile=coverage.txt -covermode=atomic 56 | 57 | - name: Upload coverage report 58 | uses: codecov/codecov-action@v5 59 | with: 60 | file: ./coverage.txt 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Steganography Lib 2 | 3 | [![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](http://godoc.org/github.com/auyer/steganography) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/auyer/steganography)](https://goreportcard.com/report/github.com/auyer/steganography) 5 | [![LICENSE MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://img.shields.io/badge/license-MIT-brightgreen.svg) 6 | [![Actions CI](https://github.com/auyer/steganography/actions/workflows/test.yaml/badge.svg)](https://github.com/auyer/steganography/actions) 7 | [![codecov](https://codecov.io/gh/auyer/steganography/branch/main/graph/badge.svg)](https://codecov.io/gh/auyer/steganography) 8 | [![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) 9 | 10 | Steganography is a library written in Pure go to allow simple LSB steganography on images. It is capable of both encoding and decoding images. It can store files of any format. 11 | This library is inspired by [Stego by EthanWelsh](https://github.com/EthanWelsh/Stego), a command line utility with the same purpose. 12 | 13 | ## Installation 14 | ```go 15 | go get -u github.com/auyer/steganography 16 | ``` 17 | 18 | ## Demonstration 19 | 20 | | Original |Encoded | 21 | | -------------------- | ------------------| 22 | | ![Original File](examples/stegosaurus.png) | ![Encoded File](examples/encoded_stegosaurus.png) 23 | 24 | The second image contains the first paragraph of the description of a stegosaurus on [Wikipedia](https://en.wikipedia.org/wiki/Stegosaurus), also available in [examples/message.txt](https://raw.githubusercontent.com/auyer/steganography/master/examples/message.txt) as an example. 25 | 26 | ------ 27 | Getting Started 28 | ------ 29 | ```go 30 | package main 31 | import ( 32 | "bufio" 33 | "image/png" 34 | "io/ioutil" 35 | 36 | "github.com/auyer/steganography" 37 | ) 38 | ``` 39 | 40 | Encode 41 | ------ 42 | Write mode is used to take a message and embed it into an image file using LSB steganography in order to produce a secret image file that will contain your message. 43 | 44 | Note that the minimum image size is 24 pixels for one byte. For each additional byte, it is necessary 3 more pixels. 45 | 46 | ```go 47 | inFile, _ := os.Open("input_file.png") // opening file 48 | reader := bufio.NewReader(inFile) // buffer reader 49 | img, _ := png.Decode(reader) // decoding to golang's image.Image 50 | 51 | w := new(bytes.Buffer) // buffer that will recieve the results 52 | err := steganography.Encode(w, img, []byte("message")) // Encode the message into the image 53 | if err != nil { 54 | log.Printf("Error Encoding file %v", err) 55 | return 56 | } 57 | outFile, _ := os.Create("out_file.png") // create file 58 | w.WriteTo(outFile) // write buffer to it 59 | outFile.Close() 60 | ``` 61 | note: all error checks were removed for brevity, but they should be included. 62 | 63 | Size of Message 64 | ------ 65 | Length mode can be used in order to preform a preliminary check on the carrier image in order to deduce how large of a file it can store. 66 | 67 | ```go 68 | sizeOfMessage := steganography.GetMessageSizeFromImage(img) // retrieves the size of the encoded message 69 | ``` 70 | 71 | Decode 72 | ----- 73 | Read mode is used to read an image that has been encoded using LSB steganography, and extract the hidden message from that image. 74 | 75 | ```go 76 | inFile, _ := os.Open(encodedInputFile) // opening file 77 | defer inFile.Close() 78 | 79 | reader := bufio.NewReader(inFile) // buffer reader 80 | img, _ := png.Decode(reader) // decoding to golang's image.Image 81 | 82 | sizeOfMessage := steganography.GetMessageSizeFromImage(img) // retrieving message size to decode in the next line 83 | 84 | msg := steganography.Decode(sizeOfMessage, img) // decoding the message from the file 85 | fmt.Println(string(msg)) 86 | 87 | ``` 88 | note: all error checks were removed for brevity, but they should be included. 89 | 90 | Complete Example 91 | ------ 92 | For a complete example, see the [examples/stego.go](examples/stego.go) file. It is a command line app based on the original fork of this repository, but modified to use the Steganography library. 93 | 94 | ----- 95 | ### Attributions 96 | - Stegosaurus Picture By Matt Martyniuk - Own work, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=42215661 97 | -------------------------------------------------------------------------------- /examples/stego.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "flag" 7 | "fmt" 8 | "image" 9 | "log" 10 | "os" 11 | 12 | "github.com/auyer/steganography" 13 | ) 14 | 15 | var pictureInputFile string 16 | var pictureOutputFile string 17 | var messageInputFile string 18 | var messageOutputFile string 19 | var decode bool 20 | var encode bool 21 | var help bool 22 | 23 | // init creates the necessary flags to run program from the command line 24 | func init() { 25 | 26 | flag.BoolVar(&decode, "d", false, "Specifies if you would like to decode a message from a given PNG file") 27 | flag.BoolVar(&encode, "e", false, "Specifies if you would like to encode a message to a given PNG file") 28 | 29 | flag.StringVar(&pictureInputFile, "i", "", "Path to the the input image") 30 | flag.StringVar(&pictureOutputFile, "o", "encoded.png", "Path to the the output image") 31 | 32 | flag.StringVar(&messageInputFile, "mi", "", "Path to the message input file") 33 | flag.StringVar(&messageOutputFile, "mo", "", "Path to the message output file") 34 | 35 | flag.BoolVar(&help, "help", false, "Help") 36 | 37 | flag.Parse() 38 | } 39 | 40 | // 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. 41 | func OpenImageFromPath(filename string) (image.Image, error) { 42 | inFile, err := os.Open(filename) 43 | if err != nil { 44 | return nil, err 45 | } 46 | defer inFile.Close() 47 | reader := bufio.NewReader(inFile) 48 | img, _, err := image.Decode(reader) 49 | if err != nil { 50 | return nil, err 51 | } 52 | return img, nil 53 | } 54 | 55 | func main() { 56 | if encode { 57 | message, err := os.ReadFile(messageInputFile) // Read the message from the message file (alternative to os.Open ) 58 | if err != nil { 59 | print("Error reading from file!!!") 60 | return 61 | } 62 | 63 | inFile, err := os.Open(pictureInputFile) // Opens input file provided in the flags 64 | if err != nil { 65 | log.Fatalf("Error opening file %s: %v", pictureInputFile, err) 66 | } 67 | defer inFile.Close() 68 | 69 | reader := bufio.NewReader(inFile) // Reads binary data from picture file 70 | img, _, err := image.Decode(reader) 71 | if err != nil { 72 | log.Fatalf("Error opening file %v", err) 73 | } 74 | encodedImg := new(bytes.Buffer) 75 | err = steganography.Encode(encodedImg, img, message) // Calls library and Encodes the message into a new buffer 76 | if err != nil { 77 | log.Fatalf("Error encoding message into file %v", err) 78 | } 79 | outFile, err := os.Create(pictureOutputFile) // Creates file to write the message into 80 | if err != nil { 81 | log.Fatalf("Error creating file %s: %v", pictureOutputFile, err) 82 | } 83 | bufio.NewWriter(outFile).Write(encodedImg.Bytes()) // writes file to disk 84 | 85 | } else if decode { 86 | 87 | inFile, err := os.Open(pictureInputFile) // Opens input file provided in the flags 88 | if err != nil { 89 | log.Fatalf("Error opening file %s: %v", pictureInputFile, err) 90 | } 91 | defer inFile.Close() 92 | 93 | reader := bufio.NewReader(inFile) 94 | img, _, err := image.Decode(reader) 95 | if err != nil { 96 | log.Fatal("error decoding file", img) 97 | } 98 | 99 | sizeOfMessage := steganography.GetMessageSizeFromImage(img) // Uses the library to check the message size 100 | 101 | msg := steganography.Decode(sizeOfMessage, img) // Read the message from the picture file 102 | 103 | // if the user specifies a location to write the message to... 104 | if messageOutputFile != "" { 105 | err := os.WriteFile(messageOutputFile, msg, 0644) // write the message to the given output file 106 | 107 | if err != nil { 108 | fmt.Println("There was an error writing to file: ", messageOutputFile) 109 | } 110 | } else { // otherwise, print the message to STDOUT 111 | for i := range msg { 112 | fmt.Printf("%c", msg[i]) 113 | } 114 | } 115 | } else { 116 | fmt.Println("How to use this script:") 117 | fmt.Println("-i: the input image to encode in / decode from") 118 | fmt.Println() 119 | fmt.Println("-e: take a message and encodes it into a specified location") 120 | fmt.Println("-mi: input message to for the encoding option (ENCODING ONLY)") 121 | fmt.Println("-o: where you would like to store the encodeded image (ENCODING ONLY)") 122 | fmt.Println("\t+ EX: ./main -e -i plain.png -mi message.txt -o secret.png") 123 | fmt.Println() 124 | fmt.Println("-d: take a picture and decodes the message from it") 125 | fmt.Println("-mo: output message. Lempty for STDIO (DECODING ONLY)") 126 | fmt.Println("\t+ EX: ./stego -d -i secret.png -mo secret.txt") 127 | return 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /steganography_test.go: -------------------------------------------------------------------------------- 1 | package steganography 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "image" 7 | "image/color" 8 | "image/jpeg" 9 | "log" 10 | "os" 11 | "testing" 12 | ) 13 | 14 | var rawInputFilePng = "./examples/stegosaurus.png" 15 | var rawInputFileJpg = "./examples/stegosaurus.jpg" 16 | var encodedInputFilePng = "./examples/encoded_stegosaurus.png" 17 | var encodedInputFileJpg = "./examples/encoded_stegosaurus_from_jpg.png" 18 | 19 | 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} 20 | 21 | func TestEncodeFromPngFile(t *testing.T) { 22 | 23 | inFile, err := os.Open(rawInputFilePng) 24 | if err != nil { 25 | log.Printf("Error opening file %s: %v", rawInputFilePng, err) 26 | t.FailNow() 27 | 28 | } 29 | defer inFile.Close() 30 | 31 | reader := bufio.NewReader(inFile) 32 | img, _, err := image.Decode(reader) 33 | if err != nil { 34 | log.Printf("Error decoding. %v", err) 35 | t.FailNow() 36 | } 37 | w := new(bytes.Buffer) 38 | err = Encode(w, img, bitmessage) // Encode the message into the image file 39 | if err != nil { 40 | log.Printf("Error Encoding file %v", err) 41 | t.FailNow() 42 | } 43 | 44 | outFile, err := os.Create(encodedInputFilePng) 45 | if err != nil { 46 | log.Printf("Error creating file %s: %v", encodedInputFilePng, err) 47 | t.FailNow() 48 | } 49 | 50 | _, err = w.WriteTo(outFile) 51 | if err != nil { 52 | log.Printf("Error writing file %s: %v", encodedInputFilePng, err) 53 | t.FailNow() 54 | } 55 | defer outFile.Close() 56 | } 57 | 58 | func TestEncodeFromJpgFile(t *testing.T) { 59 | 60 | inFile, err := os.Open(rawInputFileJpg) 61 | if err != nil { 62 | log.Printf("Error opening file %s: %v", rawInputFileJpg, err) 63 | t.FailNow() 64 | } 65 | 66 | defer inFile.Close() 67 | 68 | reader := bufio.NewReader(inFile) 69 | img, err := jpeg.Decode(reader) 70 | if err != nil { 71 | log.Printf("Error decoding. %v", err) 72 | t.FailNow() 73 | } 74 | 75 | w := new(bytes.Buffer) 76 | err = Encode(w, img, bitmessage) // Encode the message into the image file 77 | if err != nil { 78 | log.Printf("Error Encoding file %v", err) 79 | t.FailNow() 80 | } 81 | 82 | outFile, err := os.Create(encodedInputFileJpg) 83 | if err != nil { 84 | log.Printf("Error creating file %s: %v", encodedInputFileJpg, err) 85 | t.FailNow() 86 | } 87 | 88 | _, err = w.WriteTo(outFile) 89 | if err != nil { 90 | log.Printf("Error writing file %s: %v", encodedInputFilePng, err) 91 | t.FailNow() 92 | } 93 | defer outFile.Close() 94 | } 95 | 96 | func TestDecodeFromPngFile(t *testing.T) { 97 | inFile, err := os.Open(encodedInputFilePng) 98 | if err != nil { 99 | log.Printf("Error opening file %s: %v", encodedInputFilePng, err) 100 | t.FailNow() 101 | } 102 | defer inFile.Close() 103 | 104 | reader := bufio.NewReader(inFile) 105 | img, _, err := image.Decode(reader) 106 | if err != nil { 107 | log.Print("Error decoding file") 108 | t.FailNow() 109 | } 110 | 111 | sizeOfMessage := GetMessageSizeFromImage(img) 112 | 113 | msg := Decode(sizeOfMessage, img) // Read the message from the picture file 114 | 115 | if !bytes.Equal(msg, bitmessage) { 116 | log.Print("messages dont match:") 117 | log.Println(string(msg)) 118 | t.FailNow() 119 | } 120 | } 121 | 122 | func TestEncodeDecodeGeneratedSmallImage(t *testing.T) { 123 | // Creating image 124 | width := 30 125 | height := 1 126 | 127 | upLeft := image.Point{0, 0} 128 | lowRight := image.Point{width, height} 129 | 130 | newimg := image.NewNRGBA(image.Rectangle{upLeft, lowRight}) 131 | 132 | // Set color for each pixel. 133 | for x := 0; x < width; x++ { 134 | for y := 0; y < height; y++ { 135 | newimg.Set(x, y, color.White) 136 | } 137 | } 138 | 139 | w := new(bytes.Buffer) 140 | err := EncodeNRGBA(w, newimg, []uint8{84, 84, 84}) // Encode the message into the image file 141 | if err != nil { 142 | log.Printf("Error Encoding file %v", err) 143 | t.FailNow() 144 | 145 | } 146 | decodeImg, _, err := image.Decode(w) 147 | if err != nil { 148 | log.Println("Failed to Decode Image") 149 | t.FailNow() 150 | } 151 | 152 | sizeOfMessage := GetMessageSizeFromImage(decodeImg) 153 | 154 | msg := Decode(sizeOfMessage, decodeImg) // Read the message from the picture file 155 | 156 | // otherwise, print the message to STDOUT 157 | 158 | if !bytes.Equal(msg, []uint8{84, 84, 84}) { 159 | log.Print("messages dont match:") 160 | log.Println(string(msg)) 161 | t.FailNow() 162 | } 163 | } 164 | func TestSmalImage(t *testing.T) { 165 | 166 | miniImage := image.Image(image.NewNRGBA(image.Rectangle{image.Point{0, 0}, image.Point{18, 1}})) 167 | log.Print(MaxEncodeSize(miniImage)) 168 | if MaxEncodeSize(miniImage) > 0 { 169 | log.Printf("Uncaught small image size") 170 | t.FailNow() 171 | } 172 | 173 | miniImage = image.Image(image.NewNRGBA(image.Rectangle{image.Point{0, 0}, image.Point{23, 1}})) 174 | 175 | if MaxEncodeSize(miniImage) != 4 { 176 | log.Printf("Uncaught minimal image size") 177 | t.FailNow() 178 | } 179 | } 180 | 181 | func TestMessageTooLarge(t *testing.T) { 182 | 183 | miniImage := image.Image(image.NewNRGBA(image.Rectangle{image.Point{0, 0}, image.Point{24, 1}})) 184 | w := new(bytes.Buffer) 185 | err := Encode(w, miniImage, bitmessage) // Encode the message into the image file 186 | if err == nil { 187 | log.Printf("Uncaught error: message too large for image") 188 | t.FailNow() 189 | 190 | } 191 | 192 | } 193 | 194 | func TestEncodeDecodeGenerateColors(t *testing.T) { 195 | // Creating image 196 | width := 50 197 | height := 50 198 | colors := []color.Color{ 199 | color.NRGBA{R: 255, G: 0, B: 0, A: 255}, 200 | color.NRGBA{R: 0, G: 255, B: 0, A: 255}, 201 | color.NRGBA{R: 0, G: 0, B: 255, A: 255}, 202 | color.NRGBA{R: 0, G: 0, B: 0, A: 128}, 203 | color.NRGBA{R: 255, G: 255, B: 255, A: 1}, 204 | color.NRGBA{R: 255, G: 255, B: 255, A: 128}, 205 | color.NRGBA{R: 127, G: 127, B: 127, A: 255}, 206 | color.NRGBA{R: 255, G: 255, B: 255, A: 255}, 207 | } 208 | 209 | for _, color := range colors { 210 | 211 | upLeft := image.Point{0, 0} 212 | lowRight := image.Point{width, height} 213 | 214 | newimg := image.NewNRGBA(image.Rectangle{upLeft, lowRight}) 215 | 216 | // Set color for each pixel. 217 | for x := 0; x < width; x++ { 218 | for y := 0; y < height; y++ { 219 | newimg.Set(x, y, color) 220 | } 221 | } 222 | 223 | w := new(bytes.Buffer) 224 | err := EncodeNRGBA(w, newimg, bitmessage) // Encode the message into the image file 225 | if err != nil { 226 | log.Printf("Error Encoding file %v", err) 227 | t.FailNow() 228 | 229 | } 230 | decodeImg, _, err := image.Decode(w) 231 | if err != nil { 232 | log.Println("Failed to Decode Image") 233 | t.FailNow() 234 | } 235 | 236 | sizeOfMessage := GetMessageSizeFromImage(decodeImg) 237 | 238 | msg := Decode(sizeOfMessage, decodeImg) // Read the message from the picture file 239 | 240 | // otherwise, print the message to STDOUT 241 | 242 | if !bytes.Equal(msg, bitmessage) { 243 | log.Printf("Color case: %v", color) 244 | log.Print("messages dont match:") 245 | log.Println(string(msg)) 246 | t.FailNow() 247 | } 248 | } 249 | } 250 | -------------------------------------------------------------------------------- /steganography.go: -------------------------------------------------------------------------------- 1 | // Package steganography is a library that provides functions to execute steganography encoding and decoding in a given image. It is also able to check the maximum encoding size, and the size of an encoded message. 2 | package steganography 3 | 4 | import ( 5 | "bytes" 6 | "errors" 7 | "image" 8 | "image/color" 9 | "image/draw" 10 | "image/png" 11 | ) 12 | 13 | // EncodeNRGBA encodes a given string into the input image using least significant bit encryption (LSB steganography) 14 | // The minnimum image size is 24 pixels for one byte. For each additional byte, it is necessary 3 more pixels. 15 | /* 16 | Input: 17 | writeBuffer *bytes.Buffer : the destination of the encoded image bytes 18 | pictureInputFile image.NRGBA : image data used in encoding 19 | message []byte : byte slice of the message to be encoded 20 | Output: 21 | bytes buffer ( io.writter ) to create file, or send data. 22 | */ 23 | func EncodeNRGBA(writeBuffer *bytes.Buffer, rgbImage *image.NRGBA, message []byte) error { 24 | 25 | var messageLength = uint32(len(message)) 26 | 27 | var width = rgbImage.Bounds().Dx() 28 | var height = rgbImage.Bounds().Dy() 29 | var c color.NRGBA 30 | var bit byte 31 | var ok bool 32 | //var encodedImage image.Image 33 | if MaxEncodeSize(rgbImage) < messageLength+4 { 34 | return errors.New("message too large for image") 35 | } 36 | 37 | one, two, three, four := splitToBytes(messageLength) 38 | 39 | message = append([]byte{four}, message...) 40 | message = append([]byte{three}, message...) 41 | message = append([]byte{two}, message...) 42 | message = append([]byte{one}, message...) 43 | 44 | ch := make(chan byte, 100) 45 | 46 | go getNextBitFromString(message, ch) 47 | 48 | for x := 0; x < width; x++ { 49 | for y := 0; y < height; y++ { 50 | 51 | c = rgbImage.NRGBAAt(x, y) // get the color at this pixel 52 | 53 | /* RED */ 54 | bit, ok = <-ch 55 | if !ok { // if we don't have any more bits left in our message 56 | 57 | rgbImage.SetNRGBA(x, y, c) 58 | break 59 | } 60 | setLSB(&c.R, bit) 61 | 62 | /* GREEN */ 63 | bit, ok = <-ch 64 | if !ok { 65 | rgbImage.SetNRGBA(x, y, c) 66 | break 67 | } 68 | setLSB(&c.G, bit) 69 | 70 | /* BLUE */ 71 | bit, ok = <-ch 72 | if !ok { 73 | rgbImage.SetNRGBA(x, y, c) 74 | break 75 | } 76 | setLSB(&c.B, bit) 77 | 78 | rgbImage.SetNRGBA(x, y, c) 79 | } 80 | } 81 | 82 | err := png.Encode(writeBuffer, rgbImage) 83 | return err 84 | } 85 | 86 | // Encode encodes a given string into the input image using least significant bit encryption (LSB steganography) 87 | // The minnimum image size is 23 pixels 88 | // It wraps EncodeNRGBA making the conversion from image.Image to image.NRGBA 89 | /* 90 | Input: 91 | writeBuffer *bytes.Buffer : the destination of the encoded image bytes 92 | message []byte : byte slice of the message to be encoded 93 | pictureInputFile image.Image : image data used in encoding 94 | Output: 95 | bytes buffer ( io.writter ) to create file, or send data. 96 | */ 97 | func Encode(writeBuffer *bytes.Buffer, pictureInputFile image.Image, message []byte) error { 98 | 99 | rgbImage := imageToNRGBA(pictureInputFile) 100 | 101 | return EncodeNRGBA(writeBuffer, rgbImage, message) 102 | 103 | } 104 | 105 | // decodeNRGBA gets messages from pictures using LSB steganography, decode the message from the picture and return it as a sequence of bytes 106 | /* 107 | Input: 108 | startOffset uint32 : number of bytes used to declare size of message 109 | msgLen uint32 : size of the message to be decoded 110 | pictureInputFile image.NRGBA : image data used in decoding 111 | Output: 112 | message []byte decoded from image 113 | */ 114 | func decodeNRGBA(startOffset uint32, msgLen uint32, rgbImage *image.NRGBA) (message []byte) { 115 | 116 | var byteIndex uint32 117 | var bitIndex uint32 118 | 119 | width := rgbImage.Bounds().Dx() 120 | height := rgbImage.Bounds().Dy() 121 | 122 | var c color.NRGBA 123 | var lsb byte 124 | 125 | message = append(message, 0) 126 | 127 | // iterate through every pixel in the image and stitch together the message bit by bit 128 | for x := 0; x < width; x++ { 129 | for y := 0; y < height; y++ { 130 | 131 | c = rgbImage.NRGBAAt(x, y) // get the color of the pixel 132 | 133 | /* RED */ 134 | lsb = getLSB(c.R) // get the least significant bit from the red component of this pixel 135 | message[byteIndex] = setBitInByte(message[byteIndex], bitIndex, lsb) // add this bit to the message 136 | bitIndex++ 137 | 138 | if bitIndex > 7 { // when we have filled up a byte, move on to the next byte 139 | bitIndex = 0 140 | byteIndex++ 141 | 142 | if byteIndex >= msgLen+startOffset { 143 | return message[startOffset : msgLen+startOffset] 144 | } 145 | 146 | message = append(message, 0) 147 | } 148 | 149 | /* GREEN */ 150 | lsb = getLSB(c.G) 151 | message[byteIndex] = setBitInByte(message[byteIndex], bitIndex, lsb) 152 | bitIndex++ 153 | 154 | if bitIndex > 7 { 155 | 156 | bitIndex = 0 157 | byteIndex++ 158 | 159 | if byteIndex >= msgLen+startOffset { 160 | return message[startOffset : msgLen+startOffset] 161 | } 162 | 163 | message = append(message, 0) 164 | } 165 | 166 | /* BLUE */ 167 | lsb = getLSB(c.B) 168 | message[byteIndex] = setBitInByte(message[byteIndex], bitIndex, lsb) 169 | bitIndex++ 170 | 171 | if bitIndex > 7 { 172 | bitIndex = 0 173 | byteIndex++ 174 | 175 | if byteIndex >= msgLen+startOffset { 176 | return message[startOffset : msgLen+startOffset] 177 | } 178 | 179 | message = append(message, 0) 180 | } 181 | } 182 | } 183 | return 184 | } 185 | 186 | // decode gets messages from pictures using LSB steganography, decode the message from the picture and return it as a sequence of bytes 187 | // It wraps EncodeNRGBA making the conversion from image.Image to image.NRGBA 188 | /* 189 | Input: 190 | startOffset uint32 : number of bytes used to declare size of message 191 | msgLen uint32 : size of the message to be decoded 192 | pictureInputFile image.Image : image data used in decoding 193 | Output: 194 | message []byte decoded from image 195 | */ 196 | func decode(startOffset uint32, msgLen uint32, pictureInputFile image.Image) (message []byte) { 197 | 198 | rgbImage := imageToNRGBA(pictureInputFile) 199 | return decodeNRGBA(startOffset, msgLen, rgbImage) 200 | 201 | } 202 | 203 | // Decode gets messages from pictures using LSB steganography, decode the message from the picture and return it as a sequence of bytes 204 | // It wraps EncodeNRGBA making the conversion from image.Image to image.NRGBA 205 | /* 206 | Input: 207 | msgLen uint32 : size of the message to be decoded 208 | pictureInputFile image.Image : image data used in decoding 209 | Output: 210 | message []byte decoded from image 211 | */ 212 | func Decode(msgLen uint32, pictureInputFile image.Image) (message []byte) { 213 | return decode(4, msgLen, pictureInputFile) // the offset of 4 skips the "header" where message length is defined 214 | 215 | } 216 | 217 | // MaxEncodeSize given an image will find how many bytes can be stored in that image using least significant bit encoding 218 | // ((width * height * 3) / 8 ) - 4 219 | // The result must be at least 4, 220 | func MaxEncodeSize(img image.Image) uint32 { 221 | width := img.Bounds().Dx() 222 | height := img.Bounds().Dy() 223 | eval := ((width * height * 3) / 8) - 4 224 | if eval < 4 { 225 | eval = 0 226 | } 227 | return uint32(eval) 228 | } 229 | 230 | // GetMessageSizeFromImage gets the size of the message from the first four bytes encoded in the image 231 | func GetMessageSizeFromImage(pictureInputFile image.Image) (size uint32) { 232 | 233 | sizeAsByteArray := decode(0, 4, pictureInputFile) 234 | size = combineToInt(sizeAsByteArray[0], sizeAsByteArray[1], sizeAsByteArray[2], sizeAsByteArray[3]) 235 | return 236 | } 237 | 238 | // getNextBitFromString each call will return the next subsequent bit in the string 239 | func getNextBitFromString(byteArray []byte, ch chan byte) { 240 | 241 | var offsetInBytes int 242 | var offsetInBitsIntoByte int 243 | var choiceByte byte 244 | 245 | lenOfString := len(byteArray) 246 | 247 | for { 248 | if offsetInBytes >= lenOfString { 249 | close(ch) 250 | return 251 | } 252 | 253 | choiceByte = byteArray[offsetInBytes] 254 | ch <- getBitFromByte(choiceByte, offsetInBitsIntoByte) 255 | 256 | offsetInBitsIntoByte++ 257 | 258 | if offsetInBitsIntoByte >= 8 { 259 | offsetInBitsIntoByte = 0 260 | offsetInBytes++ 261 | } 262 | } 263 | } 264 | 265 | // getLSB given a byte, will return the least significant bit of that byte 266 | func getLSB(b byte) byte { 267 | if b%2 == 0 { 268 | return 0 269 | } 270 | return 1 271 | } 272 | 273 | // setLSB given a byte will set that byte's least significant bit to a given value (where true is 1 and false is 0) 274 | func setLSB(b *byte, bit byte) { 275 | if bit == 1 { 276 | *b = *b | 1 277 | } else if bit == 0 { 278 | var mask byte = 0xFE 279 | *b = *b & mask 280 | } 281 | } 282 | 283 | // getBitFromByte given a bit will return a bit from that byte 284 | func getBitFromByte(b byte, indexInByte int) byte { 285 | b = b << uint(indexInByte) 286 | var mask byte = 0x80 287 | 288 | var bit = mask & b 289 | 290 | if bit == 128 { 291 | return 1 292 | } 293 | return 0 294 | } 295 | 296 | // setBitInByte sets a specific bit in a byte to a given value and returns the new byte 297 | func setBitInByte(b byte, indexInByte uint32, bit byte) byte { 298 | var mask byte = 0x80 299 | mask = mask >> uint(indexInByte) 300 | 301 | if bit == 0 { 302 | mask = ^mask 303 | b = b & mask 304 | } else if bit == 1 { 305 | b = b | mask 306 | } 307 | return b 308 | } 309 | 310 | // combineToInt given four bytes, will return the 32 bit unsigned integer which is the composition of those four bytes (one is MSB) 311 | func combineToInt(one, two, three, four byte) (ret uint32) { 312 | ret = uint32(one) 313 | ret = ret << 8 314 | ret = ret | uint32(two) 315 | ret = ret << 8 316 | ret = ret | uint32(three) 317 | ret = ret << 8 318 | ret = ret | uint32(four) 319 | return 320 | } 321 | 322 | // splitToBytes given an unsigned integer, will split this integer into its four bytes 323 | func splitToBytes(x uint32) (one, two, three, four byte) { 324 | one = byte(x >> 24) 325 | var mask uint32 = 255 326 | 327 | two = byte((x >> 16) & mask) 328 | three = byte((x >> 8) & mask) 329 | four = byte(x & mask) 330 | return 331 | } 332 | 333 | // imageToNRGBA converts image.Image to image.NRGBA 334 | func imageToNRGBA(src image.Image) *image.NRGBA { 335 | bounds := src.Bounds() 336 | 337 | var m *image.NRGBA 338 | var width, height int 339 | 340 | width = bounds.Dx() 341 | height = bounds.Dy() 342 | 343 | m = image.NewNRGBA(image.Rect(0, 0, width, height)) 344 | 345 | draw.Draw(m, m.Bounds(), src, bounds.Min, draw.Src) 346 | return m 347 | } 348 | --------------------------------------------------------------------------------