Кроме защиты native (написанные, например, на C) и .Net файлов приложений Guardant Protection Studio может решить задачу и по защите Python-приложений.
Python — это интерпретируемый язык программирования в следствии чего задача по надежной защите и лицензированию такого кода становится особенно актуальной.
При защите Python-приложений есть несколько подходов:
1. Вынести небольшую, но критичную для работы всего сервиса, часть кода во внешнюю native-библиотеку, которую защитить с помощью Guardant Licensing API и Guardant Protection Studio.
Такой подход обеспечивает высокий уровень безопасности и гибкости, но при этом требует от разработчика умения программировать на компилируемом языке и потратить время на портирование части кода.
2. Использовать решение Cython, которое позволяет осуществить трансляцию Python кода в C код. Такой подход позволяет без переписывания кода вынести часть логики в native-файл, который будет надежно защищен посредством Guardant Licensing API и/или Guardant Protection Studio.
Ниже в документации представлена подробная инструкция по реализации такого подхода к защите Python-приложений с использование Cython и Guardant SLK.
Понадобятся файлы
скрипт настройки окружения cython и автоматической компиляции кода | |
пример исходного кода на Python, в котором проводятся вычисления и вызываются функции Guardant | |
| run_cython.pyx | пример исходного кода с измененными прототипами функций для Cython. По сути тот же код на Python, но с явно указанными типами |
| simple_test.py | пример простого приложения, которое вызывает защищенные функции/функции API |
Защита Python-приложения с использованием Cython в Debian 11
Шаг 1. Установить cython и нужные ему пакеты
sudo apt-get install -y build-essential python3-dev sudo apt install -y python3-pip sudo apt install -y cython3 pip3 install setuptools pip3 install requests
Если при использовании pip возникает ошибка вида "error: externally-managed-environment" дополнительно выполнить команды для создания окружения.
sudo apt install -y python3-venv python3 -m venv myenv source myenv/bin/activate
И затем повторить.
pip3 install setuptools pip3 install requests
Шаг 2. Установить Guardant SLK из tar.gz архива
tar -xf ./GuardantSLK-.tar.gz
Шаг 3. Создать рабочую папку
mkdir mydirname
Шаг 4. Поместить в рабочую папку файлы
setup.py
run_cython.pyx
run_python.py
simple_test.py
grdlic_python.py <--- лежит по пути .../Software Licensing Kit/api/wrappers/python
libgrdlic.a <--- лежит по пути .../Software Licensing Kit/api/lib/linux/x86_64
libgrdlic.so <--- лежит по пути .../Software Licensing Kit/api/lib/linux/x86_64
Шаг 5. Активировать DL или подключить аппаратный ключ Sign
Не важно, какие продукты и компоненты записаны в ключ, для логина будет выбран последний имеющийся компонент.
Шаг 6. Сгенерировать код на С и скомпилировать расширение
Для генерации нужно выполнить команду:
python3 setup.py build_ext --inplaceВ одной директории с исходными файлами, будут созданы новые:
run_cython.c
run_cython.cpython-39-x86_64-linux-gnu.so
Можно использовать run_cython.cpython-39-x86_64-linux-gnu.so для выбора функций для защиты, пропустить шаги 7-9, а на шаге 10 не указывать .map файл.
Однако для больших проектов рекомендуется .map файл указывать. Для его получения в нужном формате надо выполнить шаги 7-9.
Шаг 10. Подготовить проект защиты
Открыть Protection Studio, авторизоваться, выбрать файл "run_cython.so" из шага выше для защиты и указать компонент (любой из имеющихся в ключе).
При наличии выбрать .map файл, созданный на шаге 8.
В окне выбора функций для защиты, ввести в строку поиска "factorial" и выбрать "__pyx_pw_10run_cython_1factorial"
Шаг 11. Запустить защиту
Дождаться завершения процесса защиты.
Шаг 12. Скопировать защищенный run_cython.so и libprotectstorage64.so в рабочую папку
Шаг 13. Очистить от лишних файлов директорию с защищенными
Удалить, переименовать или переместить в другую папку файлы (чтобы убедиться в работе защищенного run_cython.so):
run_cython.c
run_cython.pyx
*run_cython.cpython-39-x86_64-linux-gnu.so (если run_cython.so был скомпилирован вручную для получения .map файла)
Шаг 14. Запустить simple_test.py
python3 simple_test.py
Запустится простой тест, который посчитает факториал от пяти и выполнит вызовы функций API.
Подробнее процесс работы с Cython описан в его документации:
https://cython.readthedocs.io/en/latest/src/userguide/source_files_and_compilation.html
Cython имеет ряд особенностей
1) Cython создает большое количество вспомогательных внутренних функций. При защите важно выбрать именно пользовательскую функцию, а не внутреннюю. Если по ошибке выбрана внутренняя служебная функция для защиты в Protection Studio, высока вероятность, что защита не будет работать корректно.
2) Cython при генерации кода на С добавляет префикс к названию оригинальной функции на Python, изменяя его.
Например, есть python-функция подсчета факторила и печати результата:
def factorial(x):
y = 1
for i in range(1, x+1):
y *= i
print("Factorial(" + str(x) + ")= " + str(y))
return y
В коде на С, сгенерированном через cython, она выглядит так:
static PyObject *__pyx_pw_10run_cython_1factorial(PyObject *__pyx_self, PyObject *__pyx_arg_x); /*proto*/
static int __pyx_f_10run_cython_factorial(int __pyx_v_x, CYTHON_UNUSED int __pyx_skip_dispatch) {
int __pyx_v_y;
int __pyx_v_i;
int __pyx_r;
__Pyx_RefNannyDeclarations
long __pyx_t_1;
long __pyx_t_2;
int __pyx_t_3;
PyObject *__pyx_t_4 = NULL;
PyObject *__pyx_t_5 = NULL;
PyObject *__pyx_t_6 = NULL;
int __pyx_lineno = 0;
const char *__pyx_filename = NULL;
int __pyx_clineno = 0;
__Pyx_RefNannySetupContext("factorial", 0);
/* "run_cython.pyx":15
*
* cpdef int factorial(int x):
* cdef int y = 1 # <<<<<<<<<<<<<<
* cdef int i
*
*/
__pyx_v_y = 1;
/* "run_cython.pyx":18
* cdef int i
*
* for i in range(1, x+1): # <<<<<<<<<<<<<<
* y *= i
*
*/
__pyx_t_1 = (__pyx_v_x + 1);
__pyx_t_2 = __pyx_t_1;
for (__pyx_t_3 = 1; __pyx_t_3 < __pyx_t_2; __pyx_t_3+=1) {
__pyx_v_i = __pyx_t_3;
/* "run_cython.pyx":19
*
* for i in range(1, x+1):
* y *= i # <<<<<<<<<<<<<<
*
* print("Factorial(" + str(x) + ")= " + str(y))
*/
__pyx_v_y = (__pyx_v_y * __pyx_v_i);
}
/* "run_cython.pyx":21
* y *= i
*
* print("Factorial(" + str(x) + ")= " + str(y)) # <<<<<<<<<<<<<<
*
* return y
*/
__pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_x); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4);
__pyx_t_5 = __Pyx_PyObject_CallOneArg(((PyObject *)(&PyString_Type)), __pyx_t_4); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_5);
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
__pyx_t_4 = PyNumber_Add(__pyx_kp_s_Factorial, __pyx_t_5); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4);
__Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
__pyx_t_5 = PyNumber_Add(__pyx_t_4, __pyx_kp_s_); if (unlikely(!__pyx_t_5)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_5);
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
__pyx_t_4 = __Pyx_PyInt_From_int(__pyx_v_y); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4);
__pyx_t_6 = __Pyx_PyObject_CallOneArg(((PyObject *)(&PyString_Type)), __pyx_t_4); if (unlikely(!__pyx_t_6)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_6);
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
__pyx_t_4 = PyNumber_Add(__pyx_t_5, __pyx_t_6); if (unlikely(!__pyx_t_4)) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_GOTREF(__pyx_t_4);
__Pyx_DECREF(__pyx_t_5); __pyx_t_5 = 0;
__Pyx_DECREF(__pyx_t_6); __pyx_t_6 = 0;
if (__Pyx_PrintOne(0, __pyx_t_4) < 0) __PYX_ERR(0, 21, __pyx_L1_error)
__Pyx_DECREF(__pyx_t_4); __pyx_t_4 = 0;
/* "run_cython.pyx":23
* print("Factorial(" + str(x) + ")= " + str(y))
*
* return y # <<<<<<<<<<<<<<
*
* cpdef str uhex(value):
*/
__pyx_r = __pyx_v_y;
goto __pyx_L0;
Т.е. исходная функция называется "factorial", а сгенерированная - "__pyx_pw_10run_cython_1factorial"
Как следствие, перед защитой нужно обязательно изучить сгенерированный код на С, чтобы выбрать нужную функцию для защиты. Как видно в примере выше, сгенерированный код имеет подробные комментарии: явно указано, какая строка исходного кода на Python преобразована в какой код на С.
3) На просторах интернета можно найти примеры, где Cython генерирует неправильный или не совсем правильный код С (в сложных проектах). Таким образом его использовать быстро и достаточно просто, но опытный разработчик сделает код на C лучше.