Protecting Python Sources With Cython

Distributing Python Programs As Compiled Binaries: How-To

Image for post
Image for post

Protecting your Python sources from unwanted readers is easier said than done, because .pyc bytecode is decompileable and the obfuscation is easily reverse-engineered. It took me a while to figure out a proper way to hide Python code…

Meet Cython, an optimizing static compiler that takes your .py modules and translates them to high-performant C files. Resulting C files can be compiled into native binary libraries with no effort. When the compilation is done there’s no way to reverse compiled libraries back to readable Python source code! Cython supports both Python 2 and 3, including the modern async/await syntax. From my experience, the only thing it couldn’t do is asynchronous generators.

1. Install Cython

2. Add

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
ext_modules = [
Extension("mymodule1", [""]),
Extension("mymodule2", [""]),
# ... all your modules that need be compiled ...]setup(
name = 'My Program Name',
cmdclass = {'build_ext': build_ext},
ext_modules = ext_modules

The script should explicitly enumerate files that you want to be compiled. You can also leave some files uncompiled as well, if you want. Those will still remain importable from binary modules.

3. Add

from logic import main      # this comes from a compiled binary
main ()

4. Run

python build_ext --inplace

…or, for Python 3:

python3 build_ext --inplace

The above command will generate .so and .c files next to your .py source files:

Image for post
Image for post

The .c files are intermediate sources used to generate .so files, which are binary modules you want to distribute. When building on Windows these files will probably have the .dll extension (UPD: in the comments people suggest that they actually have the .pyd extension on Windows).

You can delete .c and .py files after a successful build and keep the .so files only.

Note that .so-files contain the target platform in their names (e.g. darwin on my MacOS). Obviously, the compiled modules are not cross-platform. If you distribute your program to Ubuntu Linux users, you should compile it on Linux. Otherwise you won’t be able to load these binaries. So you’ll have to compile a platform-specific version of your code for each of your targeted platforms.

Luckily, there are tools like Vagrant that can help reduce all the OS installation burden to a couple of simple commands…

Setting Up a Different OS Environment Using VirtualBox and Vagrant

  1. Install VirtualBox and Vagrant.
  2. Run export VAGRANT_DEFAULT_PROVIDER=virtualbox (you can add it to your Bash startup script at ~/.bash_profile for convenience).
  3. Choose an OS here: Then click the New tab in “How to use” section. You’ll find setup instructions and commands there. Run those commands in your Python project folder:
Image for post
Image for post

Finally, run vagrant ssh to get into a freshly installed Ubuntu console (type exit to exit):

Image for post
Image for post

cd to the /vagrant folder to see your project files. Then perform steps 1, 4 from this manual, and you’re done:

Image for post
Image for post

For projects with a short build/release cycle, multi-plaform builds could be automated using a CI (Continuous Integration) service, like TravisCI, but that’s a story for another article.

About The Author

👉 LinkedIn:
👉 GitHub:

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store