Advanced Topics

Using Super Mode

The Super Mode is introduced since v6.2.0, there is only one extension module required to run the obfuscated scripts, and the Bootstrap Code which may confused some users before is gone now, all the obfuscated scripts are same. It improves the security remarkably, and makes the usage simple. The only problem is that only the latest Python versions 2.7, 3.7, 3.8 and 3.9 are supported.

Enable super mode by option --advanced 2, for example:

pyarmor obfuscate --advanced 2

When distributing the obfuscated scripts to any other machine, so long as extension module pytransform in any Python path, the obfuscated scrips could work well.

In order to restirct the obfuscated scripts, generate a license.lic in advanced. For example:

pyarmor licenses --bind-mac xx:xx:xx:xx regcode-01

Then specify this license with option --with-license, for example:

pyarmor obfuscate --with-license licenses/regcode-01/license.lic \
                  --advanced 2

By this way the specified license file will be embedded into the extension module pytransform. If you prefer to use outer license.lic, so it can be replaced with the others easily, just set option --with-license to special value outer, for example:

pyarmor obfuscate --with-license outer --advanced 2

More information, refer to next section.

How to use outer license file

Since v6.3.0, the runtime file license.lic has been embeded to dynamic library. If you prefer to use outer license.lic, so it can be replaced with the others easily, just set option --with-license to special value outer, for example:

pyarmor obfuscate --with-license outer

When the obfuscated scripts start, it will search license.lic in order:

  1. Check environment variable PYARMOR_LICENSE, if set, use this filename
  2. Check sys.PYARMOR_LICENSE, if set use this filename
  3. If it’s not set, search license.lic in the current path
  4. For non super mode, search license.lic in the path of runtime package pytransform
  5. Raise exception if there is still not found

Here it’s the basic usage of sys.PYARMOR_LICENSE

For non super mode, edit the function pyarmor_runtime in the runtime file dist/pytransform/, add one line:

sys.PYARMOR_LICENSE = '/path/to/license.lic'

For super mode, convert python extension to same name package pytransform. For example:

cd dist
mkdir pytransform
mv pytransform/

Then create dist/pytransform/

import sys
sys.PYARMOR_LICENSE = '/path/to/license.lic'
name = 'pytransform'
m = __import__(name, globals(), locals(), ['*'])

Obfuscating Many Packages

There are 3 packages: pkg1, pkg2, pkg2. All of them will be obfuscated, and use shared runtime files.

First change to work path, create 3 projects:

mkdir build
cd build

pyarmor init --src /path/to/pkg1 --entry pkg1
pyarmor init --src /path/to/pkg2 --entry pkg2
pyarmor init --src /path/to/pkg3 --entry pkg3

Then make the Runtime Package, save it in the path dist:

pyarmor build --output dist --only-runtime pkg1

Or run command runtime to generate Runtime Package directly:

pyarmor runtime --output dist

Next obfuscate 3 packages, save them in the dist:

pyarmor build --output dist --no-runtime pkg1
pyarmor build --output dist --no-runtime pkg2
pyarmor build --output dist --no-runtime pkg3

Check all the output and test these obfuscated packages:

ls dist/

cd dist
python -c 'import pkg1
import pkg2
import pkg3'


The runtime package pytransform in the output path dist also could be move to any other Python path, only if it could be imported.

From v5.7.2, the Runtime Package also could be generate by command runtime separately:

pyarmor runtime

Solve Conflicts With Other Obfuscated Libraries


New in v5.8.7

Suppose there are 2 packages obfuscated by different developers, could they be imported in the same Python interpreter?

If both of them are obfuscated by trial version of pyarmor, no problem, the answer is yes. But if anyone is obfuscated by registerred version, the answer is no.

Since v5.8.7, the scripts could be obfuscated with option --enable-suffix to generate the Runtime Package with an unique suffix, other than fixed name pytransform. For example:

pyarmor obfuscate --enable-suffix

The output would be like this:


The suffix _vax_000001 is based on the registration code of PyArmor.

For project, set enable-suffix by command config:

pyarmor config --enable-suffix 1
pyarmor build -B

Or disable it by this way:

pyarmor config --enable-suffix 0
pyarmor build -B

Distributing Obfuscated Packages

If there are many packages to distribute, it’s recommend to generate a Runtime Package with enable suffix separately and share it for all of these packages.

For example, first generate Runtime Package by command runtime:

pyarmor runtime --enable-suffix -O dist/shared

The output package may looks like dist/shared/pytransform_vax_000001

For each package, obfuscated it with this shared pytransform:

pyarmor obfuscate --enable-suffix --recursive --bootstrap 2 \
                  -O dist/pkg1 --runtime @dist/shared src/pkg1/

If option --runtime is not available, it’s new in v6.3.7, replace it with --no-runtime:

pyarmor obfuscate --enable-suffix --recursive --bootstrap 2 \
                  -O dist/pkg1 --no-runtime src/pkg1/

Then distribute package pytransform_vax_000001 as a normal package.

Finally, distribute obfuscated package dist/pkg1, add a dependency in setup script. For example:


Do the same thing as pkg1 for other packages pkg2, pkg3 etc.

Distributing Obfuscated Scripts To Other Platform

