WhatThePy obfuscates Python code using compression, a custom SHA256-based stream cipher, dynamic key generation, and runtime execution. The result is compact, messy code that still runs fine but is much harder to read and analyze.
Important: This is for fun and learning. It deters casual viewers but will not stop someone who knows what they are doing.
| Your Code | After WhatThePy |
|---|---|
def greet(name):
message = f"Hello, {name}!"
print(message)
return message
if __name__ == "__main__":
greet("World") |
...
洪ΠОΖНСΔΘΜΖФ鷹=lambda:_СΖΙ虎ΣΓФЙТХВГ(_Θ宙鷹荒鷹日ЙΖΥХДΑ(_У荒ЛΡ荒黃ΣНПΘР豹,_ДΒПΘΞНΠΡ麟麟Γ玄,_РК虎Π宙玄ΕНКΛ龍Ж,_麒鳳ΞБΕГОП宙БΞ獅),_ΥДΓ日Ζ地УΙΤ麒熊麒)
def _日И月黃宇ОНΖ麟龍麒洪():return bytes(_洪ΠОΖНСΔΘΜΖФ鷹())
_다허يεمα地러ص서Т風=_日И月黃宇ОНΖ麟龍麒洪()
def _ذרгΚЛΤ조さ洪터βש(_바かたצדαΜח春غР秋,_צカישىこضФオמП寒):
_אנЮ天라ΘШנ火昃הן=bytes(_v^186 for _v in [210, 219, 201, 210, 214, 211, 216]).decode()
_木סкעل寒бえいΚ머Φ=bytes(_v^109 for _v in [30, 5, 12, 95, 88, 91]).decode()
_川거黃麒جウ宙머辰けиף=__import__(_אנЮ天라ΘШנ火昃הן);_ךえ黃لظ허אס昃타麒霜=getattr(_川거黃麒جウ宙머辰けиף,_木סкעل寒бえいΚ머Φ)
_너さИر雨חר雪러Х퍼風=bytearray(len(_צカישىこضФオמП寒));_玄СβחΓ辰ΡΧסתقケ=32
for _소ד河そえΑض冬ЫてДذ in range(0,len(_צカישىこضФオמП寒),_玄СβחΓ辰ΡΧסתقケ):
_رΘЙЙ日ТРЫЪב山Γ=(_소ד河そえΑض冬ЫてДذ//_玄СβחΓ辰ΡΧסתقケ).to_bytes(8,'little')
_لפへζひחيטΩたカΩ=_ךえ黃لظ허אס昃타麒霜(_바かたצדαΜח春غР秋+_رΘЙЙ日ТРЫЪב山Γ).digest()
_قて차あ차Э雪ث黃ضΗ雪=_צカישىこضФオמП寒[_소ד河そえΑض冬ЫてДذ:_소ד河そえΑض冬ЫてДذ+_玄СβחΓ辰ΡΧסתقケ]
for _Яו暑火ءФعجПΣいق,_כ카ΧΣ터γΗכدすלר in enumerate(_قて차あ차Э雪ث黃ضΗ雪):_너さИر雨חר雪러Х퍼風[_소ד河そえΑض冬ЫてДذ+_Яו暑火ءФعجПΣいق]=_כ카ΧΣ터γΗכدすלר^_لפへζひחيטΩたカΩ[_Яו暑火ءФعجПΣいق]
return bytes(_너さИر雨חר雪러Х퍼風)
def _ع雪כΨ麒麟лΨα盈霜暑(_머머天로ט盈صלΡ커ط저,_머חتתРΥפ타ΨУへت):
_كМへしう가電けキ荒בね=bytes(_v^35 for _v in [75, 66, 80, 75, 79, 74, 65]).decode()
_クア風ΣЛд宿火玄Эи저=bytes(_v^237 for _v in [158, 133, 140, 223, 216, 219]).decode()
_л더פ虎ほ電Кδ冬다Τو=__import__(_كМへしう가電けキ荒בね);_き퍼水暑노זאΗ사لىט=getattr(_л더פ虎ほ電Кδ冬다Τو,_クア風ΣЛд宿火玄Эи저)
return _き퍼水暑노זאΗ사لىט(_머חتתРΥפ타ΨУへت+_머머天로ט盈صלΡ커ط저).digest()
def _火دね카ζ列УبلてבЗ(_저ЛЭ河로ם오רעخΣ카,_נ黃تعえカعظさΜ川エ):
_شסすけγЦВεزΞΞ코=_저ЛЭ河로ם오רעخΣ카[:16];_고زつ玄г宇ЦγЮע霜す=_저ЛЭ河로ם오רעخΣ카[16:]
_פ코ג자طاい코Б秋ىכ=_ع雪כΨ麒麟лΨα盈霜暑(_شסすけγЦВεزΞΞ코,_נ黃تعえカعظさΜ川エ)
continues... |
git clone https://github.com/techlinn/WhatThePy.git
cd WhatThePy
pip install -r requirements.txtRequirements: Python 3.8+, rich, pyfiglet
python -m whatthepy [file.py] [flags]Flags:
--strip-docs— strip module/class/function docstrings. Off by default so frameworks that read__doc__at runtime (FastAPI endpoint descriptions, click/Typer help, sphinx,help()) keep working. Pass this only if you don't need docstrings and want the extra obfuscation.--self-contained— embed everything in a single.py(no.datsidecar). Default is split mode (stub +.dat).--level {low,high}—high(default) encrypts string literals into a runtime-decrypted table;lowskips that for a faster, weaker pass.
WhatThePy is single-file: it obfuscates one .py at a time and does not rewrite cross-file imports. See Compatibility & Limitations below.
Works with PyInstaller (split mode):
pyinstaller --onefile --clean --add-data "yourfile_payload.dat;." yourfile_obfuscated.pyAdd --hidden-import=NAME for any module your code loads dynamically (importlib.import_module(...) / __import__(...) with non-literal names) — static imports are already visible to PyInstaller because the stub re-emits them at the top.
WhatThePy is designed so an obfuscated file runs identically and compiles with PyInstaller without runtime errors. Verified against f-strings, match/case literal patterns, PEP 695 type/generics, from __future__ import annotations + dataclass forward refs, TypedDict, Enum, __all__, bytes literals, decorator string args, typing.get_type_hints, default-string args, walrus, closures, __slots__, try/except messages, struct.pack, asyncio, multiprocessing (spawn), __init_subclass__, metaclasses, generators, and function-local imports.
Things to know:
- Single-file only. No batch/package mode and no cross-file import rewriting. Obfuscate each entry script individually; sibling modules are imported as-is.
- Relative imports work when the obfuscated file is imported as part of its package (the import machinery sets
__package__). Running an obfuscated package module directly as a top-level script will fail relative imports — same as plain Python. - Docstrings are kept by default (so FastAPI/click/sphinx/
help()work). Use--strip-docsto remove them. inspect.getsourcereturns the obfuscated stub source (not your original) rather than raising;inspect.getfilereturns the stub path. This is expected.- Co-located data files (assets next to your source) are not bundled automatically. With PyInstaller, add each via
--add-dataand read them fromsys._MEIPASSat runtime (fall back toos.path.dirname(__file__)when not frozen). - Python version pin. The stub checks
importlib.util.MAGIC_NUMBERand refuses to run on a different CPython bytecode version than the one it was built with. Build and run on the same Python interpreter. With PyInstaller this is automatic (the bundled interpreter matches).
| Threat | Protected? |
|---|---|
| Script kiddies | Yes |
| Casual copy-paste | Yes |
| Quick glances | Yes |
| Basic static analysis | Yes |
| Motivated attackers | Partially |
| Good reverse engineers | Maybe |
Good for: Learning, CTF challenges, deterring casual viewers Not for: Protecting secrets, valuable IP, or anything critical
Source Code
|
v
[1] Compress (zlib)
|
v
[2] Encrypt (SHA256 stream cipher + custom KDF)
|
v
[3] Either:
-> Store payload in .dat file (split mode)
-> Or encode using randomized Unicode characters
|
v
[4] Build loader stub
|
v
[5] Runtime:
- Rebuild key
- Decrypt payload
- Decompress
- Execute
This is a fun project for learning and experimentation. It makes code harder to casually read but doesn't provide serious protection. Use it for fun, learning, or light deterrence - not for anything critical.
Made this as a weekend project and please.. Don't use it for serious security needs!
MIT License - Do whatever you want with it, just don't blame me!
Made with questionable decisions and too much redbull😭
