Как зашифровать код python
Перейти к содержимому

Как зашифровать код python

  • автор:

Шифрование набора инструментов Python

По умолчанию набор инструментов Python ( .pyt ) – это текстовой файл, который можно редактировать в любом текстовом редакторе или в среде разработки Python IDE. Однако в некоторых случаях может потребоваться скрыть исходный код набора инструментов Python.

Чтобы зашифровать набор инструментов Python, щелкните на нем правой кнопкой мыши и выберите Зашифровать . При вводе и подтверждении пароля можно сохранить резервную копию исходного файла .pyt .

Внимание:

Шифрование набора инструментов Python производится непосредственно для самого файла, и исходный незашифрованный файл будет переписан. Создание резервной копии исходного набора инструментов Python рекомендуется на случай, если вы забудете пароль.

Чтобы зашифровать набор инструментов Python, щелкните на нем правой кнопкой мыши и выберите Зашифровать .

Также имеются функции ArcPy EncryptPYT и DecryptPYT для шифрования и расшифровки наборов инструментов Python.

Шифрование набора инструментов Python

По умолчанию набор инструментов Python ( .pyt ) – это текстовой файл, который можно редактировать в любом текстовом редакторе или в среде разработки Python IDE. Однако в некоторых случаях может потребоваться скрыть исходный код набора инструментов Python.

Чтобы зашифровать набор инструментов Python, щелкните на нем правой кнопкой мыши и выберите Зашифровать . При вводе и подтверждении пароля можно сохранить резервную копию исходного файла .pyt .

Внимание:

Шифрование набора инструментов Python производится непосредственно для самого файла, и исходный незашифрованный файл будет переписан. Создание резервной копии исходного набора инструментов Python рекомендуется на случай, если вы забудете пароль.

Чтобы зашифровать набор инструментов Python, щелкните на нем правой кнопкой мыши и выберите Зашифровать .

Также имеются функции ArcPy EncryptPYT и DecryptPYT для шифрования и расшифровки наборов инструментов Python.

Об одном способе защиты исходников Python-программы

Однажды мне пришлось участвовать в разработке одного небольшого проекта для научных расчётов, который разрабатывался на языке программирования Python. Изначально Python был выбран как удобный и гибкий язык для экспериментов, визуализации, быстрого прототипирования и разработки алгоритмов, но в дальнейшем стал основным языком разработки проекта. Надо заметить, что проект был хоть и не большим, но довольно насыщенным технически. Для обеспечения требуемой функциональности, в проекте широко применялись алгоритмы теории графов, математическая оптимизация, линейная алгебра и статистика. Также использовались декораторы, метаклассы и инструменты интроспекции. В процессе разработки пришлось использовать сторонние математические пакеты и библиотеки, например, такие как numpy и scipy, а также многие другие.

Со временем стало ясно, что переписывать проект на компилируемом языке слишком затратно по времени и ресурсам. Скорость работы и потребление памяти не являлись критичными показателями в данном случае и были вполне приемлемыми и достаточными. Поэтому было принято решение оставить всё как есть, и продолжить разработку и поддержку проекта на языке Python. К тому же, документация по большей части уже была написана с использованием Sphinx.

Проект являлся библиотекой, функции которой использовались в одном из модулей расширения в крупном программном комплексе. Программный комплекс был написан на C++, являлся коммерческим продуктом, имел защиту с аппаратным ключом и поставлялся клиентам без предоставления исходных кодов.

Здесь сразу обозначилась новая проблема: как защитить исходные коды нашей Python-библиотеки? Может быть, в ином случае никто бы не стал этим заниматься, я бы уж точно, но в библиотеке были реализованы некоторые ноу-хау, и руководители проекта не хотели, чтобы данные наработки попали к конкурентам. Так как я был одним из исполнителей, мне пришлось озаботиться данной проблемой. Далее я постараюсь рассказать об основной идее, что из этого вышло, и как нам удалось скрыть Python-исходники от лишних глаз.

Что предлагают люди

