Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Build stage
FROM golang:1.21-alpine AS builder

WORKDIR /app

COPY go.mod go.sum ./
RUN go mod download

COPY . .

RUN go build -o stego-web ./cmd/web/main.go

# Final stage
FROM alpine:latest

WORKDIR /app

COPY --from=builder /app/stego-web .
COPY --from=builder /app/cmd/web/frontend ./frontend

EXPOSE 8080

CMD ["./stego-web"]
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 eugene

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
92 changes: 91 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,91 @@
### Stego
# Go-Stego

Go-Stego is a steganography tool that allows you to hide messages in images and videos using the Discrete Cosine Transform (DCT) algorithm.

## Features

- **Image Support**: JPEG, PNG, BMP, and GIF (first frame).
- **Video Support**: MP4, AVI, MKV (requires `ffmpeg`).
- **Web Interface**: A built-in web server with a GUI.
- **CLI**: A powerful command-line interface with subcommands.
- **Docker Support**: Easily run the web server in a container.
- **Version**: 1.0.0

## Installation

### From Source

```bash
go build -o stego ./cmd/console/*.go
```

### With Docker

```bash
docker build -t stego-web .
docker run -p 8080:8080 stego-web
```

## Usage

### CLI

The CLI now uses a subcommand structure.

#### Encode a message into an image

```bash
./stego encode -i input.png -o output.png -m "Hello World" -p "mypassword"
```

#### Decode a message from an image

```bash
./stego decode -i output.png -p "mypassword" -l 352
```
Note: `-l` (length) is bits (len of string * 32).

#### Encode a message into a video

```bash
./stego encode -i input.mp4 -o output.mp4 -m "Hidden Message" -p "password" -v
```

### Telegram Bot

Start the bot interface:
```bash
export TELEGRAM_BOT_TOKEN="your_token_here"
./stego bot
```
The bot supports `/encode` and `/decode` commands via image/video captions.

### Web Server

Run the web server:
```bash
go run ./cmd/web/main.go
```
Then visit `http://localhost:8080`.

## Configuration

The application can be configured using environment variables or a configuration file at `config/app.env`.

Example `config/app.env`:
```env
TELEGRAM_BOT_TOKEN=your_token_here
PORT=8080
FRONTEND_PATH=./frontend
```

- **Robustness**: Use `-r` to set the strength of the hidden message (default 20).
- **Password**: Use `-p` to set a password.

## Documentation

See [ARCHITECTURE.md](docs/ARCHITECTURE.md) for details on the steganography process and project structure.

## License

MIT License. See [LICENSE](LICENSE) for details.
Binary file removed bin/console.exe
Binary file not shown.
220 changes: 165 additions & 55 deletions cmd/console/main.go
Original file line number Diff line number Diff line change
@@ -1,79 +1,189 @@
package main

import (
"flag"
"fmt"
"os"

flags "github.com/jessevdk/go-flags"
"stego/internal/config"
"stego/internal/telegram_bot"
)