First list all the avaliable platform names by command download:

pyarmor download
pyarmor download --help-platform

Display the detials with option --list:

pyarmor download --list
pyarmor download --list windows
pyarmor download --list windows.x86_64

Then specify platform name when obfuscating the scripts:

pyarmor obfuscate --platform linux.armv7

# For project
pyarmor build --platform linux.armv7

Obfuscating scripts with different features

There may be many available dynamic libraries for one same platform. Each one has different features. For example, both of windows.x86_64.0 and windows.x86_64.7 work in the platform windwos.x86_64. The last number stands for the features:

  • 0: No anti-debug, JIT, advanced mode features, high speed
  • 7: Include anti-debug, JIT, advanced mode features, high security

It’s possible to obfuscate the scripts with special feature. For example:

pyarmor obfuscate --platform linux.x86_64.7

Note that the dynamic library with different features aren’t compatible. For example, try to obfuscate the scripts with --platform linux.arm.0 in Windows:

pyarmor obfuscate --platform linux.arm.0

Because the default platform is full features windows.x86_64.7 in Windows, so PyArmor have to reboot with platform windows.x86_64.0, then obfuscate the script for this low feature platform linux.arm.0.

It also could be set the enviornment variable PYARMOR_PLATFORM to same feature platform as target machine. For example:

PYARMOR_PLATFORM=windows.x86_64.0 pyarmor obfuscate --platform linux.arm.0

# In Windows
set PYARMOR_PLATFORM=windows.x86_64.0
pyarmor obfuscate --platform linux.arm.0

Running Obfuscated Scripts In Multiple Platforms

From v5.7.5, the platform names are standardized, all the available platform names list here Standard Platform Names. And the obfuscated scripts could be run in multiple platforms.

In order to support multiple platforms, all the dynamic libraries for these platforms need to be copied to Runtime Package. For example, obfuscating a script could run in Windows/Linux/MacOS:

pyarmor obfuscate --platform windows.x86_64 \
                  --platform linux.x86_64 \
                  --platform darwin.x86_64 \

The Runtime Package also could be generated by command runtime once, then obfuscate the scripts without runtime files. For examples:

pyarmor runtime --platform windows.x86_64,linux.x86_64,darwin.x86_64
pyarmor obfuscate --no-runtime --recursive \
                  --platform windows.x86_64,linux.x86_64,darwin.x86_64 \

Because the obfuscated scripts will check the dynamic library, the platforms must be specified even if there is option --no-runtime. But if the option --no-cross-protection is specified, the obfuscated scripts will not check the dynamic library, so no platform is required. For example:

pyarmor obfuscate --no-runtime --recursive --no-cross-protection


If the feature number is specified in one of platform, for example, one is windows.x86_64.0, then all the other platforms must be same feature.


If the obfuscated scripts don’t work in other platforms, try to update all the downloaded files:

pyarmor download --update

If it still doesn’t work, try to remove the cahced platform files in the path $HOME/.pyarmor

Obfuscating Scripts By Other Python Version

If there are multiple Python versions installed in the machine, the command pyarmor uses default Python. In case the scripts need to be obfuscated by other Python, run pyarmor by this Python explicitly.

For example, first find

find /usr/local/lib -name

Generally it should be in the /usr/local/lib/python2.7/dist-packages/pyarmor in most of linux.

Then run pyarmor as the following way:

/usr/bin/python3.6 /usr/local/lib/python2.7/dist-packages/pyarmor/

It’s convenient to create a shell script /usr/local/bin/pyarmor3, the content is:

/usr/bin/python3.6 /usr/local/lib/python2.7/dist-packages/pyarmor/ "$@"


chmod +x /usr/local/bin/pyarmor3

then use pyarmor3 as before.

In the Windows, create a bat file pyarmor3.bat, the content would be like this:

C:\Python36\python C:\Python27\Lib\site-packages\pyarmor\ %*

Run bootstrap code in plain scripts

Before v5.7.0 the Bootstrap Code could be inserted into plain scripts directly, but now, for the sake of security, the Bootstrap Code must be in the obfuscated scripts. It need another way to run the Bootstrap Code in plain scripts.

First create one bootstrap package pytransform_bootstrap by command runtime:

pyarmor runtime -i

Next move bootstrap package to the path of plain script:

mv dist/pytransform_bootstrap /path/to/script

It also could be copied to python system library, for examples:

mv dist/pytransform_bootstrap /usr/lib/python3.5/ (For Linux)
mv dist/pytransform_bootstrap C:/Python35/Lib/ (For Windows)

Then edit the plain script, insert one line:

import pytransform_bootstrap

Now any other obfuscated modules could be imported after this line.


Before v5.8.1, create this bootstrap package by this way:

echo "" >
pyarmor obfuscate -O dist/pytransform_bootstrap --exact

Run unittest of obfuscated scripts

In most of obfuscated scripts there are no Bootstrap Code. So the unittest scripts may not work with the obfuscated scripts.

Suppose the test script is /path/to/tests/, first patch this test script, refer to run bootstrap code in plain scripts

After that it works with the obfuscated modules:

cd /path/to/tests

The other way is patch system package unittest directly. Make sure the bootstrap package pytransform_bootstrap is copied in the Python system library, refer to run bootstrap code in plain scripts

