Skip to main content
Reverse Engineering

Can PyArmor Be Deobfuscated? An Honest, Tested Answer

By Mithun··9 min read
Quick Answer

Not with the usual tools. The standard Python decompiler toolkit (marshal+dis, uncompyle6, pycdc) that instantly cracks a plain .pyc or PyInstaller build gets nothing from PyArmor — I tested it below, and its payload isn't even valid marshal data. PyArmor is genuinely one of the hardest off-the-shelf Python protections to reverse. It isn't *mathematically* unbreakable (any code must decrypt itself to run, so runtime analysis is theoretically possible, and old versions had weaknesses that were later patched), but there's no reliable one-click PyArmor deobfuscator for current versions. This article is about understanding that as a developer — not a cracking guide.

"PyArmor deobfuscator" is a common search, and it comes from two very different places: developers deciding whether PyArmor is strong enough to trust with their own code, and people hoping to reverse someone else's protected app. This article answers the honest technical question — *how reversible is PyArmor?* — from the defender's side. It is not a step-by-step guide to cracking anyone's software.

To answer it properly I obfuscated a real script with PyArmor 9.2.6, then pointed the standard Python reverse-engineering toolkit at the result to see what comes back. Here's what actually happened.

Why PyArmor is different from a .pyc

The reason plain compiled Python is trivial to reverse is that a .pyc is just a 16-byte header plus a marshal-serialized code object — the interpreter's own native format. Tools like uncompyle6, pycdc, or even the standard library's marshal + dis read it directly, which is why shipping .pyc (or a PyInstaller exe, which bundles .pyc) protects almost nothing — as I showed in decompiling a .pyc and decompiling a PyInstaller exe.

PyArmor breaks that chain. Instead of leaving a normal code object on disk, it replaces your script with a small bootstrap that hands an encrypted payload to a native C runtime (pyarmor_runtime.pyd/.so), which decrypts and runs it in memory. There is no standard .pyc to feed a decompiler — see what PyArmor produces for the full breakdown.

The test: standard tools vs PyArmor output

I obfuscated a small script with pyarmor gen. The obfuscated file is a bootstrap containing a 2,578-byte encrypted blob; the actual runtime is a separate 632 KB native .pyd. First question: is there any normal .pyc anywhere to decompile? No — the only .pyc in the output is the runtime package's own __init__ wrapper, and it contains none of the program's logic or strings:

$ find dist -name '*.pyc'
dist/pyarmor_runtime_000000/__pycache__/__init__.cpython-311.pyc # runtime wrapper only
$ grep -c 'LT-PRO-2026-5590' dist/lictool.py
0
$ grep -c 'sk-lictool...' dist/pyarmor_runtime_000000/pyarmor_runtime.pyd
0
searching the PyArmor output for the original secrets — nothing

So the hard-coded API key and license string that come out in plain text from a normal .pyc are nowhere to be found here. Second question: can the standard-library toolkit read the encrypted payload the way it reads a .pyc body? I pulled out the embedded byte-string and tried marshal.loads on it:

>>> blob[:8]
b'PY000000' # PyArmor's own container tag, not a .pyc magic
>>> marshal.loads(blob) # this reads a normal .pyc body fine
ValueError: bad marshal data (unknown type code)
>>> marshal.loads(blob[8:]) # skip the tag - still not marshal
ValueError: bad marshal data (unknown type code)
the standard .pyc toolkit fails on the PyArmor payload

bad marshal data is the honest headline: the payload isn't a serialized code object at all, it's encrypted. uncompyle6, decompyle3, and pycdc all rely on getting a real code object to work on, so they have nothing to start from. The easy attacks that defeat .pyc and PyInstaller simply don't apply.

This is the practical difference between *obfuscation* and PyArmor's approach: it's closer to encryption-with-a-native-decryptor than to the reversible source transforms most "obfuscators" apply. That's why it holds up where plain compilation doesn't.

So is PyArmor unbreakable? No — and here’s the honest nuance

