Python source obfuscator — encode strings, bytecode, and CPython C compilation
Project description
PyPrivate-Obf
A Python source obfuscator reconstructed from the Py Private Android application.
The original author and owner of this encoder is the PSH Team — t.me/psh_team.
This repository is a reverse-engineered Python reconstruction of their work from the APK source.
CLI Preview
Installation
pip install pyprivate-obf
To use CPython obfuscation (Cython + GCC compilation), also install:
pip install cython
# Debian / Ubuntu
sudo apt install gcc python3-dev
# Termux
pkg install clang python-dev
CLI Usage
Interactive wizard
Run without any arguments to get a step-by-step menu:
pyprivate-obf
Or force interactive mode explicitly:
pyprivate-obf --interactive
Non-interactive (flags)
pyprivate-obf <input.py> [OPTIONS] [-o output.py]
All available flags
| Flag | Description |
|---|---|
--encode-strings |
Replace every string literal with bytes([...]).decode() |
--bytecode |
Marshal bytecode + opcode-pattern swap |
--cpython |
Cython --embed → C source → GCC native binary launcher |
--layers N |
Number of marshal layers to apply (default: 1, max: 20) |
--re-check-version |
Re-prepend Python version guard inside every layer |
--expiry "YYYY-MM-DD HH:MM:SS.000000" |
Add an expiry date guard |
--expiry-message "..." |
Message shown when expired (default: "This file has expired.") |
--pip PKG1 PKG2 ... |
Auto-install packages on first run |
--check-version |
Lock script to the current Python major.minor version |
--telegram-bot-token TOKEN |
Telegram channel guard — bot token |
--telegram-channel-id @channel |
Telegram channel guard — channel id |
-o / --output FILE |
Output file path (default: <input>.enc.py) |
Examples
# Encode strings + 3 bytecode layers
pyprivate-obf script.py --encode-strings --bytecode --layers 3
# CPython (native binary) obfuscation
pyprivate-obf script.py --cpython
# Add an expiry date
pyprivate-obf script.py --bytecode --expiry "2025-12-31 23:59:59.000000"
# Auto-install dependencies on first run
pyprivate-obf script.py --bytecode --pip requests numpy pillow
# Lock to current Python version
pyprivate-obf script.py --bytecode --check-version
# Telegram channel membership gate
pyprivate-obf script.py --bytecode \
--telegram-bot-token 123456:ABCdef \
--telegram-channel-id @mychannel
# Chain everything with a custom output path
pyprivate-obf script.py --encode-strings --bytecode --layers 2 \
--check-version -o protected.py
Encoding Methods
There are four independent encoding methods. They live in pyprivate_obf/encoding_methods/ and each one accepts a source: str and returns an obfuscated str.
encode_strings
Rewrites every string literal in the source as bytes([...]).decode().
pyprivate-obf script.py --encode-strings
Before:
print("hello world")
After:
print(bytes([104, 101, 108, 108, 111, 32, 119, 111, 114, 108, 100]).decode())
bytecode_obfuscation
Compiles the source, serialises it with marshal, then swaps a known opcode pattern in the bytecode so it cannot be cleanly disassembled with stock dis. Output format:
import marshal
exec(marshal.loads(b'...'))
pyprivate-obf script.py --bytecode
pyprivate-obf script.py --bytecode --layers 5 # 5 nested marshal layers
cpython_obfuscation
Compiles the source to C using Cython --embed, strips the compressed string-table sections so only clean ASCII C code remains, then wraps it in a Python launcher that writes the .c file to .py_private/<timestamp>, compiles it with GCC, and runs the native binary.
Requires: cython (pip) + gcc + Python headers.
pyprivate-obf script.py --cpython
The output .py file contains only the clean C source as a plain string — no binary or base64 data.
simple_marshal
Plain marshal wrap with no opcode swap. Used as a fallback when no encoding method is selected, and in DEBUG mode.
# applied automatically when no --bytecode / --cpython flag is given
pyprivate-obf script.py
Guard Layers
Guards are injected at the top of the output script before encoding. Multiple guards can be combined.
Expiry Time
Refuses to run after a given date and time.
pyprivate-obf script.py --expiry "2025-06-30 23:59:59.000000"
pyprivate-obf script.py --expiry "2025-06-30 23:59:59.000000" --expiry-message "License expired."
Python Version Lock
Refuses to run on any Python version other than the one used to obfuscate.
pyprivate-obf script.py --check-version
Pip Installer
Auto-installs missing packages on first run.
pyprivate-obf script.py --pip requests numpy pillow
Telegram Channel Guard
Verifies that the user has joined a Telegram channel before allowing execution.
pyprivate-obf script.py \
--telegram-bot-token 123456:ABCxyz \
--telegram-channel-id @mychannel
Python API
Use the encoders and guards directly from Python:
from pyprivate_obf import encode_strings, bytecode_obfuscation
from pyprivate_obf import ExpiryTime, CheckVersion, LibrariesInstaller, EnterChannel
source = open("script.py").read()
# Encode strings
source = encode_strings(source)
# Bytecode obfuscation
source = bytecode_obfuscation(source)
# CPython obfuscation (requires cython + gcc)
from pyprivate_obf.encoding_methods.cpython_obfuscation import cpython_obfuscation
source = cpython_obfuscation(source)
# Add expiry guard
source = ExpiryTime(
time="2025-12-31 23:59:59.000000",
message="This script has expired."
).get_new_source(source)
# Add pip installer guard
source = LibrariesInstaller(
package_names=["requests", "numpy"]
).get_new_source(source)
# Add Python version lock
source = CheckVersion().get_new_source(source)
# Add Telegram channel guard
source = EnterChannel(
bot_token="123456:ABCxyz",
channel_id="@mychannel"
).get_new_source(source)
with open("protected.py", "w") as f:
f.write(source)
Credits
This project is a reverse-engineered Python reconstruction of the Py Private v4.1.2 Android application.
The original encoder was authored by the PSH Team: t.me/psh_team
The APK was decompiled using apktool. The inner Python source was bundled via Chaquopy as .pyc files (Python 3.9 bytecode) inside assets/chaquopy/app.imy. Each .pyc was decompiled and reconstructed into clean Python source.
License
MIT © pooraddyy
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file pyprivate_obf-1.0.0.tar.gz.
File metadata
- Download URL: pyprivate_obf-1.0.0.tar.gz
- Upload date:
- Size: 18.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
869fb8da3c5435f178536a3902219f971423a935bc86334b1d5a5c1e53496f09
|
|
| MD5 |
026143db7afa63da0a8259a07c8b932f
|
|
| BLAKE2b-256 |
97f34e3ce56a237f4451ea43c111ed166e1cfc44b270fab6652d788f3e69d113
|
Provenance
The following attestation bundles were made for pyprivate_obf-1.0.0.tar.gz:
Publisher:
publish.yml on pooraddyy/pyprivate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyprivate_obf-1.0.0.tar.gz -
Subject digest:
869fb8da3c5435f178536a3902219f971423a935bc86334b1d5a5c1e53496f09 - Sigstore transparency entry: 1886001813
- Sigstore integration time:
-
Permalink:
pooraddyy/pyprivate@91f4b917b64da39fdc0d36a1ec0dd6592ddc29c6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pooraddyy
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@91f4b917b64da39fdc0d36a1ec0dd6592ddc29c6 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file pyprivate_obf-1.0.0-py3-none-any.whl.
File metadata
- Download URL: pyprivate_obf-1.0.0-py3-none-any.whl
- Upload date:
- Size: 20.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e86d8adb5961722f9c5d2b6b0a9244e39b0f7d4a3892fcb6dd200dd7f4fb4806
|
|
| MD5 |
2760c046e489a73f26fa83d6cc12bfa2
|
|
| BLAKE2b-256 |
45784b290ca2707f536a5ec0f04ebf0e86a3804b00a8f7254ddd29cc22226bc5
|
Provenance
The following attestation bundles were made for pyprivate_obf-1.0.0-py3-none-any.whl:
Publisher:
publish.yml on pooraddyy/pyprivate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pyprivate_obf-1.0.0-py3-none-any.whl -
Subject digest:
e86d8adb5961722f9c5d2b6b0a9244e39b0f7d4a3892fcb6dd200dd7f4fb4806 - Sigstore transparency entry: 1886001845
- Sigstore integration time:
-
Permalink:
pooraddyy/pyprivate@91f4b917b64da39fdc0d36a1ec0dd6592ddc29c6 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/pooraddyy
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@91f4b917b64da39fdc0d36a1ec0dd6592ddc29c6 -
Trigger Event:
workflow_dispatch
-
Statement type: