Mistode (Mist Code, pronounced like miss-told) is a code obfuscation tool for Python and C source. It combines AST/regex parsing with a distributed layout engine, making the delivered code hard to read, runnable as-is, and fully restorable with the key.
-
π‘οΈ Advanced Obfuscation Engine:
- Randomized Token Generation: Configurable token length (8-32 chars) and styles (Similar Characters like
Oo01Ilor Random Alphanumeric). - Deduplication & Validation: tokens are guaranteed collision-free and validated against language keywords.
- Seed Support: Fully reproducible obfuscation with
--seed.
- Randomized Token Generation: Configurable token length (8-32 chars) and styles (Similar Characters like
-
π Key-Gated Restoration (
--password):- Authenticated stream cipher built from the standard library:
scryptkey stretching, SHA-256 CTR keystream, HMAC-SHA256 tag (encrypt-then-MAC). - No keystream reuse: the nonce is derived from each payload, so a known plaintext cannot unlock the rest of the file.
- Deterministic: identical input and password produce identical output, so reproducible builds remain possible.
- Cipher is configurable:
--cipher stream(default) or--cipher none(plain base64).
- Authenticated stream cipher built from the standard library:
-
π Python Support (v3.14+):
- AST-Based Precision: parses the syntax tree so renaming is safe and accurate.
- Selective Preservation: automatically protects imports, built-ins (
print,len), stdlib calls,__all__exports, keyword-argument names,main(soconsole_scriptsandpython -mkeep working) and attribute names. - Docstring Hiding: replaces docstrings with minimal placeholders, restored losslessly from embedded layout data.
-
π¨ C Support:
- Robust Tokenization: a regex engine that reliably handles macros, pointers, structs, and both
#include <...>and#include "...". - Layout Engine: preserves complex file structures with distributed
/* @mistode:chunk:... */block-comment markers, which remain valid after line-continuation backslashes in multi-line macros. - Symbol Safety: automatically preserves keywords, preprocessor directives and standard headers.
gcc/nmprobing (with the source directory on the include path) is preferred when available; otherwise a conservative heuristic scanner is used.
- Robust Tokenization: a regex engine that reliably handles macros, pointers, structs, and both
-
π Zero-Loss Restoration:
- Distributed Layout Data: layout data (whitespace, comments, docstrings, original newlines) is compressed into interleaved chunk comments (plain base64 by default, encrypted with
--password). - Embedded Metadata: identifier mappings are embedded directly in the file footer. No key file is required for restoration.
- Bit-Perfect Restore: restores every byte of the original code, including comments, formatting, CRLF and empty lines.
- Distributed Layout Data: layout data (whitespace, comments, docstrings, original newlines) is compressed into interleaved chunk comments (plain base64 by default, encrypted with
-
π¦ Self-Contained Deliverables:
- Project (directory) mode mirrors the whole tree, keeping original filenames so imports/includes still resolve.
- Non-source files are copied by default (
--assets copy), including templates, data files and configs, so the project runs without further setup;--assets-skip-secretsskips.env,*.pemand similar files.
-
βοΈ Modern Tooling:
- Configuration File: Global defaults via
pyproject.toml(style, length, seed, cipher, assets...). - Detailed Statistics:
--statsreports identifier counts, preserved-name counts and file-size analysis.
- Configuration File: Global defaults via
Important
Mistode requires Python 3.14 or higher.
pip install mistode# Obfuscate 'app.py' (generates app.obf.py)
mistode o app.py --stats
# Restore to original (generates app.res.py)
mistode r app.obf.py
# Verify match
diff app.py app.res.py# Obfuscate 'main.c' (generates main.obf.c)
mistode o main.c --stats
# Compile and run obfuscated code
gcc main.obf.c -o main_obf
./main_obf
# Restore
mistode r main.obf.c# Obfuscate with a delivery key
mistode o app.py --password "$DELIVERY_KEY"
# The artifact runs without the key
python app.obf.py
# Restore from the artifact
mistode r app.obf.py --password "$DELIVERY_KEY"Pass a directory instead of a single file to obfuscate a whole project:
# Obfuscate src/ into src.obf/ (mirrored tree, original filenames kept
# so imports/includes still resolve; cross-file references are preserved)
mistode o src/
# Run the obfuscated project directly (Python)
python src.obf/app.py
# Or compile it (C)
gcc -Isrc.obf src.obf/main.c src.obf/util.c -o app
# Non-source assets (templates, data, configs) are copied by default so
# the output is runnable; inspect or disable that with:
mistode o src/ --assets list
mistode o src/ --assets skip
# Restore the whole project byte-identically
mistode r src.obf/ # produces src.res/Project mode handles cross-file references automatically: names imported via from mod import name (Python) and symbols shared across compilation units (C) are preserved in every file, so the obfuscated project remains runnable and compilable.
# General Syntax
mistode [command] [file] [options]
# Commands
o, obf, obfuscate Obfuscate a file
r, res, restore Restore a file| Option | Alias | Description |
|---|---|---|
--out |
-o |
Specify output filename. |
--key |
-k |
Write/read the identifier mapping as a JSON file (optional; embedded metadata is the default source). |
--stats |
Show detailed statistics after processing. | |
--style |
similar (default) or random. |
|
--length |
-l |
Token length (8-32). |
--seed |
-s |
Random seed for reproducibility. |
--password |
-p |
Password that gates restoration of encrypted payloads. |
--cipher |
stream (default with --password) or none. |
|
--aggressive-methods |
Also rename class methods whose receivers are only self / the class name. |
|
--assets |
Whether to copy non-source files: copy (default), skip, list. |
|
--assets-skip-secrets |
Do not copy .env, *.pem, *.key and similar files. |
Project-wide defaults can be defined in pyproject.toml; Mistode searches the current and parent directories.
[tool.mistode]
style = "similar" # "similar" (Io01) or "random" (aB3d)
length = 24 # Stronger tokens
stats = true # Always show stats
seed = 12345 # Deterministic builds
cipher = "stream" # Payload cipher (requires --password)
assets = "copy" # Copy non-source files
assets_skip_secrets = falseFor a detailed guide, see examples/CONFIG_GUIDE.md.
| Component | Status | Notes |
|---|---|---|
| Variable Names | β | Replaced with tokens |
| Function/Class Names | β | Replaced with tokens |
| Docstrings | β | Replaced with placeholders (restored losslessly) |
main |
β | Preserved (entry point for console_scripts / python -m) |
__all__ exports |
β | Preserved (public API) |
| Keyword-argument names | β | Preserved (callers pass them by name) |
| Attribute names | β | Preserved (obj.attr is never renamed) |
| Imports | β | Preserved (import math) |
| Built-ins | β | Preserved (print, len) |
| Stdlib Methods | β | Preserved (os.path.join) |
| Component | Status | Notes |
|---|---|---|
| Functions | β | User-defined only |
| Variables/Structs | β | Local and Global |
| Comments | β | Hidden in obfuscated output (restored losslessly) |
| Multi-line macros | β | Chunk markers use block comments so \ continuations survive |
| Keywords | β | Preserved (if, while) |
| Preprocessor | β | Preserved (#include, #define, macro names) |
| Std Lib | β | Preserved (printf, malloc) |
Restoration data travels inside the artifact; the password is the only external secret.
-
Distributed Layout Data (Primary): Layout data (whitespace, comments, docstrings, original newlines) is compressed and injected as comments throughout the file (
#@mistode:chunk://* @mistode:chunk: */for C). This is what makes 100% bit-perfect restoration possible. -
Embedded Mappings (Secondary): The renaming map is compressed into the file footer (
#@mistode:metadata:). -
Password (Key): With
--password, both payload kinds are stored assecure_chunk/secure_metadata: scrypt-derived key, per-payload nonce, HMAC tag. A missing or wrong password fails explicitly. -
Key File (Optional):
--keywrites the identifier mapping to JSON. Useful for auditing or restoring when the embedded metadata was stripped; it is not required for the normal workflow.
- Dynamic access is not tracked: names accessed via strings (
globals()[...],setattr,getattr(obj, "name"), pickling) cannot be renamed consistently. Such patterns are the standard limitation of identifier-renaming obfuscators. - External references are the public contract: names exported via
__all__and names used across files (imported or called from other modules) are preserved, not obfuscated. - C heuristic mode: without
gcc/nmonPATH, external symbol detection falls back to a conservative heuristic scanner. On machines wheregccis available, itsnm -uoutput is unioned with that scan, so more names are treated as external and fewer are renamed. - Obfuscation is not end-to-end secrecy: string literals, numeric constants and control flow stay readable. Obfuscation raises the cost of understanding the code; it does not hide business logic completely.
--passwordprotects the restoration data, not the code: an attacker without it can still read the obfuscated source, just not restore it. Use a long, unique passphrase;scryptraises the cost of offline guessing but cannot rescue a weak one.- Encrypted output is still plain
.py/.c: the password protects restoration, not execution. - Docstrings are removed from the artifact: they are stored in the restoration payload, so
__doc__is empty at runtime andhelp()/ Sphinx /doctestsee no documentation until the file is restored. --cipher streamrequires--password: a missing password is an error, not a silent downgrade to plaintext.- KDF salt is fixed (
mistode-kdf-v1): the same passphrase derives the same key everywhere. Required for reproducible builds; the trade-off is that precomputation transfers across installations, so passphrase strength matters more. - Legacy payloads: files encrypted by Mistode <= 0.1.3 (no nonce/tag) fail authentication with an explicit error; re-obfuscate them with the current release.
- Token pasting disables renaming: a file using
##is left unrenamed, because the preprocessor can synthesise any identifier. - Rename rate is limited: dynamic features (attribute access, star imports, keyword arguments, reflection) make many names external contracts. On this project's own source, ~45% of eligible names are renamed in single-file mode and ~36% in project mode; real-world code with more cross-module surface is lower.
--aggressive-methodsis unsafe for libraries: it renames class methods whose receivers are provably local (self/class name). External callers reach methods by name, so libraries can break; use it only for self-contained code.--keyis ignored in project mode for restore: project artifacts carry per-file metadata; restore from it directly (a combined key is written on obfuscation for auditing, but restore rejects--keyto avoid ambiguous per-file mappings).
Contributions are welcome. See CONTRIBUTING.md for details.
This project is licensed under the GPLv3 License.