A minimal, ready-to-build project that ships a Python app as a single native executable with its source obfuscated and AES-256-GCM encrypted by obfy.
The flow is two steps: obfy build turns ./src into a protected drop-in mirror
(./protected), then PyInstaller bundles that mirror — never your real source —
into one binary.
src/run.py ──► obfy build ──► protected/ ──► pyinstaller ──► dist/app
src/app/ (encrypted) (one binary)
| Path | What it is |
|---|---|
src/run.py |
App entry point. Edit or replace. |
src/app/ |
Your code package — this is what gets protected. |
app.spec |
PyInstaller spec, already pointed at the protected ./protected tree. |
build.sh |
One-shot: obfy build + pyinstaller. |
Pipfile |
Build-time tools (obfy, pyinstaller), managed with pipenv. |
protected/, build/, dist/ |
Generated output (git-ignored). |
- CPython 3.10–3.13. Build with the same Python version you ship for — obfy's marshalled bytecode is interpreter-version specific.
- macOS (Apple Silicon / Intel), Linux (x86_64 / aarch64), or Windows (x64).
- PyInstaller produces a binary for the OS and architecture you build on. Build on each target platform you want to ship (e.g. in CI).
This template uses pipenv (the Pipfile pins
python_version = "3.12"). Install the build tooling into a managed virtualenv:
pip install --user pipenv # if you don't have it
pipenv install # creates the venv from PipfileAdd your app's own runtime dependencies to the
Pipfiletoo (pipenv install <pkg>), so PyInstaller can discover and bundle them.
Prefer a plain venv + pip? That works as well:
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install obfy pyinstallerpipenv run build # == pipenv run bash build.shThat runs the two steps for you. To run them by hand (inside pipenv shell or an
activated venv):
# 1. Protect ./src into ./protected (a drop-in mirror)
obfy build --src ./src --out ./protected --python python --level 5
# 2. Bundle the protected mirror into one executable
pyinstaller --noconfirm app.specRun the result:
./dist/app # Windows: dist\app.exe
./dist/app Ada # pass args through as usual-
obfy build --src ./src --out ./protecteddiscovers every.pyundersrc/, obfuscates it (--level 5also natively compiles eligible functions so their CPython bytecode never ships), then compiles → marshals → AES-256-GCM encrypts each module../protectedis a 1:1 mirror of./src: each.pybecomes a tiny self-activating stub, the real code lives encrypted inprotected/__obfy__/*.obfy, and obfy's native loader is bundled in. -
app.specpoints PyInstaller atprotected/run.py(notsrc/run.py) and ships the whole./protected/tree as data, plusobfy_runtimeas a hidden import because the loader is imported dynamically at runtime. The decryption happens in memory at import time; plaintext source is never written to disk.
obfy build takes --level 0–5, a sophistication dial where each level does
strictly more — 1 strips docstrings, 2 adds string mangling + dead code, 3
adds function-local renames, 4 adds cross-module public-name renames, and 5
adds native function compilation: eligible functions are lowered to obfy's own
bytecode VM, so their CPython bytecode never ships (a decrypted module shows only
stubs, with nothing to marshal.loads + dis). Reference:
obfuscation levels.
This template builds at --level 5 — the maximum. A PyInstaller binary is a
self-contained app with no framework resolving names by string, so the most
aggressive setting is safe here. Functions the ISA doesn't yet cover fall back to
level-4 encrypted marshal automatically, so the build never breaks. If a
dependency does dynamic name/attribute lookups that renaming would break, lower
--level in build.sh.
- Your code: put it under
src/app/(or add packages alongside it) and import it fromsrc/run.py. Keepsrc/to code only —obfy buildcopies every non-.pyfile under--srcinto the output, so don't point it at a tree that contains.envfiles or keys. - Executable name / icon / windowed mode: edit
app.spec(name=, addicon=, setconsole=Falsefor a GUI app). - Obfuscation level: change
--levelinbuild.sh(0–5; higher does strictly more). See obfuscation levels. - Excluding files from protection (e.g. framework files read as text): use
--exclude(fnmatchpatterns, repeatable). See packaging.
Full obfy documentation: docs.camouflage.network/obfy.
MIT — see LICENSE. The code you build with this template is yours.