Skip to main content

python-obfuscator

CI GitHub release (latest by date) Downloads Codecov

A Python source-code obfuscator built on the standard-library ast module. It applies multiple independent techniques — each individually togglable — to make code harder to read while keeping it fully executable. See Known limitations before use.

If this project is useful to you, consider sponsoring development.


Installing

pip install python-obfuscator

Requires Python ≥ 3.10.

From source (contributors)

pip install -e ".[dev]"

Or with Poetry:

poetry install

(poetry install pulls dev dependencies from the lockfile; use poetry run <command> to run tools inside that environment.)


Quick start — CLI

# Writes obfuscated/your_file.py (path structure is preserved)
pyobfuscate -i your_file.py

# Print to stdout
pyobfuscate -i your_file.py --stdout

# Disable specific techniques
pyobfuscate -i your_file.py --disable dead_code_injector --disable exec_wrapper

# Show version
pyobfuscate --version

Python API

One-shot helper

from python_obfuscator import obfuscate

source = "x = 1\nprint(x + 2)"
result = obfuscate(source)
print(result)

Selective techniques

from python_obfuscator import obfuscate, ObfuscationConfig

# All techniques except dead-code injection
config = ObfuscationConfig.all_enabled().without("dead_code_injector")
result = obfuscate(source, config=config)

# Only string encoding
config = ObfuscationConfig.only("string_hex_encoder")
result = obfuscate(source, config=config)

Reusing across multiple files (caches the pipeline)

from python_obfuscator import Obfuscator, ObfuscationConfig

obf = Obfuscator(ObfuscationConfig.all_enabled())
for path in my_files:
    path.write_text(obf.obfuscate(path.read_text()))

Config combinators

cfg = ObfuscationConfig.all_enabled()   # every registered technique
cfg = ObfuscationConfig.only("variable_renamer", "exec_wrapper")
cfg = cfg.without("exec_wrapper")       # returns a new frozen config
cfg = cfg.with_added("dead_code_injector")

Techniques

Name Priority What it does
variable_renamer 10 Renames local variables, function names, parameters, and class names to visually ambiguous identifiers (lIIllIlI…). Excludes builtins, imports, dunders, and attribute-accessed names.
string_hex_encoder 20 Replaces every string literal "hi" with bytes.fromhex('6869').decode('utf-8'). Skips f-strings.
dead_code_injector 30 Injects dead variable assignments at every scope level — module body, function bodies, class bodies, if/for/while/try/with branches. Some assignments reference other dead variables to simulate computation.
exec_wrapper 100 Wraps the entire module in a single exec("…") call, reducing the top-level AST to one statement. Runs last.

Techniques are applied in priority order (lowest first).


Example

Input

def greet(name):
    msg = "Hello, " + name
    print(msg)

greet("world")

Output (all techniques enabled — abridged)

exec('def lIlIllI(IIlIlII):\n    lIllIlI = bytes.fromhex(\'48656c6c6f2c20\').decode(\'utf-8\') + IIlIlII\n    ...\nlIlIllI(bytes.fromhex(\'776f726c64\').decode(\'utf-8\'))')

Performance overhead

Benchmarks run on an Apple M-series machine, 20 iterations each. The test programs cover OOP, algorithms, functional patterns, number theory, and string processing.

Total overhead (all techniques)

Program Original Obfuscated Overhead
algorithms.py 0.94 ms 1.98 ms +112%
cipher.py 1.37 ms 2.67 ms +95%
data_structures.py 0.72 ms 2.20 ms +207%
functional.py 0.67 ms 1.71 ms +155%
number_theory.py 1.84 ms 3.16 ms +72%
oop_simulation.py 0.68 ms 1.66 ms +144%

Per-technique contribution (average across all programs)

Technique Avg overhead Notes
variable_renamer ~5% Pure rename — negligible at runtime
string_hex_encoder ~12% bytes.fromhex call per string literal
dead_code_injector ~85% Dominant cost — dead assignments execute every iteration
exec_wrapper ~2% Single extra exec layer

The dead-code injector's overhead scales with the number of scopes and loop iterations in the original program. Programs with tight inner loops see the most overhead.


Known limitations

  • Class method names are not renamed. Attribute-accessed names (obj.method) cannot be safely renamed without full type-inference, so the renamer conservatively excludes them.
  • Keyword argument names are not renamed. fn(key=val) call-site keyword strings are bare AST strings, not Name nodes, and are not updated when a parameter is renamed.
  • No scope-aware renaming. The same identifier used in two independent function scopes maps to the same obfuscated name (which is semantically correct but less obfuscated than it could be).
  • No control-flow obfuscation. Opaque predicates, bogus branches, and integer encoding are not implemented.

Running the test suite

After a dev install (from source):

pytest

Coverage is enforced at ≥ 95% on every CI run.

# With coverage report
coverage run -m pytest && coverage report

# E2E tests with benchmark output
pytest tests/e2e/ -v -s

With Poetry, run the same commands through the project environment, for example poetry run pytest, poetry run coverage run -m pytest, and poetry run pytest tests/e2e/ -v -s.


Authors

David Teather — davidteather

License

MIT — see LICENSE.

Metadata

Release files for python-obfuscator 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for python-obfuscator 0.1.0
File Size Uploaded
python_obfuscator-0.1.0.tar.gz 16.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for python-obfuscator 0.1.0
File Interpreter ABI Platform
python_obfuscator-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 36.2 kB

Release files / python_obfuscator-0.1.0.tar.gz

Download URL python_obfuscator-0.1.0.tar.gz
Size 16.2 kB
Tags Source
SHA-256 checksum
How to use checksums
61300b60808781708ffa45d55d9377c0ba548368f7e0528933f094de399925e9
BLAKE2b-256 checksum
How to use checksums
d3f08a7f1424c929b6361c81ce850cdf3aa0bca5c62af1633425070e9dd00e9e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 3, 2026.

Transparency log

Release files / python_obfuscator-0.1.0-py3-none-any.whl

Download URL python_obfuscator-0.1.0-py3-none-any.whl
Size 19.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
e1a08766deae301ce81956b2b653aac8aa3681ea67e312c1a161fbda24b1b896
BLAKE2b-256 checksum
How to use checksums
1ac4043ec475d101a586fa7aa0fb02ba7cecb4bebd083368d9739047c4ed634d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Apr 3, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

0.0.2

1 release file

0.0.1

1 release file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page