"The standard tools can't read it" is not the same as "it can never be reversed." Two honest caveats matter:

  • Code must run to work. At execution time the native runtime decrypts your code and the interpreter runs real bytecode. In principle, someone with deep skills, a debugger, and enough time can observe that — which is why no client-side protection is ever *absolute*. It raises the cost of reversing enormously; it doesn't make it mathematically impossible.
  • It's a moving target. PyArmor is an active cat-and-mouse product. Older generations (the pre-9 pytransform model) had weaknesses that security researchers documented and that Dashingsoft then hardened; current versions add stronger modes (RFT renaming, and BCC compiling functions to C). Whatever was true about an old version often isn't true about the current one — and any "PyArmor unpacker" you find is usually tied to a specific, older release.

Put together: there is no dependable, general, one-click PyArmor deobfuscator for current versions. If you're evaluating PyArmor to protect your own code, that's a genuinely strong position. If you were hoping to find a tool to reverse someone else's protected application, the honest answer is that it doesn't reliably exist — and attacking software you don't own may be illegal where you live.

What this actually means if you’re protecting code

The useful takeaways from all this are for defenders:

  1. PyArmor is a strong deterrent — among the strongest off-the-shelf options. If your threat model is casual copying, competitors, or resellers, it raises the bar far above shipping .pyc or a PyInstaller exe. See how it compares in PyArmor vs Nuitka vs Cython vs Pyobfuscate.
  2. Strong obfuscation still isn't secrecy for real secrets. Because the program must decrypt itself to run, a determined attacker at runtime could in theory recover an embedded key. So even with PyArmor, keep real API keys and license secrets server-side, not baked into the client — how to protect API keys in Python.
  3. Layer your defenses. Obfuscation controls *how readable* your code is; it doesn't control *who runs it*. Pair it with server-side license validation and hardware binding so a bypass of one layer doesn't hand over everything.
  4. If you don't need PyArmor-grade protection, a free AST obfuscator like our Python Obfuscator still meaningfully deters casual reading (it's a lower bar than PyArmor's encrypted runtime — honestly — but free and with no runtime to ship), and native compilers like Nuitka/Cython raise the bar a different way.

The broader principle — that any code which runs can ultimately be analyzed, so protection is about cost, not impossibility — is covered in is Python obfuscation secure? and protecting Python source code.

See how obfuscation changes what a decompiler recovers

Our free AST Python Obfuscator renames identifiers and encrypts strings before you ship — so even decompiled output is unreadable. In your browser, no signup.

Open the Python Obfuscator

Free tools mentioned here

Related guides

Frequently asked questions

Can PyArmor be deobfuscated?

Not with the standard Python decompiler tools. PyArmor doesn't leave a normal .pyc on disk — your code becomes an encrypted payload run by a native C runtime, so marshal, dis, uncompyle6 and pycdc have nothing to work on (in testing, the payload wasn't even valid marshal data). It isn't mathematically unbreakable, since code must decrypt itself to run and old versions had weaknesses that were patched, but there's no reliable general deobfuscator for current versions.

Is there a PyArmor deobfuscator online?

There's no dependable, general online tool that reverses current PyArmor output back to source. Because PyArmor encrypts your code and runs it through a native runtime, the usual online decompilers (which expect a standard .pyc) can't process it. Any "PyArmor unpacker" you find is typically tied to a specific older version and won't work on the current release, which is actively hardened against exactly that.

Is PyArmor actually secure?

It's one of the strongest off-the-shelf ways to protect Python. In testing, the hard-coded secrets that leak in plaintext from a normal .pyc were nowhere to be found in PyArmor's output, and standard decompilers failed on its encrypted payload. But no client-side protection is absolute — the code must run, so a determined attacker with runtime analysis could theoretically recover parts of it. Treat it as a strong deterrent, not encryption for real secrets.

Does PyArmor protect hard-coded API keys?

It hides them far better than plain compilation — grep found no plaintext secrets in PyArmor's output, unlike a decompiled .pyc. But because the program must decrypt itself to run, an embedded key is in principle recoverable at runtime by a skilled attacker. The safe rule stands regardless of obfuscator: don't ship real secrets in client-side code — load them from the environment or your own backend.

What is the strongest way to protect a Python script?

There's no single answer, but a layered approach is strongest: obfuscate or encrypt the code (PyArmor is a leading option, or a free AST obfuscator for a lower bar), keep real secrets server-side, validate licenses on your own server rather than client-side, and for maximum barrier compile sensitive parts to a native module with Cython or Nuitka. Each layer raises the cost of reversing; none makes it impossible.

Keep reading