How to convert Python Code to Cython .SO or .PYD for Obfuscation and Protection

Convert Python to Cython

Python is a widely used programming language or script for various purposes. Developers usually distribute byte-code files (.PYC) for direct consumption that can be run on end-user machines supporting the Python Virtual Environment. Let us know how to convert Python code to Cython .SO and .PYD for better Obfuscation and Security.

Remember that the Linux machines support .SO files and the Windows machines support .PYD files.

Convert Python Code to Cython .SO or .PYD Files

The Cython is a Superset of Python. It includes the capabilities of both Python and C languages. Cython converted code works really fast when compared to Python code. This is due to the inherent translation of byte code to machine level code by Cython. In contrast, the Python code is interpreted line by line.

The advantages of converting the Python code to Cython are

  • Speed of execution or completion if you use Cython data types for Python variables explicitly
  • Small size of the final distributable .SO or .PYD
  • Difficult to decompile but not impossible through paid decompilers
  • In-built Obscurity in converting Python to Cython code
  • Almost automatic conversion of Python to Cython code if the requirement is only Protection

Let us create a sample Python Project (Like an extension) with proper directory structures.

Project Structure:

Project
setup.py
run.py
---- application1
		|
		|-------package1
				|
				|------area_calculator.py

Create a folder named "application1" as the envelope for Python files or classes. Inside, create a folder for package "package1". Inside the package1, create a Python file "area_calculator.py".

area_calculator.py

# area_calculator.py
import math
class AreaClass:
    def __init__():
        print('consturctor')
        
    @staticmethod
    def circle(radius):
        PI = math.pi
        return PI*radius*radius
        
    @staticmethod
    def square(side):
        return side*side
        
    @staticmethod
    def rectangle(side1, side2):
        return side1*side2
    

 

setup.py

# setup.py
from setuptools import find_packages, setup
from setuptools.extension import Extension
from Cython.Build import cythonize
from Cython.Distutils import build_ext
setup(
    
    name="myextension",
    version="1",
    ext_modules = cythonize(
        [
            Extension("application1.package1.area_calculator", ["application1/package1/area_calculator.py"]),
        ],
        build_dir = "build_cythonize",
        compiler_directives={
            'language_level' : "3",
            'always_allow_keywords': True,
        }
    ),
    cmdclass=dict(
        build_ext=build_ext
    ),
)

 

You may need to install Microsoft C++ Build tools on windows machines. Because, you should convert Python to C-Language / Cython literally.

Now, all the project files are ready for Cythonizing. Let us compile this Python Code to Cython. 

>python setup.py build_ext --inplace

The output of above command creating various files like .c, .obj and .pyd can be seen in below trace.

Compiling application1/package1/area_calculator.py because it changed.
[1/1] Cythonizing application1/package1/area_calculator.py
running build_ext
building 'application1.package1.area_calculator.py' extension
creating build
creating build\temp.win-amd64-cpython-311
creating build\temp.win-amd64-cpython-311\Release
creating build\temp.win-amd64-cpython-311\Release\build_cythonize
creating build\temp.win-amd64-cpython-311\Release\build_cythonize\application1
creating build\temp.win-amd64-cpython-311\Release\build_cythonize\application1\package1
build\temp.win-amd64-cpython-311\Release\build_cythonize\application1/package1/area_calculator.obj /OUT:C:\Python\Proj\application1\package1\area_calculator\area_calculator.cp311-win_amd64.pyd /IMPLIB:build\temp.win-amd64-cpython-311\Release\build_cythonize\application1/package1\area_calculator.cp311-win_amd64.lib
   Creating library build\temp.win-amd64-cpython-311\Release\build_cythonize\application1/package1\area_calculator.cp311-win_amd64.lib and object build\temp.win-amd64-cpython-311\Release\build_cythonize\application1/package1\py.cp311-win_amd64.exp
Generating code
Finished generating code

This completes Cythonizing Python Code. Let us test the newly generated .SO (Linux machines) or .PYD (Windows machines) using run.py script below.

run.py

# run.py
from application1.package1.area_calculator import AreaClass
mycircle_area = AreaClass.circle(5.0)
print("Area of Circle (5.0): " + str(mycircle_area))

Now test accessing methods of classes of area_calculator.py by running the python file run.py.

Project>python run.py
Area of Circle (5.0): 78.53981633974483

Now, quietly rename or remove the area_calculator.py from package1 folder. Observe that there is a PYD file with the name "area_calculator.cp311-win_amd64.pyd" with 311 being python version 3.11 in our testing windows machine. This is the Cython file converted from Python.

Try to execute the run.py again.

Project>python run.py
Area of Circle (5.0): 78.53981633974483

You can see the same output as this time the newly created PYD extension worked like charm as the extension that can be distributed to end users.

If you try to open the .PYD file with some text editor like Notepad++, you can not understand the original source code easily. This is nothing but Code Obfuscation. Even the open sourced UNCOMPYL6 openly states that "we dont handle Cython generated code." So, you can assume that there is some level of security for Cython distributed files.

In our next article, you can learn converting this Cython Code to Executable.

Share this Last Minute Python tutorial with your friends and colleagues to encourage authors.