stegtext 0.2.0

Text steganography command-line tool and library.
Documentation

stegtext

Text steganography command-line tool and library.

Links:

How It Works

Unicode has many homoglyphs; that is, different characters with the same appearance.

This tool hides a secret value in plain text by selectively replacing characters with homoglyphs.

The encoded result is visually indistinguishable from the plain text.

The amount of secret data that you can hide depends on the embedding capacity (the number of characters with homoglyphs) of the plain text.

See the Technical Details section of the API documentation for additional details.

Example

Hide a secret message in plain text:

# show "input.txt"
$ cat input.txt
The Hitchhiker's Guide to the Galaxy is a 1979 science fiction comedy
novel by English author Douglas Adams, adapted from the first four parts
of his radio comedy series of the same name.

# hide secret, write to "hidden.txt"
$ stegtext encode 'hi there' < input.txt > hidden.txt

# show "hidden.txt"
$ cat hidden.txt
Тhe Нitchhiker'ѕ Guide to the Galaxy is a 1979 ѕcіеncе fictіon соmedy
novel by Englіsh author Douglаs Аdаmѕ, adapted frоm the fіrѕt four рartѕ
of hiѕ rаdio сomedу ѕеriеs оf the sаmе name.

# read secret from "hidden.txt"
$ stegtext decode < hidden.txt
hi there

Install

stegtext can be installed several ways:

Install from Package

Install the stegtext package with cargo install:

cargo install stegtext

Install from Git Repository

Install directly from the main branch of the Git repository:

cargo install --git https://codeberg.org/pablotron/stegtext

Install as Container

Install as a container with Podman:

# pull container image
podman pull codeberg.org/pablotron/stegtext:latest

Run container:

# container image url
IMAGE_URL=codeberg.org/pablotron/stegtext

# run ephemeral stegtext container, read text from "input.txt", hide
# secret "hi there", and write result to "hidden.txt"
podman run --rm -i $IMAGE_URL encode 'hi there' < input.txt > hidden.txt

Container image notes:

  • less than 750k in size
  • contains stripped, statically-linked binary and nothing else
  • can run rootless and with no capabilities (e.g. --cap-drop ALL)
  • signed by OpenPGP key ID 021136521548EB198F64FF738E182534CDD1F2B8

Build

Build release binary using cargo build:

# clone git repo
git clone https://codeberg.org/pablotron/stegtext
cd stegtext

# build release binary
cargo build --release

Build stegtext container image using Podman:

# clone git repo
git clone https://codeberg.org/pablotron/stegtext
cd stegtext

# build image
podman build -t stegtext:latest .

Run previously-built stegtext container:

# run ephemeral stegtext container, read text from "input.txt", hide
# secret "hi there", and write result to "hidden.txt"
podman run --rm -i stegtext:latest encode 'hi there' < input.txt > hidden.txt

Usage

Available subcommands:

  • capacity: Print embedding capacity of input, in bytes.
  • encode: Embed secret in text and then print result.
  • decode: Extract secret from text and print it.
  • help: Print usage.

Rust Library

stegtext can also be used as a Rust library. Here are a couple of examples.

Hide secret in text:

// secret
const SECRET: [u8; 5] = *b"hello";

// input text
const TEXT: &str = "\
Тhe Нitсhhikеr's Guіdе to thе Galaxy is a 1979 ѕcіеncе fiсtion соmedy
novеl bу Englіѕh author Douglаѕ Adаmѕ, adарtеd frоm the fіrѕt four parts
of hiѕ radiо соmedу ѕеriеѕ оf thе sаmе name.";

// expected output
const EXP: &str = "\
Тhe Нitсhhikеr's Guide to the Galaxy is a 1979 ѕcіеncе fiсtion соmedy
noνеl by Еnglіsh author Dоuglаs Аdаms, аdарtеd from thе fіrst four parts
of hiѕ radiо соmedу ѕеriеѕ оf thе sаmе name.";

// encode secret, check result
assert_eq!(stegtext::encode(&SECRET, TEXT)?, EXP);

Get secret from text:

// text with embedded secret
const TEXT: &str = "\
Тhe Нitсhhikеr's Guide to the Galaxy is a 1979 ѕcіеncе fiсtion соmedy
noνеl by Еnglіsh author Dоuglаs Аdаms, аdарtеd from thе fіrst four parts
of hiѕ radiо соmedу ѕеriеѕ оf thе sаmе name.";

// decode secret, check result
assert_eq!(stegtext::decode(TEXT)?, "hello");

Get text embedding capacity:

// input text
const TEXT: &str = "\
Тhe Нitсhhikеr's Guіdе to thе Galaxy is a 1979 ѕcіеncе fiсtion соmedy
novеl bу Englіѕh author Douglаѕ Adаmѕ, adарtеd frоm the fіrѕt four parts
of hiѕ radiо соmedу ѕеriеѕ оf thе sаmе name. Іt centrеѕ оn the
misаdvеntureѕ оf Arthur Dent, the оnly man tо ѕurvive the destruction of
Earth, as he roams the cosmos and learns the truth behind his very
existence. The novel's namesake is an in-universe electronic travel
guide written in the form of an encyclopaedia, through which the story
is framed.";

// calculate capacity, check result
assert_eq!(stegtext::capacity(TEXT), 24);

Additional documentation is available here: stegtext API documentation.

Testing

Tests are run automatically as part of the CI/CD pipeline (see .forgejo/workflows/ci.yml) and release process (see release.py and Containerfile.release).

However, you can manually run the audit, code coverage, linting, and testing commands with the commands in the following sections.

Audit Dependencies

Install cargo-audit and then use cargo audit to check dependencies for security vulnerabilities:

cargo audit
    Fetching advisory database from `https://github.com/RustSec/advisory-db.git`
      Loaded 1271 security advisories (from /data/home/pabs/.cargo/advisory-db)
    Updating crates.io index
    Scanning Cargo.lock for vulnerabilities (22 crate dependencies)

Check Code Coverage

Install cargo-tarpaulin and use cargo tarpaulin to check code coverage:

cargo tarpaulin --engine llvm --fail-under 95
...
|| Uncovered Lines:
|| src/bin/stegtext.rs: 80-84
|| src/lib.rs: 393, 434, 565
|| Tested/Total Lines:
|| src/bin/stegtext.rs: 5/10 +0.00%
|| src/lib.rs: 150/153 +0.03%
||
95.09% coverage, 155/163 lines covered, +0.06% change in coverage

Lint Code

Use cargo clippy to run the code linter:

cargo clippy
    Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.04s

Lint Markdown

Use markdownlint-cli via Podman to lint the Markdown in README.md:

# container image URL
IMAGE_URL=ghcr.io/igorshubovych/markdownlint-cli

# run markdownlint-cli in ephemeral container, lint README.md
podman run --rm -it -v .:/src -w /src $IMAGE_URL ./README.md

Run Tests

Use cargo test to run the test suite and check the examples in the API documentation:

cargo test
...
test result: ok. 12 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out