Why "encrypt your Python source" isn't always protection
A look at the most common way people try to protect commercial Python code, why it comes apart, and what actually changes the math for an attacker.
You wrote a Python desktop app. People pay for it. You would like them to keep paying for it, rather than passing around a copy with the license check commented out. So you look for a way to protect it, and you find a familiar suggestion: encrypt your source.
The pitch is clean. A tool takes your .py files, encrypts them with AES-256 into some new extension (often .pye), and installs an import hook. At runtime, the hook decrypts each module back to Python and hands it to the interpreter. Your source is "encrypted at rest", the marketing says, and there is no performance hit once a module is loaded.
It sounds strong. AES-256 is strong. But the strength of the cipher is not the question. The question is the one every protection scheme has to answer: where does the key live, and what do you get when you decrypt?
The two questions
Where does the key live? For the app to run on your customer's machine, it has to decrypt itself on your customer's machine. That means the key, or everything needed to derive it, has to be present locally when the program runs. It ships with your app. An attacker does not need to break AES. They need to find the key that is already sitting in front of them, or simply let the program decrypt itself and read the result out of memory.
What do you get when you decrypt? With the encrypt-your-source approach, the answer is the worst possible one: you get the original source. That is the entire point of the technique. The plaintext that appears in memory is your actual .py, comments and all. Recover it once and you have not just something that runs; you have the thing you were trying to hide.
Put those two together and the outcome is not surprising. The key is local, and decryption yields your source, so recovering your source is a matter of running the decryption the program already knows how to do. This is not a hypothetical. For the popular .pye implementations, people have published turnkey decryptors, including browser-based ones, that take an encrypted file and hand back the original .py in seconds. The whole category has this shape.
None of this makes the tools authors dishonest or the AES implementation weak. It is an architectural ceiling. If the design goal is "reproduce the original source at runtime", then the original source is, by construction, recoverable.
What would actually change the math
An attacker's decision is economic. They will invest effort up to the point where cracking is cheaper than buying. So protection is not about being unbreakable, which nothing is. It is about raising the cost of recovery far above the price of a license. A few things move that cost a lot:
- Do not ship the key. If the material needed to decrypt is not present in the artifact, an attacker cannot simply extract it. It has to come from somewhere they do not control.
- Do not decrypt to source. If the thing that ever exists in memory is not your original
.py, then recovering it does not hand back your code. The further the runtime representation is from readable source, the more work reconstruction takes. - Do not hold the whole program in the clear at once. Decrypting everything up front, even into a non-source form, just relocates the problem. Decrypting narrowly, briefly, and per unit of work shrinks the window an attacker has to snapshot anything useful.
- Make dynamic analysis expensive. A native runtime that resists debuggers, instrumentation, and memory scraping turns "attach a tool and watch it decrypt" into real work.
Notice that none of these are about the cipher. They are about architecture: key custody, the form of the runtime representation, the size of the plaintext window, and the difficulty of watching the process. A scheme can use the exact same AES and be far harder to defeat, purely because of how those four questions are answered.
How we approached it
This is the part where I disclose that I build one of these tools, PyLocket, so read the rest with that in mind. I am including it because it is a concrete example of answering those four questions differently, not because you have to use ours.
- The key is never in the artifact. Instead of shipping a key, a protected app requests key material from a license-activation service, bound to the specific device, at activation time. The artifact you distribute does not contain what is needed to decrypt it.
- We do not decrypt to source. Code is encrypted at the level of individual functions, with authenticated encryption, and executed by a compiled native runtime. The interpreter is never handed your original
.py. There is nothing to reconstruct back into readable source. - The plaintext window is small. Functions are decrypted narrowly, for execution, and the decrypted form is kept in guarded memory and cleared afterward. The whole program is never sitting in the clear.
- The runtime resists analysis. The native runtime carries anti-debugging and anti-analysis defenses, so "attach a debugger and read it out" is not a five-minute job.
The honest framing is the same one I would apply to anyone's tool: this is not unbreakable. A determined, skilled reverser with enough time can chip away at anything. The goal is to move the cost of that from an afternoon to months, so that for essentially everyone it is cheaper to just buy your software license.
That is a real and worthwhile outcome, and it is a different outcome than "we AES your source and decrypt it back at import".
When encrypting your source with other tools is fine anyway
To be fair to the category: if you are giving away a free tool and you just want to keep casual eyes off your code, or you are on an older version of Python that modern runtimes do not support; an encrypt-your-source utility is simple and it is genuinely low-friction. It raises the bar above "nothing". Just go in knowing what it is: a speed bump, not a wall, and recoverable to your exact source by anyone who cares to.
If, on the other hand, you are selling the app and the source is the asset, ask the two questions before you pick a tool. Where does the key live? And what do you get when you decrypt? If the answers are "in the app" and "your original source", you have your answer about how much protection you are really buying.
PyLocket is a platform for protecting, licensing, and selling Python desktop apps: server-issued keys, a hardened native runtime, code-signed and identity-verified builds, and built-in licensing and delivery. If the threat-model view above is how you think about this, take a look.
Comments
Post a Comment