Then edit /path/to/unittest/, insert one line:

import pytransform_bootstrap

Now all the unittest scripts could work with the obfuscated scripts. It’s useful if there are many unittest scripts.

Let Python Interpreter Recognize Obfuscated Scripts Automatically

In a few cases, if Python Interpreter could recognize obfuscated scripts automatically, it will make everything simple:

  • Almost all the obfuscated scripts will be run as main script
  • In the obfuscated scripts call multiprocessing to create new process
  • Or call Popen, os.exec etc. to run any other obfuscated scripts

Here are the base steps:

  1. First create one bootstrap package pytransform_bootstrap:

    pyarmor runtime -i

    Before v5.8.1, it need be created by obfuscating an empty package:

    echo "" >
    pyarmor obfuscate -O dist/pytransform_bootstrap --exact
  2. Then create virtual python environment to run the obfuscated scripts, move the bootstrap package to virtual python library. For example:

    # For windows
    mv dist/pytransform_bootstrap venv/Lib/
    # For linux
    mv dist/pytransform_bootstrap venv/lib/python3.5/
  1. Edit venv/lib/ or venv/lib/pythonX.Y/, import pytransform_bootstrap before the main line:

    import pytransform_bootstrap
    if __name__ == '__main__':

It also could be inserted into the end of function main, or anywhere they could be executed as module site is imported.

After that in the virtual environment python could run the obfuscated scripts directly, because the module site is automatically imported during Python initialization.

Refer to


The command pyarmor doesn’t work in this virtual environment, it’s only used to run the obfuscated scripts.


Before v5.7.0, you need create the bootstrap package by the Runtime Files manually.

Obfuscating Python Scripts In Different Modes

Advanced Mode is introduced from PyArmor 5.5.0, it’s disabled by default. Specify option --advanced to enable it:

pyarmor obfuscate --advanced 1

# For project
cd /path/to/project
pyarmor config --advanced 1
pyarmor build -B

From PyArmor 5.2, the default Restrict Mode is 1. It could be changed by the option --restrict:

pyarmor obfuscate --restrict=2
pyarmor obfuscate --restrict=3

# For project
cd /path/to/project
pyarmor config --restrict 4
pyarmor build -B

All the restricts could be disabled by this way if required:

pyarmor obfuscate --restrict=0

# For project
pyarmor config --restrict=0
pyarmor build -B

If the obfuscates scripts uses the license generated by licenses, in order to disable all the restricts, pass option --disable-restrict-mode to command licenses. For example:

pyarmor licenses --disable-restrict-mode r001

pyarmor obfuscate --with-license=licenses/r001/license.lic

# For project
pyarmor config --with-license=licenses/r001/license.lic
pyarmor build -B

The modes of Obfuscating Code Mode, Wrap Mode, Obfuscating module Mode could not be changed in command obfuscate. They only could be changed by command config when Using Project. For example:

pyarmor init --src=src .
pyarmor config --obf-mod=1 --obf-code=1 --wrap-mode=0
pyarmor build -B

Using Plugin to Extend License Type

PyArmor could extend license type for obfuscated scripts by plugin. For example, check internet time other than local time.

First create plugin script The key function in this script is check_ntp_time, the other important function is _get_license_data which used to get extra data from the license.lic of obfuscated scripts.

Then insert 2 comments in the entry script

# {PyArmor Plugins}
# PyArmor Plugin: check_ntp_time()

Now obfuscate entry script:

pyarmor obfuscate --plugin check_ntp_time

If the plugin file isn’t in the current path, use absolute path instead:

pyarmor obfuscate --plugin /usr/share/pyarmor/check_ntp_time

Finally generate one license file for this obfuscated script, pass extra license data by option -x, this data could be got by function _get_license_data in the plugin script:

pyarmor licenses -x 20190501 rcode-001
pyarmor obfuscate --with-license licenses/rcode-001/license.lic \
                  --plugin check_ntp_time

For command pack:

pyarmor licenses -x 20190501 rcode-001
pyarmor pack --with-license licenses/rcode-001/license.lic \
             -x " --plugin check_ntp_time"

More examples, refer to

About how plugins work, refer to How to Deal With Plugins


The output function name in the plugin must be same as plugin name, otherwise the plugin will not take effects.

Bundle Obfuscated Scripts To One Executable File

Run the following command to pack the script to one executable file dist/foo.exe. Here isn’t obfuscated, it will be obfuscated before packing:

pyarmor pack -e " --onefile"

If you don’t want to bundle the license.lic of the obfuscated scripts into the executable file, but put it outside of the executable file. For example:


So that we could generate different licenses for different users later easily. Here are basic steps:

  1. First create runtime-hook script

    import sys
    from os.path import join, dirname
    with open(join(dirname(sys.executable), 'license.lic'), 'rb') as fs:
        with open(join(sys._MEIPASS, 'license.lic'), 'wb') as fd:
  2. Then pack the scirpt with extra options:

    pyarmor pack --clean --without-license -x " --exclude" \
            -e " --onefile --icon logo.ico --runtime-hook"

Option --without-license tells pack not to bundle the license.lic of obfuscated scripts to the final executable file. By option --runtime-hook of PyInstaller, the specified script will be executed before any obfuscated scripts are imported. It will copy outer license.lic to right path.

