Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
RRC_steganography — Provably secure linguistic steganography embedding secret messages into LLM-generated text via rotation range-coding. Includes embed, extract, and round-trip verification scripts. | Kitploit
Tools/GitHubGitHub/ryehr/rrc_steganography
SteganographyCryptographyMachine LearningPapers & ResearchLearning & EducationAI Security
GitHubryehr/rrc_steganography

RRC_steganography

Provably secure linguistic steganography embedding secret messages into LLM-generated text via rotation range-coding. Includes embed, extract, and round-trip verification scripts.

View Repository
61801 month agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

RRC Steganography

Rotation Range-Coding (RRC) Steganography — an efficient and provably secure linguistic steganographic method that embeds secret messages into natural-language text generated by large language models.

Paper: Efficient Provably Secure Linguistic Steganography via Range Coding (ACL 2026)

How It Works

StepDescription
Embed (Algorithm 3)Convert the secret message to a decimal value and iteratively rotate it within a shrinking interval guided by the LM's probability distribution and a PRNG-generated offset. Each rotation step produces one token.
Extract (Algorithm 4)Re-run the LM on the stegotext to recover interval bounds, then reverse-rotate back to the original decimal value and binarize it.

The rotation mechanism solves two issues of vanilla range-coding steganography:

  1. Probability-distribution preservation — the rotated secret value is uniformly distributed within each interval, so token selection follows the original LM distribution.
  2. Fresh randomness — a new PRNG offset at each step prevents randomness reuse.

Repository Structure

.
├── rrc_core.py        # Algorithms 3 and 4, shared by every entry point
├── RRC_embed.py       # Embedding CLI
├── RRC_extract.py     # Extraction CLI (reports recovery accuracy)
├── test_roundtrip.py  # Round-trip test across several message lengths
├── fastest_embed.py   # Embedding-speed benchmark
├── 0.Prompts.tsv      # 1000 prompts (first ~10 words of C4 sequences)
└── requirements.txt

The encoder and the decoder share rrc_core.py rather than holding separate copies of the loop. The two sides must agree bit for bit, and a divergence between two independently maintained copies surfaces only as a failed round trip, never on inspection.

Requirements

  • Python >= 3.10
  • PyTorch >= 2.0
  • Transformers >= 4.35 (tested through 5.x)
pip install -r requirements.txt

Quick Start

1. Verify correctness

python test_roundtrip.py                                  # GPT-2, 32/128/512 bits
python test_roundtrip.py --model meta-llama/Llama-2-7b-hf

2. Embed

python RRC_embed.py \
    --language_model meta-llama/Llama-2-7b-hf \
    --bit_length 128 --key 42 --top_k -1

3. Extract

python RRC_extract.py \
    --language_model meta-llama/Llama-2-7b-hf \
    --bit_length 128 --key 42 \
    --input_file 1.RC_decimal_Llama-2-7b-hf_bit128.tsv

The encoder records the secret it embedded and the token ids that carried it, so extraction reports a recovery rate. Two receiver models are scored:

  • ids — the receiver is given the token sequence. This isolates the coding scheme; anything below 100% is a numerical or logical fault.
  • text — the receiver is given the detokenised string and tokenises it again, as a text channel requires. Re-tokenisation does not always reproduce the sender's segmentation, so this is a lower bound; the gap between the two measures tokenisation ambiguity rather than the coding scheme. On Llama-2-7b at 128 bits: 100% for ids, ~91% for text.

Important: sender and receiver must share the language model, key, bit length and top-k setting, and must also run the same hardware and library versions. See Numerical requirements.

CLI Arguments

Shared by RRC_embed.py and RRC_extract.py:

ArgumentDefaultDescription
--language_modelmeta-llama/Llama-3.1-8BHuggingFace model id
--bit_length128Secret message length in bits
--top_k-1Top-k truncation; -1 = full vocabulary
--key42Symmetric key K (PRNG seed)
--precision_margin40Decimal digits beyond those 2**bit_length needs
--max_ctx_len3900KV-cache window
--deviceautocuda or cpu

RRC_embed.py also takes --prompts, --output, --limit, --seed, --max_tokens, --resume, --no_verify, and --part / --part_max for sharding a prompt set across processes (--part_max 3 with --part 0,1,2, one per GPU).

RRC_extract.py also takes --input_file, --mode {ids,text,both}, --limit and --verbose.

Numerical requirements

RRC narrows an interval of width 2**l down to width ~1, so the recovered value is sensitive to relative error by a factor of 2**l: at 128 bits a relative perturbation of 1e-16 anywhere in the chain displaces the result by ~1e22 against a tolerance of 0.5. Extraction does not degrade gracefully — it either returns the message or returns unrelated bits. Measured on Llama-2-7b:

SettingRelative perturbation of the probabilitiesExtraction
Repeating the same batch-1 decode0 (bitwise identical)works
The same row at batch 2 / 8 / 16~1e-2fails
Left-padding the prompt~1e-2fails
One teacher-forced pass instead of incremental decoding~4e-3fails

Two consequences shape the implementation. Batching is unavailable, so sequences are processed one at a time. And the receiver replays the sender's incremental decoding step for step rather than scoring the stegotext in a single forward pass, even though it holds every token in advance. Both shortcuts measure ~9x faster and neither preserves the probabilities.

Everything after the logits is float64 and produces bitwise identical boundaries on CPU and GPU, so the forward pass is the only step that ties a stegotext to its hardware.

Decimal precision

Precision is derived from the message length as ceil(bit_length * log10(2)) + margin and set before anything else runs. It scales with the message: 2**256 needs 78 digits and 2**8192 needs 2466.

Precision below that threshold does not fail cleanly. The low-order digits of the secret value are rounded away, the interval collapses within a few tokens and the termination test fires early, so a run can complete and report figures that are arithmetically unreachable. Holding the precision at 50 digits regardless of message length gives:

bitsutilisation at 50 digitssamples completingwith derived precision
256157.05%8/20100.17%
512299.67%9/20102.48%
1024660.91%7/10102.58%
20481046.40%1/598.18%
40962483.92%3/3, no error raised100.87%
81924766.49%1/299.07%

--precision_margin adjusts the headroom above the derived minimum; the minimum itself is enforced.

Verified termination

Algorithm 3 stops when (L + R) / 2 - d_s falls in (-0.5, 0.5]. That test can fire while the interval is narrower than 1, and the window [mid - 0.5, mid + 0.5) is then wider than the interval carrying it. Reverse rotation is an isometry modulo the interval width, so a window straddling the cut leaves the receiver a full interval away from d_s.

The sender therefore replays the receiver's reverse rotation before committing to stop and emits another token if the replay does not return the secret. The replay is pure Decimal work (~0.05 ms against ~20 ms for a forward pass), so the guard costs nothing measurable. --no_verify disables it.

Measured on Llama-2-7b:

bitsrecovered without the guardwith it
128291/300 (97.0%)300/300
51264/65 (98.5%)40/40
102420/2020/20
204810/1010/10

At 128 bits the guard cost 0.095% additional generated tokens. The samples at 1024 and 2048 bits are too few to bound the rate — 0/20 is the likelier outcome than not at a 3% rate — so they are not evidence that longer messages behave differently. Guarded, 370/370 were recovered.

Download Tool