Как известно, наверное, большинству разработчиков, Python — язык интерпретируемый, динамический с богатыми возможностями интроспекции. Бинарные файлы модулей *.pyc и *.pyo (байт-код) легко декомпилируются, поэтому распространять их в чистом виде нельзя (если уж мы решили не показывать исходники по-настоящему).

Как, я думаю, любой на моём месте, сначала я решил поискать, а что вообще делают люди в таких случаях? Первые же поисковые запросы показали, что люди не знают, что делать и спрашивают об этом на stackoverflow и в других местах, например, вот вопрос на stackoverflow. Поискав, я пришёл к выводу, что везде предлагают несколько спорных способов:

  • Забить и не париться, всё равно, кому надо — расковыряет;
  • Переписать на компилируемом языке;
  • Сделать обфускацию исходников, например с помощью раз и два;
  • Транслировать все Python-модули в модули расширения (*.pyd) с помощью Cython или Nuitka (как сделал warsoul — автор данной статьи);
  • Заменить опкоды в исходниках Python-интерпретатора и распространять свою сборку, как предлагалhodik.

По многим причинам я отбросил все эти способы как неподходящие. Например, обфускация Python-кода. Ну какая может быть обфускация, когда синтаксис языка построен на отступах, а сам язык пронизан «хитрой интроспекцией»? Транслировать все Python-модули в бинарные модули расширения тоже не представлялось возможным, т. к. проект, напомню, был достаточно сложным технически с использованием множества сторонних пакетов, да и сам состоял из большого числа модулей в многоуровневой иерархии пакетов, которые было утомительно перегонять в *.pyd, а потом ловить баги, вылезающие на ровном месте. Возиться с заменой опкодов не хотелось, так как пришлось бы распространять и поддерживать собственную сборку интерпретатора Python, да ещё и компилировать им Python-модули всех используемых сторонних библиотек.

В какой-то момент мне показалось, что эта идея с защитой Python-исходников бесполезная, надо всё это бросить и убедить руководство, что ничего не выйдет и заняться чем-нибудь полезным. Отдаём *.pyc файлы и ладно, кто там будет разбираться? Убедить не получилось, переписывать библиотеку на C++ никому не хотелось, а проект нужно было сдавать. В итоге всё же кое-что получилось сделать. Об этом, если всё ещё интересно, можно прочитать далее.

Что сделали мы

Что может лучше всего защитить какую-либо информацию на цифровом носителе от посторонних? Я думаю, что это шифрование. Вооружившись этой фундаментальной идеей, я решил, что исходники надо шифровать, а иначе и быть не должно. Для стороннего наблюдателя, который начал проявлять излишний интерес, всё это должно выглядеть как куча непонятных файлов с непонятным содержимым. Вполне себе обфускация, но более продвинутая чем заменять имена переменных и вставлять пустые строчки.

Ход моих мыслей был следующим:

  • Шифруем каким-либо способом все исходники нашей Python-библиотеки, можно их даже перемешать и изменить имена файлов модулей и пакетов;
  • Пишем обвязку для того, чтобы Python-интерпретатор умел загружать и импортировать модули из зашифрованных текстовых файлов (расшифровка, восстановление структуры пакетов и имён файлов, импорт и т. д.);
  • «Прячем» всё это в бинарный модуль расширения (*.pyd), чтобы никто не догадался.

Основная идея, думаю, ясна — это более продвинутая обфускация. Как это сделать? Погуглив, я пришёл к выводу, что сделать это вполне реально и даже достаточно просто. С шифрованием исходников всё понятно, зашифровать и/или обфусцировать файлы можно множеством способов, главное, чтобы там была «каша» и «ничего не понятно», а также всё это должно возвращаться к первоначальному виду неизвестным способом (в случае обфускации). Для приведённого здесь примера я буду использовать Python-модуль base64 для «шифрования». В некритичных случаях можно применять замечательный пакет obfuscate.

Python the Importer Protocol

Как же нам реализовать возможность импортировать модули из зашифрованных файлов? К счастью, в Python реализована система хуков при импорте, которая работает на основе Importer Protocol (PEP 302). Значит эту возможность и будем использовать. Для перехвата импортов используется словарь sys.meta_path , в котором должны храниться объекты finder/loader , реализующие Importer Protocol. Опрос этого словаря всегда происходит до того момента, как будут проверены пути в sys.path .