Try to run dist/foo.exe, it should report license error.

  1. Finally run licenses to generate new license for the obfuscated scripts, and copy new license.lic and dist/foo.exe to end users:

    pyarmor licenses -e 2020-01-01 code-001
    cp license/code-001/license.lic dist/

Bundle obfuscated scripts with customized spec file

If there is a customized .spec file works, for example:

pyinstaller myscript.spec

Refer to repack pyinstaller bundle with obfuscated scripts

Or obfuacate and pack scripts with option -s directly:

pyarmor pack -s myscript.spec

If it raises this error:

Unsupport .spec file, no XXX found

Check .spec file, make sure there are 2 lines in top level (no identation):

a = Analysis(...

pyz = PYZ(...

And there are 3 key parameters when creating an Analysis object, for example:

a = Analysis(

PyArmor will append required options to these lines automatically. But before v5.9.6, it need to be patched by manual:

  • Add module pytransform to hiddenimports
  • Add extra path DISTPATH/obf/temp to pathex and hookspath

After changed, it may be like this:

a = Analysis([''],
             pathex=[os.path.join(DISTPATH, 'obf', 'temp'), ...],
             hiddenimports=['pytransform', ...],
             hookspath=[os.path.join(DISTPATH, 'obf', 'temp'), ...],


This featuer is introduced since v5.8.0

Before v5.8.2, the extra path is DISTPATH/obf, not DISTPATH/obf/temp

Improving The Security By Restrict Mode

By default the scripts are obfuscated by restrict mode 1, that is, the obfuscated scripts can’t be changed. In order to improve the security, obfuscating the scripts by restrict mode 2 so that the obfuscated scripts can’t be imported out of the obfuscated scripts. For example:

pyarmor obfuscate --restrict 2

Or obfuscating the scripts by restrict mode 3 for more security. It will even check each function call to be sure all the functions are called in the obfuscated scripts. For example:

pyarmor obfuscate --restrict 3

However restrict mode 2 and 3 aren’t applied to Python package. There is another solution for Python package to improve the security:

  • The .py files which are used by outer scripts are obfuscated by restrice mode 1
  • All the other .py files which are used only in the package are obfuscated by restrict mode 4

For example, mypkg includes 2 files:


Here it’s the content of mypkg/

from .foo import hello

def open_hello(msg):
    print('This is public hello: %s' % msg)

def proxy_hello(msg):
    print('This is proxy hello from foo: %s' % msg)

Now obfuscate this package by this way:

cd /path/to/mypkg
pyarmor obfuscate -O obf/mypkg --exact
pyarmor obfuscate -O obf/mypkg --restrict 4 --recursive --exclude .

So it’s OK to import mypkg and call any function in the

cd /path/to/mypkg/obf

>>> import mypkg
>>> mypkg.open_hello("it should work")
>>> mypkg.proxy_hello("also OK")

But it doesn’t work to call any function in the For example:

cd /path/to/mypkg/obf

>>> import mypkg
>>>"it should not work")

More information about restrict mode, refer to Restrict Mode

Using Plugin To Improve Security

By plugin any private checkpoint could be injected into the obfuscated scripts, and it doesn’t impact the original scripts. Most of them must be run in the obfuscated scripts, if they’re not commented as plugin, it will break the plain scripts.

No one knows your check logic, and you can change it in anytime. So it’s more security. For example, check there is debugger process, check the sum of byte code of caller, which could be got by sys._getframe etc.

Using Inline Plugin To Check Dynamic Library

Althouth PyArmor provides cross protection, it also could check the dynamic library in the startup to make sure it’s not changed by others. This example uses inline plugin to check the modified time protecting the dynamic library by inserting the following comment to

# PyArmor Plugin: import os
# PyArmor Plugin: libname = os.path.join( os.path.dirname( __file__ ), '' )
# PyArmor Plugin: if not os.stat( libname ).st_mtime_ns == 102839284238:
# PyArmor Plugin:     raise RuntimeError('Invalid Library')

Then obfuscate the script and enable inline plugin by this way:

pyarmor obfuscate --plugin on

Once the obfuscated script starts, the following plugin code will be run at first

import os
libname = os.path.join( os.path.dirname( __file__ ), '' )
if not os.stat( libname ).st_mtime_ns == 102839284238:
    raise RuntimeError('Invalid Library')

Checking Imported Function Is Obfuscated

In the pytransform there is one decorator assert_armored() and one function check_armored() used to make sure the imported functions from other module are obfuscated.

For example, there are 2 scripts and

# This is

import foo

def start_server():
    foo.connect('root', 'root password')
    foo.connect2('user', 'user password')

# This is

def connect(username, password):
    mysql.dbconnect(username, password)

def connect2(username, password):
    db2.dbconnect(username, password)

In the, it need to be sure foo.connect is obfuscated. Otherwise the end users may replace the obfuscated with this plain script, and run the obfuscated

def connect(username, password):
    print('password is %s', password)

The password is stolen, in order to avoid this, use decorator function to make sure the function connect is obfuscated by plugin.

Now let’s edit, insert inline plugin code

import foo

# PyArmor Plugin: from pytransform import assert_armored

# PyArmor Plugin: @assert_armored(foo.connect, foo.connect2)
def start_server():
    foo.connect('root', 'root password')

Then obfuscate it with plugin on:

pyarmor obfuscate --plugin on

The obfuscated script would be like this

import foo

from pytransform import assert_armored

@assert_armored(foo.connect, foo.connect2)
def start_server():
    foo.connect('root', 'root password')

Before call start_server, the decorator function assert_armored will check both connect functions are pyarmored, otherwise it will raise exception.

You can also check it by check_armored()

import foo

from pytransform import check_armored

def start_server():

    if not check_armored(foo.connect, foo.connect2):
        print('Found hacker')

    foo.connect('root', 'root password')

Call pyarmor From Python Script

It’s also possible to call PyArmor methods inside Python script not by os.exec or subprocess.Popen etc. For example

from pyarmor.pyarmor import main as call_pyarmor
call_pyarmor(['obfuscate', '--recursive', '--output', 'dist', ''])

In order to suppress all normal output of pyarmor, call it with --silent

from pyarmor.pyarmor import main as call_pyarmor
call_pyarmor(['--silent', 'obfuscate', '--recursive', '--output', 'dist', ''])

From v5.7.3, when pyarmor called by this way and something is wrong, it will raise exception other than call sys.exit.

Generating license key by web api

It’s also possible to generate license key as string other than writing to a file inside Python script. It may be useful in case the new license need to be generated by web api.

from pyarmor.pyarmor import licenses as generate_license_key
lickey = generate_license_key(name='reg-001',
                              bind_data='any string')
print('Generate key: %s' % lickey)

If there are more than one product need generate licenses from one Web API, set keyword home to each registerred product. For example

from pyarmor.pyarmor import licenses as generate_license_key
lickey = generate_license_key(name='product-001',
print('Generate key for product 1: %s' % lickey)

lickey = generate_license_key(name='product-002',
print('Generate key for product 2: %s' % lickey)

Check license periodly when the obfuscated script is running

Generally only at the startup of the obfuscated scripts the license is checked. Since v5.9.3, it also could check the license per hour. Just generate a new license with --enable-period-mode and overwrite the default one. For example:

pyarmor obfuscate
pyarmor licenses --enable-period-mode code-001
cp licenses/code-001/license.lic ./dist

Work with Nuitka

Because the obfuscated scripts could be taken as normal scripts with an extra runtime package pytransform, they also could be translated to C program by Nuitka. When obfuscating the scripts, the option --restrict 0 and --no-cross-protection should be set, otherwise the final C program could not work. For example, first obfustate the scripts:

pyarmor obfuscate --restrict 0 --no-cross-protection --package-runtime 0

Then translate the obfuscated one as normal python scripts by Nuitka:

cd ./dist
python -m nuitka --include-package=pytransform

There is one problem is that the imported modules (packages) in the obfuscated scripts could not be seen by Nuitka. To fix this problem, first generate the corresponding .pyi with original script, then copy it within the obfuscated one. For example:

# Generating "mymodule.pyi"
python -m nuitka --module

pyarmor obfuscate --restrict 0 --no-bootstrap --package-runtime 0
cp mymodule.pyi dist/

cd dist/
python -m nuitka --module

But it may not take advantage of Nuitka features by this way, because most of byte codes aren’t translated to c code indeed.


So long as the C program generated by Nuitka is linked against libpython to execute, pyarmor could work with Nuitka. But in the future, just as said in the Nuitka official website:

It will do this - where possible - without accessing libpython but in C
with its native data types.

In this case, pyarmor maybe not work with Nuitka.

Work with Cython

Here it’s an example show how to cythonize a python script obfuscated by pyarmor with Python37:

print('Hello Cython')

First obfuscate it with some extra options:

pyarmor obfuscate --package-runtime 0 --no-cross-protection --restrict 0

The obfuscated script and runtime files will be saved in the path dist, about the meaning of each options, refer to command obfuscate.

Next cythonize both and with extra options -k and --lenient to generate foo.c and pytransform.c:

cd dist
cythonize -3 -k --lenient

Without options -k and --lenient, it will raise exception:

undeclared name not builtin: __pyarmor__

Then compile foo.c and pytransform.c to the extension modules. In MacOS, just run the following commands, but in Linux, with extra cflag -fPIC:

gcc -shared $(python-config --cflags) $(python-config --ldflags) \
     -o foo$(python-config --extension-suffix) foo.c

gcc -shared $(python-config --cflags) $(python-config --ldflags) \
    -o pytransform$(python-config --extension-suffix) pytransform.c

Finally test it, remove all the .py files and import the extension modules:

mv /tmp
python -c 'import foo'

It will print Hello Cython as expected.

Work with PyUpdater

PyArmor should work with PyUpdater by this way, for example, there is a script

  1. Generate foo.spec by PyUpdater

  2. Generate foo-patched.spec by pyarmor with option --debug:

    pyarmor pack --debug -s foo.spec
    # If the final executable raises protection error, try to disable restirct mode
    # by the following extra options
    pyarmor pack --debug -s foo.spec -x " --restrict 0 --no-cross-protection"

This patched foo-patched.spec could be used by PyUpdater in build command

If your Python scripts are modified, just obfuscate them again, all the options for command obfuscate could be got from the output of command pack

If anybody is having issues with the above. Just normally compiling it in PyArmor then zipping and putting it into “/pyu-data/new” works. From there on you can just normally sign, process and upload your update.

More information refer to the description of command pack and advanced usage Bundle obfuscated scripts with customized spec file

Binding obfuscated scripts to Python interpreter

In order to improve the security of obfuscated scripts, it also could bind the obfuscated scripts to one fixed Python interperter, the obfuscated scripts will not work if the Python dynamic library are changed.

If you use command obfuscate, after the scripts are obfuscated, just generate a new license.lic which is bind to the current Python and overwrite the default license. For example:

pyarmor licenses --fixed 1 -O dist/license.lic

When start the obfuscated scripts in target machine, it will check the Python dynamic library, it may be pythonXY.dll, or libpythonXY.dylib in different platforms. If this library is different from the python dynamic library in build machine, the obfuscated script will quit.

If you use project to obfuscate scripts, first generate a fixed license:

cd /path/to/project
pyarmor licenses --fixed 1

By default it will be saved to licenses/pyarmor/license.lic, then configure the project with this license:

pyarmor config --license=licenses/pyarmor/license.lic

If obfuscate the scripts for different platform, first get the bind key in target platform. Create a script then run it with Python interpreter which would be bind to:

from ctypes import CFUNCTYPE, cdll, pythonapi, string_at, c_void_p, c_char_p
from sys import platform

def get_bind_key():

    if platform.startswith('win'):
        from ctypes import windll
        dlsym = windll.kernel32.GetProcAddressA
        prototype = CFUNCTYPE(c_void_p, c_void_p, c_char_p)
        dlsym = prototype(('dlsym', cdll.LoadLibrary(None)))

    refunc1 = dlsym(pythonapi._handle, b'PyEval_EvalCode')
    refunc2 = dlsym(pythonapi._handle, b'PyEval_GetFrame')

    size = refunc2 - refunc1
    code = string_at(refunc1, size)

    print('Get bind key: %s' % sum(bytearray(code)))

if __name__ == '__main__':

It will print the bind key xxxxxx, then generate one fixed license with this bind key:

pyarmor licenses --fixed xxxxxx -O dist/license.lic

It also could bind the license to many Python interpreters by passing multiple keys separated by ,:

pyarmor licenses --fixed 1,key2,key3 -O dist/license.lic
pyarmor licenses --fixed key1,key2,key3 -O dist/license.lic

The special key 1 means current Python interpreter.


Do not use this feature in 32-bit Windows, because the bind key is different in different machine, it may be changed even if python is restarted in the same machine.

Customizing cross protection code

In order to protect core dynamic library of PyArmor, the default protection code will be injected into the entry scripts, refer to Special Handling of Entry Script. However this public protection code may be bypassed deliberately, the better way is to write your private protection code, it could improve the security largely.

Since v6.2.0, command runtime could generate the default protection code, it could be as template to write your own protection code. Of course, you may write it by yourself. Only if it could make sure the runtime files aren’t changed by someone else as running the obfuscated scripts.

First generate protection script build/

pyarmor runtime --advanced 2 --output build

Then edit it with your private code, after that, obfuscate the scripts and set option --cross-protection to this customized script, for example:

pyarmor obfuscate --cross-protection build/ \
              --advanced 2


The option --advanced in command obfuscate must be same as in command runtime, because the runtime files may be different totaly.

Storing runtime file license.lic to any location

By creating a symbol link in the runtime package, it’s easy to store runtime file license.lic to any location when running the obfuscated scripts.

In linux, for example, store license file in /opt/my_app:

ln -s /opt/my_app/license.lic /path/to/obfuscated/pytransform/license.lic

In windows, store license file in C:/Users/Jondy/my_app:

mklink \path\to\obfuscated\pytransform\license.lic C:\Users\Jondy\my_app\license.lic

When distributing the obfuscated package, just run this function on post-install:

import os

def make_link_to_license_file(package_path, target_license="/opt/mypkg/license.lic"):
    license_file = os.path.join(package_path, 'pytransform', 'license.lic')
    if os.path.exists(license_file):
        os.rename(license_file, target_license)
    os.symlink(target_license, license_file)

Register multiple pyarmor in same machine

From v5.9.0, pyarmor reads license and capsule data from environment variable PYARMOR_HOME, the default value is ~/.pyarmor. So it’s easy to register multiple pyarmor in one machine by setting environment variable PYARMOR_HOME to another path before run pyarmor.

It also could create a new command pyarmor2 for the second project by the following way.

In Linux, create a shell script pyarmor2

export PYARMOR_HOME=$HOME/.pyarmor_2
pyarmor "$@"

Save it to /usr/local/pyarmor2, and change its mode:

chmod +x /usr/local/pyarmor2

In Windows, create a bat script pyarmor2.bat

SET PYARMOR_HOME=%HOME%\another_pyarmor
pyarmor %*

After that, run pyarmor2 for the second project:

pyarmor2 register
pyarmor2 obfuscate

How to get license information of one obfuscated package

How to get the license information of one obfuscated package? Since v6.2.5, just run this script in the path of runtime package pytransform

from pytransform import pyarmor_init, get_license_info
licinfo = get_license_info()
print('This obfuscated package is issued by %s' % licinfo['ISSUER'])
print('License information:')

For the scripts obfuscated by super mode, there is no package pytransform, but an extension pytransform. It’s simiar and more simple

from pytransform import get_license_info
licinfo = get_license_info()
print('This obfuscated package is issued by %s' % licinfo['ISSUER'])
print('License information:')

Since v6.2.7, it also could call the helper script by this way:

cd /path/to/obfuscated_package
python -m pyarmor.helper.get_license_info

How to protect data files

This is still an experiment feature.

PyArmor does not touch data files, but it could wrap data file to python module, and then obfuscate this data module by restrict mode 4, so that it only could be imported from the obfuscated scripts. By this way, the data file could be protected by PyArmor.

Since v6.2.7, there is a helper script which could create a python module from data file, for example:

python -m pyarmor.helper.build_data_module data.txt >

Next obfuscate this data module with restrict mode 4:

pyarmor obfuscate --exact --restrict 4 --no-runtime

After that, use the data file in other obfuscated scripts. For example:

import data

# Here load the content of data file to memory variable "text"
# And clear it from memory as exiting the context
with data.Safestr() as text:

Before v6.2.7, download this helper script and run it directly:

python data.txt >

How to remove docstrings

By setting PYTHONOPTIMIZE=2 in the command line the docstrings could be removed from the obfuscated scripts. For examples:

# In linux
PYTHONOPTIMIZE=2 pyarmor obfuscate

# In Windows
pyarmor obfuscate

Using restrict mode with threading and multiprocessing

It may complain of protection exception if using multiprocessing or threading with restrict mode 3 and 4 directly. Because both of these system modules aren’t obfuscated, but they try to call the function in the restrict modules.

One solution is to extend system Thread to overwrite its method run with lambda function. For example,

from threading import Thread

class PrivateThread(Thread):

    def lambda_run(self):
            if self._target:
                self._target(*self._args, **self._kwargs)
            del self._target, self._args, self._kwargs

    run = lambda self : self.lambda_run()

def foo():

t = PrivateThread(target=foo)

If you have extended system Thread and defined method run by yourself, just rename run to lambda_run, and add lambda method run. For example

from threading import Thread

class MyThread(Thread):

    # def run(self):
    def lambda_run(self):

    # Define a lambda method `run`
    run = lambda self : self.lambda_run()

Another solution is to define a public module with restrict mode 1, let plain scripts call functions in this public module.

For example, here is a script using public module

import multiprocessing as mp
import pub_foo

def hello(q):
    print('module name: %s' % __name__)

if __name__ == '__main__':
    ctx = mp.get_context('spawn')
    q = ctx.Queue()
    # call "proxy_hello" instead private "hello"
    p = ctx.Process(target=pub_foo.proxy_hello, args=(q,))

The content of public module

import foo

def proxy_hello(q):
    return foo.hello(q)

Now obfuscate with mode 3 and with mode 1:

pyarmor obfuscate --restrict 3

# both of options --exact and --no-runtime are required
pyarmor obfuscate --restrict 1 --exact --no-runtime

The third solution is to obfuscate system module threading or some modules in package multiprocessing with mode 1. Make sure the caller is obfuscated.

Repack PyInstaller bundle with obfuscated scripts

Since v6.5.5, PyArmor provides a helper script which is used to repack PyInstaller bundle with obfuscated scripts.

First pack the script by PyInstaller, next obfuscate the scripts by PyArmor, finally run this script to repack the bundle with obfuscated scripts.

  • Pack the script with PyInstaller, make sure the final bundle works. For real scripts, other options may be required, please check PyInstaller documentation. If the final bundle could not work in this step, please report issues to PyInstaller issues:

    # One folder mode
    # Check it works
    # If prefer to one file mode, run this command
    pyinstaller --onefile
    # Check it works
  • Obfuscate the scripts to “obfdist”, make sure the obfuscated scripts work. For real scripts, other options may be required, please check obfuscate to find more usages, and the scripts also could be obfuscated by build:

    # Option --package-runtime should be set to 0
    pyarmor obfuscate -O obfdist --package-runtime 0
    # If prefer to super mode, run this command
    pyarmor obfuscate -O obfdist --advanced 2
    # Check it works
    python dist/
  • Repack the final executable, use the same Python interpreter as PyInstaller using:

    # If one folder mode
    python -p obfdist dist/foo/foo.exe
    # Overwrite the old one
    cp foo-obf.exe dist/foo/foo.exe
    # If one file mode
    python -p obfdist dist/foo.exe
    # Overwrite the old one
    cp foo-obf.exe dist/foo.exe

Here foo-obf.exe is the patched bundle.

The obfuscated scripts in the obfdist must be in the same path as it in the PyInstaller bundle. The option -d is used to print the archive information, copy the obfuscated scripts to the right place according to the structure of the archive. For example, this command could print the archive information:

python -d -p obfdist dist/foo.exe

Note that if the structure of obfuscated scripts are changed, run the main script by Python directly, make sure it still works.


Before v6.5.5, please download from

Since v6.5.5, run it by this way:

python -m pyarmor.helper.repack -p obfdist dist/foo

Build obfuscated scripts to extensions

There is a helper script in the package of pyarmor used to build obfuscated scripts to extensions

  1. Obfuscate the script with --no-cross-protection and --restrict 0, for example:

    pyarmor obfuscate --no-cross-protection --restrict 0
  2. Build obfuscated script to extension, for example:

    python dist/

If option -i is specified, the obfuscated scripts will be deleted after building, so the output path dist is clean. For example:

python -i dist/

By default only the obfuscated scripts in the dist are handled, if there are sub-directories, list all of them like this:

python dist/ dist/a/ dist/b/

Or list all the scripts in the command line, for example:

# In Linix
python $(find dist/ -name "*.py")

# In Windows
FOR /R dist\ %I IN (*.py) DO python %I

The extension will ignore the block if __name__ == "__main__", in order to run this block as main script, build it with option -e to generate an executable, for example:

python -e dist/

This executable must be run in the current Python environment, it equals:

python dist/

Show more usage and options by -h:

python -h


Before v6.6.0, please download from

Since v6.6.0, run it by this way:

python -m pyarmor.helper.buildext ...


For Windows, if something is wrong with building extension, just write a simple to build demo.c to demo.pyd:

    from distutils.core import setup, Extension

    module1 = Extension('demo',
                        sources = ['demo.c'])

    setup (name = 'pyarmor.helper.buildext',
           version = '1.0',
           description = 'This is a helper package to build extension',
           ext_modules = [module1])

Then run it::

    python build_ext

Distributing Obfuscated Package With pip

Here it’s a simple package:

└── mylib
    ├── mylib
    │   ├──
    │   └──

First generate unique Runtime Package with --enable-suffix 1:

cd mylib
pyarmor runtime -O dist/share --enable-suffix 1

Then obfuscate the package with this runtime:

pyarmor obfuscate --with-runtime @dist/share mylib/

Next edit, add all the required runtime files as data files. For example, suppose the unique package name is pytransform_vax_xxxxxx

      package_dir={'mylib': 'dist'},
      data_files=[('pytransform_vax_xxxxxx', 'dist/share/pytransform_vax_xxxxxx/*')]

Finally build the source package:

python sdist


Do not obfuscate

For super mode, the runtime files are different, please modify as required.

Run Obfuscated Scripts By Different Python Versions

This feature is introduced in v6.8.0

Generally the obfuscated scripts can be run only by one Python version. In order to run it by other Python version, one solution is, first obfuscate the scripts by different Python version, then merge them to one script.

There is a helper script in the package of pyarmor used to merge different obfuscated scripts to one.

Here it’s the basic usage:

# First obfuscate the scripts by Python 2.7
python2.7 obfuscate -O py27

# Then obfuscate the scripts by Python 3.8
python3.8 obfuscate -O py38

# Finally run this script to merge all of them
python py38/ py27/

# Look the results
ls merged_dist/

It also works for super mode:

# First obfuscate the scripts by Python 2.7
python2.7 obfuscate --advanced 2 -O py27

# Then obfuscate the scripts by Python 3.8
python3.8 obfuscate --advanced 2 -O py38

# Finally run this script to merge all of them
python py38/ py27/

# Look the results
ls merged_dist/


Try to use option --no-cross-protection to obfuscate the scripts if the merged scripts raise protection error.


Before v6.8.0, please download from

Since v6.8.0, run it by this way:

python -m pyarmor.helper.merge ...

How to customize error message

I have started to play around with pyarmor. When using a license file that expires you get the message “License is expired”. Is there a way to change this message?

From pyarmor v7.8.0 (it’s not released now), there are 2 license error messages could be customized by runtime configure file ~/.pyarmor/runtime.cfg with json format:

  • License is expired
  • License is not for this machine

In order to customize the error message, first create the file ~/.pyarmor/runtime.cfg, edit it, then obfuscate the scripts.

There are 3 kind of ways to customize error handlers by editing the content of this file:

  1. Quit directly if “errors” is set to keyword “exit”
  1. Show same message for any license error
  1. Show customized message for different errors

For old version, you need patch the source script in the pyarmor package. There is a function pyarmor_runtime

def pyarmor_runtime(path=None, suffix='', advanced=0):
        pyarmor_init(path, is_runtime=1, suffix=suffix, advanced=advanced)
    except Exception as e:
        if sys.flags.debug or hasattr(sys, '_catch_pyarmor'):
        sys.stderr.write("%s\n" % str(e))

Change the hanler of the exception as you desired.

If the scripts are obfuscated by super mode, this solution doesn’t work. You may create a script to catch exceptions raised by obfuscated script For example

    import foo
except Exception as e:
    print('something is wrong')

By this way not only the exceptions of pyarmor but also of normal scripts are catched. In order to handle the exceptions of pyarmor only, first create runtime package by runtime, and obfuscate the scripts with it:

pyarmor runtime --advanced 2 -O dist
pyarmor obfuscate --advanced 2 --runtime @dist

Then create a boot script dist/ like this

    import pytransform_bootstrap
except Exception as e:
    print('something is wrong')
    import foo

The script dist/ is created by runtime, it’s obfuscated from an empty script, so only pyarmor bootstrap exceptions are raised by it.