Stegano is a Go library that provides tools for embedding and extracting data within images using steganographic techniques. The library currently supports any image that support the image.Iamge type in go and includes ZSTD compression to optimize data storage within images.
- Features
- What is Steganography?
- Installation
- Usage
- Working with Images
- Advanced Options
- Notes
- Benchmarks
- Future Improvements
- Multi-Image Support: Supports all images compatible with the
image.Image
type in Go. - Data Compression: Utilizes ZSTD compression for efficient embedding.
- Capacity Calculation: Calculates the maximum data capacity of an image for embedding.
- Variable Depth Encoding: Embeds bits up to and including the specified index.
- Concurrency: Increases speed at the cost of memory usage.
- Embed at Certain Depth: Embeds 1 bit per channel at the specified index.
- Save Image: Efficient PNG encoding.
- Decode Image: Helper function to decode images into the
image.Image
type.
Steganography is the practice of hiding data inside other, non-suspicious data in such a way that it is imperceptible to an observer. This is different from encryption, where data is scrambled to make it unreadable, but still detectable. In steganography, the goal is to hide the data so that it goes unnoticed.
Steganography works by altering the least significant bits (LSBs) of the carrier medium, such as an image or audio file. These bits typically do not significantly affect the quality of the medium, making them ideal for storing hidden information. The process involves:
- Embedding the Message: A secret message is embedded into the carrier medium (for example, an image) by altering its least significant bits.
- Extraction: To retrieve the message, the algorithm extracts the hidden data from the least significant bits without making the change noticeable to the human eye or ear.
For example, in an image, each pixel typically has three color channels (Red, Green, Blue). Data can be embedded in the least significant bits of each channel. When done correctly, the changes are so subtle that they are not visible to the human eye, but the hidden information can still be extracted.
Below is an example of how steganography works with an image. The original image, embedded data, and resulting image appear unchanged to the human eye.
Original Image | Embedded Data | Resulting Image |
---|---|---|
Hello, World! (Encoded in LSBs) |
In this example, the message "Hello, World!" is hidden within the image, but the image looks the same as the original one to the naked eye.
To use this library, install it and its dependencies. Add the package to your Go project by running the following command:
go get github.com/scott-mescudi/stegano@latest
import (
"github.com/scott-mescudi/stegano"
)
This function demonstrates how to embed a message into a cover image.
func main() {
coverFile, err := stegano.Decodeimage("coverimage.png")
if err != nil {
log.Fatalln(err)
}
// Create an EmbedHandler instance for managing the embedding process.
embedder := stegano.NewEmbedHandler()
// Encode and save the message in the cover image.
// The settings used are:
// - Minimum bit depth for embedding.
// - PNG output format by default.
// - Optional compression enabled.
// This library by default embeds data up to and including the index.
// For example, if embedding at depth 3:
// - A binary value of 0x11111111 will become 0x11110000.
// to embed into the LSB use a bitdepth of 0 or stegano.MinBitDepth.
err = embedder.EncodeAndSave(coverFile, []byte("Hello, World!"), stegano.MinBitDepth, stegano.DefaultpngOutputFile, true)
if err != nil {
log.Fatalln(err)
}
}
Note: You can also enable concurrency for embedding using
NewEmbedHandlerWithConcurrency(numGoroutines)
for better performance with large files.
This function demonstrates how to retrieve a hidden message from an image.
func main() {
coverFile, err := stegano.Decodeimage("embeddedimage.png")
if err != nil {
log.Fatalln(err)
}
// Create an ExtractHandler instance for managing the extraction process.
extractor := stegano.NewExtractHandler()
// Decode the message from the image.
// Ensure the following settings match those used during embedding:
// - Minimum bit depth.
// - Optional compression enabled/disabled.
data, err := extractor.Decode(coverFile, stegano.MinBitDepth, true)
if err != nil {
log.Fatalln(err)
}
// Print the extracted message.
fmt.Println(string(data))
}
Disclaimer: Ensure that the bit depth, compression settings, and other parameters used during embedding are known, as incorrect settings may result in corrupted or irretrievable data.
This method embeds data without the use of automatic compression.
func main() {
coverFile, err := stegano.Decodeimage("coverimage.png")
if err != nil {
log.Fatalln(err)
}
// Create an EmbedHandler instance for managing the embedding process.
embedder := stegano.NewEmbedHandler()
// Embed the message into the image without saving to a file directly.
embeddedImage, err := embedder.EmbedDataIntoImage(coverFile, []byte("Hello, World!"), stegano.MinBitDepth)
if err != nil {
log.Fatalln(err)
}
// Save the modified image to a file.
err = stegano.SaveImage(stegano.DefaultpngOutputFile, embeddedImage)
if err != nil {
log.Fatalln(err)
}
}
This function demonstrates how to extract uncompressed data.
func main() {
coverFile, err := stegano.Decodeimage("embeddedimage.png")
if err != nil {
log.Fatalln(err)
}
// Create an ExtractHandler instance for managing the extraction process.
extractor := stegano.NewExtractHandler()
// Extract data from the image.
data, err := extractor.ExtractDataFromImage(coverFile, stegano.MinBitDepth)
if err != nil {
log.Fatalln(err)
}
// Print the extracted message.
fmt.Println(string(data))
}
For fine-grained control, embed data at a specific bit depth.
func main() {
coverFile, err := stegano.Decodeimage("coverimage.png")
if err != nil {
log.Fatalln(err)
}
// Create an EmbedHandler instance for managing the embedding process.
embedder := stegano.NewEmbedHandler()
// Embed the message at a specific bit depth.
// For example, if embedding at depth 3:
// - A binary value of 0x11111111 will become 0x11110111.
embeddedImage, err := embedder.EmbedAtDepth(coverFile, []byte("Hello, World!"), 3)
if err != nil {
log.Fatalln(err)
}
// Save the modified image to a file.
err = stegano.SaveImage(stegano.DefaultpngOutputFile, embeddedImage)
if err != nil {
log.Fatalln(err)
}
}
Disclaimer: Altering specific bit depths can affect image quality. Use bit depth settings carefully to maintain a balance between data embedding and visual fidelity.
Retrieve data from an image, ensuring the correct bit depth is used.
func main() {
coverFile, err := stegano.Decodeimage("embeddedimage.png")
if err != nil {
log.Fatalln(err)
}
// Create an ExtractHandler instance for managing the extraction process.
extractor := stegano.NewExtractHandler()
// Extract data from the image at the specified bit depth.
data, err := extractor.ExtractAtDepth(coverFile, 3)
if err != nil {
log.Fatalln(err)
}
// Print the extracted message.
fmt.Println(string(data))
}
Disclaimer: Extraction must use the exact bit depth specified during embedding. Mismatched settings will likely result in errors or incorrect data.
Determine the maximum data capacity of an image for a given bit depth.
func main() {
coverFile, err := stegano.Decodeimage("embeddedimage.png")
if err != nil {
log.Fatalln(err)
}
// Calculate and print the data capacity for the given image.
capacity := stegano.GetImageCapacity(coverFile, stegano.MinBitDepth)
fmt.Printf("Image capacity at bit depth %d: %d bytes\n", stegano.MinBitDepth, capacity)
}
Disclaimer: The maximum data capacity depends on the image size and bit depth. Be aware that embedding too much data can visibly degrade the image.
The library provides concurrency options for embedding and extraction. Use NewEmbedHandlerWithConcurrency
or NewExtractHandlerWithConcurrency
to control the number of goroutines for faster processing.
embedder, err := stegano.NewEmbedHandlerWithConcurrency(12)
if err != nil {
log.Fatalln(err)
}
extractor, err := stegano.NewExtractHandlerWithConcurrency(12)
if err != nil {
log.Fatalln(err)
}
- Always use the same settings (bit depth, compression) for embedding and extraction.
- Default output format MUST be PNG or any lossless image formats.
Library Name | Test 1 (ns/op) | Test 2 (ns/op) | Test 3 (ns/op) | Avg Time (ns/op) | Avg Time (ms/op) |
---|---|---|---|---|---|
Stegano | 352,531,567 |
349,444,200 |
348,196,967 |
350,390,578 |
350.39 ms |
Stegano with concurrency | 286,168,125 |
293,260,925 |
284,079,175 |
287,169,408 |
287.17 ms |
auyer/steganography | 1,405,256,700 |
1,424,957,200 |
1,401,682,600 |
1,410,965,500 |
1,410.97 ms |
Image size: 10,473,459 bytes
Text size: 641,788 bytes
Benchmark code can be found here
- Huffman Encoding: Add support for Huffman-based compression.
- Audio Support: Add support for audio formats.
- Multi-Carrier Support: If multiple carrier files are provided, split data into each carrier and include what part of the data it contains.