Для минимальной реализации протокола импорта нужно реализовать два метода: find_module и load_module . Метод find_module отвечает за поиск конкретного модуля/пакета (ведь нам нужно перехватывать импорт только своих модулей, а остальные отдавать на откуп стандартному механизму), а метод load_module , соответственно, загружает конкретный модуль только если он был «найден» в методе find_module .

Итак, вроде бы добрались до сути. Можно привести простой пример. Минимальный пример класса, реализующего Importer Protocol, подходящего для наших целей. Он будет заниматься импортом модулей «зашифрованных» base64 из обычной структуры пакетов (в данном случае для простоты мы просто «зашифровали содержимое» файлов, но никак не меняли их имена и структуру пакетов). Считаем, что расширения файлов для наших модулей будут гордо называться «.b64».

Класс импортёра

#coding=utf-8 import os import sys import imp import base64 EXT = '.b64' #=============================================================================== class Base64Importer(object): """Служит для поиска и импорта python-модулей, кодированных в base64 Класс реализует Import Protocol (PEP 302) для возможности импортирования модулей, зашифрованных в base64 из указанного пакета. """ #--------------------------------------------------------------------------- def __init__(self, root_package_path): self.__modules_info = self.__collect_modules_info(root_package_path) #--------------------------------------------------------------------------- def find_module(self, fullname, path=None): """Метод будет вызван при импорте модулей Если модуль с именем fullname является base64 и находится в заданной папке, данный метод вернёт экземпляр импортёра (finder), либо None, если модуль не является base64. """ if fullname in self.__modules_info: return self return None #--------------------------------------------------------------------------- def load_module(self, fullname): """Метод загружает base64 модуль Если модуль с именем fullname является base64, то метод попытается его загрузить. Возбуждает исключение ImportError в случае любой ошибки. """ if not fullname in self.__modules_info: raise ImportError(fullname) # Для потокобезопасности imp.acquire_lock() try: mod = sys.modules.setdefault(fullname, imp.new_module(fullname)) mod.__file__ .format(self.__class__.__name__) mod.__loader__ = self if self.is_package(fullname): mod.__path__ = [] mod.__package__ = fullname else: mod.__package__ = fullname.rpartition('.')[0] src = self.get_source(fullname) try: exec src in mod.__dict__ except: del sys.modules[fullname] raise ImportError(fullname) finally: imp.release_lock() return mod #--------------------------------------------------------------------------- def is_package(self, fullname): """Возвращает True если fullname является пакетом """ return self.__modules_info[fullname]['ispackage'] #--------------------------------------------------------------------------- def get_source(self, fullname): """Возвращает исходный код модуля fullname в виде строки Метод декодирует исходные коды из base64 """ filename = self.__modules_info[fullname]['filename'] try: with file(filename, 'r') as ifile: src = base64.decodestring(ifile.read()) except IOError: src = '' return src #--------------------------------------------------------------------------- def __collect_modules_info(self, root_package_path): """Собирает информацию о модулях из указанного пакета """ modules = <> p = os.path.abspath(root_package_path) dir_name = os.path.dirname(p) + os.sep for root, _, files in os.walk(p): # Информация о текущем пакете filename = os.path.join(root, '__init__' + EXT) p_fullname = root.rpartition(dir_name)[2].replace(os.sep, '.') modules[p_fullname] = < 'filename': filename, 'ispackage': True ># Информация о модулях в текущем пакете for f in files: if not f.endswith(EXT): continue filename = os.path.join(root, f) fullname = '.'.join([p_fullname, os.path.splitext(f)[0]]) modules[fullname] = < 'filename': filename, 'ispackage': False >return modules 

Как это работает? Первым делом при создании экземпляра класса собирается информация о модулях нашей библиотеки, которую мы «шифруем». Затем при загрузке конкретного модуля, читается нужный «зашифрованный» файл, «расшифровывается» и импортируется с помощью средств модуля imp уже из «расшифрованной» текстовой строки. Как использовать данный класс? Очень легко. Буквально, одной строчкой включается возможность импортировать «зашифрованные» исходники нашей библиотеки, а по сути ставится хук на импорт:

