1.3. Basic Obfuscation¶
Contents
We’ll assume you have Pyarmor 8.0+ installed already. You can tell Pyarmor is installed and which version by running the following command in a shell prompt (indicated by the $ prefix):
$ pyarmor --version
If Pyarmor is installed, you should see the version of your installation. If it isn’t, you’ll get an error.
This tutorial is written for Pyarmor 8.0+, which supports Python 3.7 and later. If the Pyarmor version doesn’t match, you can refer to the tutorial for your version of Pyarmor by using the version switcher at the bottom right corner of this page, or update Pyarmor to the newest version.
Throughout this tutorial, assume run pyarmor in project path which includes:
project/
├── foo.py
├── queens.py
└── joker/
├── __init__.py
├── queens.py
└── config.json
Pyarmor uses pyarmor gen with rich options to obfuscate scripts to meet the needs of different applications.
Here only introduces common options in a short, using any combination of them as needed. About usage of each option in details please refer to pyarmor gen
1.3.1. More options to protect script¶
For scripts, use these options to get more security:
$ pyarmor gen --enable-jit --mix-str --assert-call foo.py
Using --enable-jit
tells Pyarmor processes some sentensive data by
c
function generated in runtime.
Using --mix-str
[1] could mix the string constant (length > 4) in the scripts.
Using --assert-call
makes sure function is obfuscated, to prevent
called function from being replaced by special ways
For example,
data = "abcxyz"
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
if __name__ == '__main__':
fib(n)
String constant abcxyz
and function fib
will be protected like this
data = __mix_str__(b"******")
def fib(n):
a, b = 0, 1
while a < n:
print(a, end=' ')
a, b = b, a+b
if __name__ == '__main__':
__assert_call__(fib)(n)
If function fib
is obfuscated, __assert_call__(fib)
returns original
function fib
. Otherwise it will raise protection exception.
[1] | --mix-str is not available in trial version |
1.3.2. More options to protect package¶
For package, append 2 extra options:
$ pyarmor gen --enable-jit --mix-str --assert-call --assert-import --restrict joker/
Using --assert-import
prevents obfsucated modules from being replaced
with plain script. It checks each import statement to make sure the modules are
obfuscated.
Using --restrict
makes sure the obfuscated module is only available
inside package. It couldn’t be imported from any plain script, also not be run
by Python interpreter.
By default __init__.py
is not restricted, in order to let others use your
package functions, just import them in the __init__.py
, then others could
get exported functions in the public __init__.py
.
In this test package, joker/__init__.py
is an empty file, so module
joker.queens
is not exported. Let’s check this, first create a script
dist/a.py
import joker
print('import joker OK')
from joker import queens
print('import joker.queens OK')
Then run it:
$ cd dist
$ python a.py
... import joker OK
... RuntimeError: unauthorized use of script
In order to export joker.queens
, edit joker/__init__.py
, add one
line
from joker import queens
Then do above test again, now it should work:
$ cd dist/
$ python a.py
... import joker OK
... import joker.queens OK
1.3.3. Checking runtime key periodically¶
Checking runtime key every hour:
$ pyarmor gen --period 1 foo.py
1.3.4. Binding to many machines¶
Using -b
many times to bind obfuscated scripts to many machines.
For example, machine A and B, the ethernet addresses are 66:77:88:9a:cc:fa
and f8:ff:c2:27:00:7f
respectively. The obfuscated script could run in both
of machine A and B by this command
$ pyarmor gen -b "66:77:88:9a:cc:fa" -b "f8:ff:c2:27:00:7f" foo.py
1.3.5. Using outer file to store runtime key¶
First obfuscating script with --outer
:
$ pyarmor gen --outer foo.py
In this case, it could not be run at this time:
$ python dist/foo.py
Let generate an outer runtime key valid for 3 days by this command:
$ pyarmor gen key -e 3
It generates a file dist/pyarmor.rkey
, copy it to runtime package:
$ cp dist/pyarmor.rkey dist/pyarmor_runtime_000000/
Now run dist/foo.py
again:
$ python dist/foo.py
Let’s generate another license valid for 10 days:
$ pyarmor gen key -O dist/key2 -e 10
$ ls dist/key2/pyarmor.rkey
Copy it to runtime package to replace the original one:
$ cp dist/key2/pyarmor.rkey dist/pyarmor_runtime_000000/
The outer runtime key file also could be saved to other paths, but the file name
must be pyarmor.rkey
, here list the search order:
- First search runtime package
- Next search path
PYARMOR_RKEY
- Next search path
sys._MEIPASS
- Next search current path
If no found in these paths, raise runtime error and exits.
1.3.6. Localization runtime error¶
Some of runtime error messages could be customized. When something is wrong with the obfuscated scripts, it prints your own messages.
First create messages.cfg
in the path .pyarmor
:
$ mkdir .pyarmor
$ vi .pyarmor/message.cfg
Then edit it. It’s a .ini
format file, change the error messages as needed
[runtime.message]
error_1 = this license key is expired
error_2 = this license key is not for this machine
error_3 = missing license key to run the script
error_4 = unauthorized use of script
Now obfuscate the script in the current path to use customized messages:
$ pyarmor gen foo.py
If we want to show same message for all of license errors, edit it like this
[runtime.message]
error_1 = invalid license key
error_2 = invalid license key
error_3 = invalid license key
Here no error_4
, it means this error uses the default message.
And then obfuscate the scripts again.
1.3.7. Packing obfuscated scripts¶
Pyarmor need PyInstaller to pack scripts first, then replace plain scripts with obfuscated ones in bundle.
1.3.7.1. Packing to one file¶
First packing script to one file by PyInstaller with option -F
:
$ pyinstaller -F foo.py
It generates one bundle file dist/foo
, pass this to pyarmor:
$ pyarmor gen -O obfdist --pack dist/foo foo.py
This command will obfuscate foo.py
first, then repack dist/foo
, replace
the original foo.py
with obfdist/foo.py
, and append all the runtime
files to bundle.
The final output is still dist/foo
:
$ dist/foo
1.3.7.2. Packing to one folder¶
First packing script to one foler by PyInstaller:
$ pyinstaller foo.py
It generates one bundle folder dist/foo
, and an executable file
dist/foo/foo
, pass this executable to pyarmor:
$ pyarmor gen -O obfdist --pack dist/foo/foo foo.py
Like above section, dist/foo/foo
will be repacked with obfuscated scripts.
Now run it:
$ dist/foo/foo