var opts struct {
Pass string `short:"p" long:"pass" default:"abcdefghijklmnopqrstuvwxyz" required:"true" description:"Password is just any letters combination of any size and used for encoding/decoding for this file. It MUST BE the same for encoding and decoding of one image."`
Msg string `short:"m" long:"message" required:"false" description:"Message which should be encoded (or decoding verification, not nessesary)."`
MsgLen int `short:"l" long:"len" required:"true" description:"Length of message. MUST BE known to decoder and it's equal to 1 Msg's symbol = 32 bits."`
Robust int `short:"r" long:"robust" default:"20" required:"true" description:"The main parameter of encoding. More Robust cause more visible hidden message, but it is more stable for compression. Value 20 is fine for most cases. 50 is visible, but image is not corrupted."`
Action string `short:"a" long:"action" required:"true" description:"Available values: d, e, b (decode / encode / benchmark)"`
PathIn string `short:"i" long:"input" required:"true" description:"Path to input files/dir"`
PathOut string `short:"o" long:"output" required:"false" description:"Path to output files dir"`
// Version is the current version of the application.
const Version = "1.0.0"

func main() {
if err := run(); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

// You can use "encode", "decode" or "bench" (encode and decode together)
const (
encodeAction = iota
decodeAction
benchAction
)
func run() error {
config.LoadConfig()

var Action = decodeAction
if len(os.Args) < 2 {
printUsage()
return nil
}

func printHelp() {
var options flags.Options = 1
p := flags.NewParser(&opts, options)
p.WriteHelp(os.Stdout)
switch os.Args[1] {
case "encode", "e":
return handleEncode(os.Args[2:])
case "decode", "d":
return handleDecode(os.Args[2:])
case "bench", "b":
return handleBench(os.Args[2:])
case "bot":
return handleBot(os.Args[2:])
case "-V", "--version", "version":
fmt.Printf("Go-Stego version %s\n", Version)
return nil
case "-h", "--help", "help":
printUsage()
return nil
default:
return fmt.Errorf("unknown subcommand %q", os.Args[1])
}
}

func main() {
if _, err := flags.Parse(&opts); err != nil {
fmt.Println("")
printHelp()
return
func printUsage() {
fmt.Println("Go-Stego: A steganography tool using DCT")
fmt.Println("\nUsage:")
fmt.Println(" stego [subcommand] [options]")
fmt.Println("\nSubcommands:")
fmt.Println(" encode, e Encode a message into an image or video")
fmt.Println(" decode, d Decode a message from an image or video")
fmt.Println(" bench, b Perform both encoding and decoding for benchmarking")
fmt.Println(" bot Start the Telegram bot interface")
fmt.Println(" version Print version information")
fmt.Println(" help Print this help message")
fmt.Println("\nUse 'stego [subcommand] --help' for more information on a subcommand.")
}

type commonOptions struct {
pass string
robust int
pathIn string
pathOut string
video bool
msgLen int
}

func handleEncode(args []string) error {
fs := flag.NewFlagSet("encode", flag.ExitOnError)
msg := fs.String("m", "", "Message to encode")
pass := fs.String("p", "abcdefghijklmnopqrstuvwxyz", "Password for encoding")
robust := fs.Int("r", 20, "Robustness parameter (default 20)")
pathIn := fs.String("i", "", "Input file or directory")
pathOut := fs.String("o", "", "Output file or directory (optional)")
video := fs.Bool("v", false, "Treat input as video")

if err := fs.Parse(args); err != nil {
return err
}

switch (opts.Action) {
case "b", "bench": Action = benchAction
case "e", "encode": Action = encodeAction
case "d", "decode": Action = decodeAction
if *msg == "" {
return fmt.Errorf("message (-m) is required for encoding")
}
if *pathIn == "" {
return fmt.Errorf("input path (-i) is required")
}

if opts.MsgLen == 0 {
switch Action {
case encodeAction, benchAction:
opts.MsgLen = len(opts.Msg) * 32
fmt.Printf("Length of a message: %d. Use it for decoding.\n", opts.MsgLen)
default:
fmt.Println("Specify length of a message!")
return
}
opts := commonOptions{
pass: *pass,
robust: *robust,
pathIn: *pathIn,
pathOut: *pathOut,
video: *video,
msgLen: len(*msg) * 32,
}

fileInfo, err := CreateDir()
if err != nil {
fmt.Println(err.Error())
return
fmt.Printf("Length of a message: %d bits. Use it for decoding.\n", opts.msgLen)

return runFileOperation(opts, "encode", *msg)
}

func handleDecode(args []string) error {
fs := flag.NewFlagSet("decode", flag.ExitOnError)
pass := fs.String("p", "abcdefghijklmnopqrstuvwxyz", "Password for decoding")
msgLen := fs.Int("l", 0, "Length of message in bits (required for decoding)")
pathIn := fs.String("i", "", "Input file or directory")
video := fs.Bool("v", false, "Treat input as video")

if err := fs.Parse(args); err != nil {
return err
}

fmt.Println("called ", Action)
if *pathIn == "" {
return fmt.Errorf("input path (-i) is required")
}
if *msgLen <= 0 {
return fmt.Errorf("message length (-l) is required and must be positive for decoding")
}

switch Action {
case benchAction:
fallthrough
case encodeAction:
runFileOperation(fileInfo, "encode")
if Action != benchAction {
break
}
fallthrough
case decodeAction:
runFileOperation(fileInfo, "decode")
opts := commonOptions{
pass: *pass,
pathIn: *pathIn,
msgLen: *msgLen,
video: *video,
}

return runFileOperation(opts, "decode", "")
}

func handleBench(args []string) error {
fs := flag.NewFlagSet("bench", flag.ExitOnError)
msg := fs.String("m", "", "Message to encode")
pass := fs.String("p", "abcdefghijklmnopqrstuvwxyz", "Password for encoding/decoding")
robust := fs.Int("r", 20, "Robustness parameter (default 20)")
pathIn := fs.String("i", "", "Input file or directory")
pathOut := fs.String("o", "", "Output file or directory (optional)")
video := fs.Bool("v", false, "Treat input as video")

if err := fs.Parse(args); err != nil {
return err
}

if *msg == "" {
return fmt.Errorf("message (-m) is required for benchmarking")
}
if *pathIn == "" {
return fmt.Errorf("input path (-i) is required")
}

opts := commonOptions{
pass: *pass,
robust: *robust,
pathIn: *pathIn,
pathOut: *pathOut,
video: *video,
msgLen: len(*msg) * 32,
}

fmt.Printf("Benchmark: Length of a message: %d bits.\n", opts.msgLen)

if err := runFileOperation(opts, "encode", *msg); err != nil {
return fmt.Errorf("benchmark encoding failed: %w", err)
}

// For benchmark decoding, the input is the output of the encoding phase.
decodeOpts := opts
decodeOpts.pathIn = opts.pathOut
return runFileOperation(decodeOpts, "decode", "")
}

func handleBot(args []string) error {
fs := flag.NewFlagSet("bot", flag.ExitOnError)
token := fs.String("t", config.GetEnv("TELEGRAM_BOT_TOKEN", ""), "Telegram Bot Token (or set TELEGRAM_BOT_TOKEN in config/app.env)")

if err := fs.Parse(args); err != nil {
return err
}

if *token == "" {
return fmt.Errorf("telegram bot token is required (-t or TELEGRAM_BOT_TOKEN env var)")
}

b := bot.StegoBot{Token: *token}
return b.Start()
}
Loading
Loading