sys.meta_path.append(Base64Importer(root_pkg_path)) 

где root_pkg_path абсолютный или относительный путь к корневому пакету нашей библиотеки. При этом, убирая данную строчку, мы можем использовать обычные исходники, если они доступны. Всё происходит абсолютно прозрачно, а все изменения происходят в одном месте.

Вот и всё, с этого момента импорт модулей из нашей библиотеки осуществляется с перехватом и «расшифровкой». Наш хук будет дёргаться при любом вызове инструкции import, и если импортируется модули нашей библиотеки, хук будет их обрабатывать и загружать, остальные импорты будут обрабатываться стандартно. Что нам и требовалось для более продвинутой обфускации. Представленный код импортёра и установки хука можно положить уже в *.pyd файл и надеяться на то, что никто не будет его дизассемблировать в надежде понять, что мы тут наворотили. В реальном проекте можно использовать настоящее шифрование, в том числе с использованием аппаратного ключа, что должно повысить надёжность данного метода. Также изменение имён файлов и структуры пакетов может быть полезным для большего запутывания.

Заключение

В качестве заключения хочу сказать, что я противник скрывать исходники, которые нельзя просто так взять и скрыть. В данном случае я не осмелюсь обсуждать этическую сторону вопроса и нужность/полезность сокрытия Python-исходников. Тут я просто представил метод, как это можно сделать и получить какой-то результат. Естественно, это не панацея. Python-код, действительно, невозможно скрыть полностью и от всех. Код модулей всегда можно получить с помощью интроспекции встроенными возможностями языка после их загрузки, например, из переменной sys.modules . Но это уже не так очевидно, как если бы исходники были открыты изначально.

Возможно, что всё, что тут написано и яйца выеденного не стоит — давно известные истины, либо бред сумасшедшего. Но если вышеописанное кому-то может оказаться полезным, я буду рад. Лично для меня данный опыт был полезен, хотя бы потому, что позволил лучше разобраться в мощной и гибкой системе загрузки и импортирования модулей и Importer Protocol. О тех самых штуках, которые чаще всего не требуются разработчикам для написания программ на Python. Всегда интересно узнать что-то новое.

Спасибо за внимание.

UPD 14.08.2013:
По просьбе tangro сделал минимальный проект, в котором демонстрируется описанный способ, но без настоящего шифрования (применены только некие алгоритмы обратимого преобразования).
Скачать zip-архив можно по ссылке. Нужен Python 2.7 x86 под Windows. Запускать нужно скрипт «test_main.py».

UPD 2:
И более интересный пример, в котором производятся некоторые вычисления. Здесь все импорты и вызовы функций из зашифрованных модулей скрыты в бинарном модуле. Скачать архив можно по ссылке.

  • python
  • защита данных
  • защита исходников
  • защита исходных кодов python
  • python importer protocol
  • obfuscation
  • обфускация

Скрыть код на питоне на компьютере

Ситуация: Есть проект, написанный на питоне. Проект крутится на мини-компьютере, который размещается среди прочих контроллеров и устройств в шкафу. Проект коммерческий, и мы не хотим, чтобы пользователь, которому мы продадим шкаф, смог бы украсть код, либо внести туда изменения. Вопрос, как защитить код? У нас есть идея зашифровать весь диск SSD мини-компьютера инструментами linux. Насколько это надежно?

Отслеживать
задан 18 авг в 9:49
Артур Гильметдинов Артур Гильметдинов
63 6 6 бронзовых знаков
если у пользователя есть доступ к работающему диску, то чем поможет шифрование?
18 авг в 9:51
Изучайте это направление
18 авг в 10:05
закинь в докер и держи приложуху там
18 авг в 10:21

Просто физического доступа недостаточно, конечно. Но ведь диск же будет не просто в шкафу выключенный лежать, а в системе работать. И если у пользователя будет доступ к системе, то будет и доступ к диску.

