Skip to main content

Python code protection

Project description

Stonefish

Industry-grade Python code protection.

Quickstart

Protecting Python packages

If you have a Python package that you'd like to build with Stonefish, make sure it follows PEP 517 and contains at least a minimal pyproject.toml. There, simply replace the your build system (e.g., setuptools) with stonefish:

[build-system]
# requires = ["setuptools"]
# build-backend = "setuptools.build_meta"
requires = ["stonefish"]
build-backend = "stonefish.build_meta"

# ...
# more project metadata if you follow PEP 621
# <https://peps.python.org/pep-0621/>
# (recommended)
# ...

Done! Your project builds are now protected with Stonefish. Try it out with

pip install .

or

(pip install build)
python -m build . --wheel

Protecting standalone Python scripts

If you'd like to project just a single Python file, you can use the stonefish command-line utility, e.g.,

stonefish /path/to/file.py

How Stonefish protects your code

Python packages ship their code to all users, and there are different ways for every user to retrieve it. Unless built with Stonefish. Some examples:

Just opening the source files

After installation, users will find the package code in, e.g., ~/.local/lib/python3.11/site-packages/numpy. (The path is different for different operation systems.)

When building with Stonefish, though, a source code like

├── pyproject.toml
└── stonefish_example
    └── __init__.py

is installed as

├── _agg
│   ├── __init__.dat
│   ├── __init__.py
│   └── __pycache__
│       └── __init__.cpython-311.pyc
├── __init__.py
└── __pycache__
    └── __init__.cpython-311.pyc

where all program logic is encrypted in the binary __init__.dat. The actual source code remains protected.

inspect — Inspect live objects
import stonefish_example
import inspect

print(inspect.getsource(stonefish_example.solve))
Without Stonefish With Stonefish
def solve():
    magic = 42
    return 2 * magic - 1
[...]
OSError: could not get source code
Dill
import stonefish_example
from dill.source import getsource

print(getsource(stonefish_example.solve))
Without Stonefish With Stonefish
def solve():
    magic = 42
    return 2 * magic - 1
[...]
IndexError: list index out of range
IPython's ??
In [1]: import stonefish_example

In [2]: stonefish_example.solve??
Without Stonefish With Stonefish
Signature: stonefish_example.solve()
Docstring: <no docstring>
Source:
def solve():
    magic = 42
    return 2 * magic - 1
File:      ~/path/to/file.py
Type:      function
Signature: stonefish_example.solve(*args, **kwargs)
Docstring: <no docstring>
File:      Dynamically generated function.
           No source code available.
Type:      function
dis — Disassembler for Python bytecode
import stonefish_example
import dis

dis.dis(stonefish_example.solve)
Without Stonefish With Stonefish
1        0 RESUME                0

2        2 LOAD_CONST            1 (42)
         4 STORE_FAST            0 (magic)

3        6 LOAD_CONST            2 (2)
         8 LOAD_FAST             0 (magic)
        10 BINARY_OP             5 (*)
        14 LOAD_CONST            3 (1)
        16 BINARY_OP            10 (-)
        20 RETURN_VALUE
         0 COPY_FREE_VARS        1

3        2 RESUME                0
         4 PUSH_NULL
         6 LOAD_DEREF            2 (f)
         8 LOAD_FAST             0 (args)
        10 BUILD_MAP             0
        12 LOAD_FAST             1 (kwargs)
        14 DICT_MERGE            1
        16 CALL_FUNCTION_EX      1
        18 RETURN_VALUE
xdis
import stonefish_example
import xdis.std as dis

dis.dis(stonefish_example.solve)
Without Stonefish With Stonefish
2:     0 LOAD_CONST           (42)
       2 STORE_FAST           (magic)

3:     4 LOAD_CONST           (2)
       6 LOAD_FAST            (magic)
       8 BINARY_MULTIPLY
      10 LOAD_CONST           (1)
      12 BINARY_SUBTRACT
      14 RETURN_VALUE
3:     0 LOAD_DEREF           (f)
       2 LOAD_FAST            (args)
       4 BUILD_MAP            0
       6 LOAD_FAST            (kwargs)
       8 DICT_MERGE           1
      10 CALL_FUNCTION_EX     (keyword and positional arguments)
      12 RETURN_VALUE
decompyle3, uncompyle6 etc.

Those tools are meant to recreated Python code from .pyc files. Since Stonefish moves the actual code into an encrypted

decompyle3  ~/path/to/__init__.cpython-38.pyc
Without Stonefish With Stonefish
def solve():
    magic = 42
    return 2 * magic - 1
from ._agg import _Qcbq7 as solve

Limitations

  • Stonefish renames class/function names, so you cannot rely on the __name__ attribute in your code.

  • Users must add *.dat files to their package data, e.g.,

    [tool.setuptools.package-data]
    "*" = ["*.dat"]
    

    in pyproject.toml. This is because the encrypted code must be shipped with the package.

  • Local imports must be relative, i.e., from . import x instead of import x if x//x.py is an internal folder or directory. This is recommended anyway.

  • Stonefish will put all data files in a flat directory structure. That's why it cannot yet handle files that are read from two different Python paths, e.g.,

    ./data.dat
    
    ./a.py
        Path(__file__).parent / "data.dat"
    
    ./b/b.py
        Path(__file__).parent / .. / "data.dat"
    
  • Stonefish discards private (underscored) names from the API. If you want your users to use these variables or functions, you'll have to rename them.

  • Stonefish cannot yet handle relative * imports, e.g.,

    from .utils import *
    

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distribution

stonefish-0.3.18-py3-none-any.whl (67.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page