18 авг в 10:27
@Ruslan а при чём тут докер, что-то помешает выковырять код программы из докера?
18 авг в 10:27

2 ответа 2

Сортировка: Сброс на вариант по умолчанию

Самый простой и надежный способ — это скомпилировать ключевые места в сишный модуль через cython .

print('helloword') 
$ cythonize -i test.py 

Получится файлик test.cpython-311-x86_64-linux-gnu.so . Теперь исходники можно удалить из проекта:

$ rm test.py test.c $ python -c "import test" helloword 

Но у пользователя остаётся возможность вызывать функции в вашем коде.

Отслеживать
ответ дан 20 авг в 19:05
33.1k 3 3 золотых знака 26 26 серебряных знаков 59 59 бронзовых знаков

Обфускация — это процесс преобразования исходного кода в форму, которая делает его трудночитаемым и понимаемым для человека, сохраняя при этом его функциональность. Это может быть полезно для защиты интеллектуальной собственности или для предотвращения обратного проектирования.

Вот несколько практик и инструментов для обфускации кода на Python:

  1. PyArmor: Это популярный инструмент для обфускации кода Python. Он может обфусцировать часть кода или весь проект, а также поддерживает многие платформы.
  2. Rename переменных и функций: Простейший способ обфускации — это переименование переменных, функций и классов в нечто неинформативное, например, из calculate_total в a1. Удаление комментариев и документации: Хотя это не делает код более трудным для анализа, это убирает полезную информацию, которая может помочь в понимании кода.
  3. Использование строковых операций: Вы можете динамически создавать и выполнять код с помощью функций eval() и exec(). Но будьте осторожны, так как это может создать уязвимости в безопасности.
  4. Упаковка кода: Существуют инструменты, такие как cx_Freeze или PyInstaller, которые позволяют упаковать ваш код в исполняемый файл. Это делает анализ исходного кода более сложным.
  5. Использование компиляции: Вы можете компилировать ваш код в байт-код с помощью модуля py_compile. Это создает файл .pyc, который труднее анализировать, чем исходный код.
  6. Интеграция с C/C++: Если у вас есть критические части кода, которые вы хотите защитить, вы можете реализовать их на C или C++ и вызывать из Python.
  7. Обфускация строк: Строки можно кодировать и декодировать на лету, чтобы их было труднее найти и понять.
  8. Использование ProGuard для Jython: Если вы используете Jython (Python для JVM), вы можете использовать инструменты обфускации Java, такие как ProGuard.
  9. Избегайте обфускации стандартных библиотек: Это может вызвать проблемы совместимости.

И нужно учитывать, что обфускация не делает ваш код полностью безопасным. Она лишь затрудняет его анализ. Если кто-то действительно хочет разобраться в вашем коде, он найдет способ это сделать. Обфускация — это лишь один из инструментов в вашем арсенале защиты.

Еще несколько практик, кроме обфускации которые могут вам помочь (или на что-то надоумить, т к у многих есть много условий и «за»/»против»:

  1. Использование байт-кода: Python автоматически компилирует исходный код в байт-код (файлы .pyc). Хотя байт-код можно декомпилировать, это добавляет дополнительный шаг в процесс анализа.
  2. Работа с лицензиями: Внедрите систему лицензирования, чтобы ограничить использование вашего продукта только лицензированными пользователями. (К примеры мы привязываемся к железу, т. к. нет выхода в сеть)
  3. Тампер-детекция: Внедрите механизмы, которые определяют, был ли изменен ваш код или исполняемый файл, и предпринимают соответствующие действия (например, завершают выполнение программы).
  4. Защита от отладки: Используйте методы и инструменты, которые обнаруживают попытки отладки вашего кода и прерывают выполнение или вводят злоумышленника в заблуждение.
  5. Криптографическая защита: Шифруйте чувствительные данные или ключевые части кода. Также можно использовать цифровые подписи для проверки целостности кода.
  6. Облачные вычисления: Если возможно, переместите критические части вашего приложения на серверную сторону. Таким образом, конечный пользователь никогда фактически не видит или не имеет доступа к этим частям кода.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *