Будучи разработчиком Android, я слышал о запутывании кода довольно рано в своей карьере. Поскольку он предварительно настроен в плагине gradle для Android (на самом деле это proguard под капотом), но вы на самом деле не связываетесь с ним, если не хотите. С другой стороны, когда вы разрабатываете библиотеку java/kotlin и хотите использовать обфускацию кода, все становится немного по-другому. В этой статье я хотел бы дать краткий обзор моей стратегии публикации запутанных библиотек kotlin.
Если вы не уверены, что это значит, запутывание кода – это модификация исходного кода, делающая его непонятным, что означает, что невозможно для понимания третьей стороной. Это отличная инициатива AppSec и часто заботится о минимальном уровне безопасности, который вы можете иметь при публикации исходного кода в открытом доступе. Усложняя реинжиниринг кода, вы гарантируете, что интеллектуальная собственность вашей библиотеки защищена от угроз безопасности, несанкционированного доступа и обнаружения уязвимостей приложений.
ProGuard является самым популярным оптимизатором для байт-кода Java, а также обеспечивает запутывание имен классов, полей и методов.
Proguard – это, по сути, инструмент командной строки, который принимает входные файлы jar (ears, wars, ears, zip, apk, каталоги), а затем выполняет поиск, оптимизацию, запутывание и предварительную проверку для вывода нового jar.
Здесь нас просто интересует шаг запутывания, который переименует классы, поля и методы, используя короткие бессмысленные имена, что действительно затруднит чтение кода.
Выбор наших инструментов
Вероятно, есть много способов сделать это, но поскольку у меня есть опыт разработки мобильных устройств, мы будем делать это с помощью плагина proguard gradle, который немного упростит задачу, особенно во время публикации часть. Кроме того, я буду использовать gradle groovy dsl, просто потому, что мне нравится его удобство больше, чем kotlin сейчас (для скриптов gradle). Возможно, мне понадобится еще одна статья, чтобы оправдать это, но потерпите. Вот как мы интегрируем плагин proguard gradle:
buildscript { repositories { jcenter() } dependencies { classpath "com.guardsquare:proguard-gradle:7.0.1" } }
Теперь нам нужно использовать его для запутывания jar
нашей библиотеки . Чтобы сделать это, мы собираемся использовать синтаксис gradle для создания задачи, которая делает именно это.
task("obfuscateArtifact", type: ProGuardTask, dependsOn: jar)
Давайте на секунду проанализируем сигнатуру задачи. Здесь мы объявляем, что это задача типа ProGuard task
и что это зависит от jar
задача, что означает, что она должна выполняться после нее. Эта задача jar
выполняется либо из плагина kotlin, либо из плагина java, который вы уже используете, и он отвечает за упаковку артефактов вашего приложения в файл jar. Причину, по которой наша задача obfuscate Artifact
должна выполняться после этой, можно увидеть на изображении выше. Proguard принимает файл jar в качестве входных данных, поэтому нам нужна эта задача для создания jar а затем мы применим нашу задачу поверх нее.
Теперь давайте настроим ввод/вывод нашей задачи:
def artifactName = "$libraryArtifactId-${libraryVersion}.jar" def obfuscatedFolder = "$buildDir/obfuscated" injars "$buildDir/libs/$artifactName" outjars "$obfuscatedFolder/$artifactName"
Задача jar
по умолчанию создаст файл выходного файла в папке buildDir/libs
. По умолчанию его имя соответствует шаблону: artifactId-version.jar
. Идентификатор artifactId
определяется в задаче публикации, которую мы увидим позже. Полезно иметь эти значения в качестве переменных, объявленных в файле gradle.properties
.
Следующие свойства предназначены для обработки деобфускации. Они предназначены для использования, когда вы хотите понять свои трассировки стека.
printseeds "$obfuscatedFolder/seeds.txt" printmapping "$obfuscatedFolder/mapping.txt"
Обработка зависимостей
После этого мы должны заставить proguard понять зависимости нашего проекта. Для этого нам нужно предоставить ему стандартную библиотеку java и нашу среду выполнения, а также зависимости во время компиляции. Краткое объяснение последних двух:
- Путь к классам
во время компиляции
содержит классы, которые вы добавили для компиляции своего кода. Это эквивалент пути к классу, переданного вjavac
(или любой другой компилятор java). - Путь к
runtime:
classpath содержит классы, которые используются при запуске вашего приложения. Это путь к классу, переданный исполняемому файлуjava
. Это весь код, который ваша библиотека использует во время работы.
В proguard мы можем использовать свойство library jars
для предоставления этих зависимостей:
libraryjars "${System.getProperty('java.home')}/lib/rt.jar" libraryjars configurations.runtime libraryjars sourceSets.main.compileClasspath
В первой строке есть путь, указывающий на rt.jar
. Это означает runtime JAR
и в нем есть все классы из основного Java API. Это путь, который вы используете, когда ориентируетесь на java 8 и ниже. Две другие строки обрабатывают зависимости во время выполнения и во время компиляции, которые мы получаем с помощью dsl gradle.
Подведение итогов
Последним кусочком этой головоломки является конфигурационный файл proguard. Я предпочитаю делать это в отдельном файле. Если вы используете intellij, вы можете получить хорошую подсветку синтаксиса для этого файла, что в моей книге всегда приятно иметь.
configuration files("$projectDir/gradle/configuration.pro")
Конфигурация, которую вы собираетесь использовать, зависит от вас, даже если вы ничего не добавите в конфигурационный файл, proguard по умолчанию обработает запутывание кода. Мы еще вернемся к этому файлу.
Хорошо, теперь, если вы хотите запутать свой код, вам просто нужно:
- выполнить
jar
задачу - выполнить
запутать артефакт
задача
Проблема решена! А может, и нет.
Предполагая, что вы получите artifactId
верно, то, что мы делали до сих пор, действительно может сработать для вас. Задача запутывания запустит сборку, а затем запутает jar. Но это не идеально. Мы не хотим каждый раз вручную запускать эту конкретную задачу. На мой взгляд, этап запутывания теперь должен быть просто частью процесса публикации нашей библиотеки. Нам больше не нужно об этом вспоминать. Чтобы исправить это, нам нужно взглянуть на этот так называемый конвейер публикаций.
Если он у вас уже есть, это может быть просто публикация
задача , что- то вроде этого:
group = "com.myLibrary.group." version = "$myLibraryVersion" publishing { publications { create(MavenPublication) { artifactId = "$myLibraryArtifactId" from components["java"] name = "My Library's name" description = "My Library's description" } } }
Ключевым моментом здесь является объявление from components["java"]
. Цитирую документы gradle:
Компоненты определяются плагинами и обеспечивают простой способ определения публикации для публикации. Они содержат один или несколько артефактов, а также соответствующие метаданные. Например, компонент java состоит из производственного JAR—файла, созданного задачей jar, и информации о его зависимостях .
Итак, вот как наша задача публикации получает артефакты и зависимости проекта. Поэтому, если мы хотим опубликовать запутанную библиотеку, нам нужно удалить эту строку и предоставить нашей публикации выходные данные, созданные obfuscateArtifact
task.
Итак, как мы можем это сделать? Как мы видели в документации, components["java"]
автоматически определяется плагином java/kotlin и включает в себя все классы из стандартных исходных наборов
. Если мы хотим определить наши собственные артефакты, которые мы хотим опубликовать, мы можем использовать свойство artifact
. Вот как вы это делаете: внутри блока create
добавьте:
artifact("$buildDir/obfuscated/${libraryArtifactId}-${version}.jar") { builtBy obfuscateArtifact }
Теперь давайте прочитаем это задом наперед:
built By
указывает, какая задача будет создавать артефакт- аргумент функции
artifact
– это путь, по которому эта задача будет выводить его. Этот путь был определен ранее с помощьювыведите Jar
свойство в нашей задачеobfuscateArtifact
.
Почти на месте! Теперь нам нужно предоставить информацию о зависимостях. Это происходит автоматически при использовании components["java"
, но здесь нам нужно предоставить его вручную. Все еще находясь в блоке create
, добавьте:
pom.withXml { def dependencies = asNode().appendNode("dependencies") configurations.implementation.allDependencies.each { dep -> def depNode = dependencies.appendNode("dependency") depNode.appendNode("groupId", dep.group) depNode.appendNode("artifactId", dep.name) depNode.appendNode("version", dep.version) } }
И это все! Теперь мы можем забыть о запутывании. Мы просто запускаем задачу публикации и получаем беспрепятственную публикацию нашего запутанного кода.
Конфликты именования
Если вы используете эту библиотеку в другой кодовой базе, которая также будет запутывать свой код, у вас могут возникнуть некоторые конфликты именования из-за того, что proguard использует одно и то же имя для классов/методов в этом собственном процессе запутывания проектов. Чтобы исправить это, вы можете предоставить proguard пользовательский словарь.
//class-dictionary.txt Lorem ipsum dolor sit amet consectetur adipiscing ...
Затем вам нужно будет сообщить об этом proguard в configuration.pro
файл, добавив строку.
-classobfuscationdictionary class-dictionary.txt
Теперь proguard будет использовать слова в этом файле для именования наших классов на этапе запутывания, избегая конфликтов имен.
Последнее, что мне хотелось бы делать, это создавать другой artifactId
для неискаженной версии библиотеки и для публикации обеих версий. Вы можете увидеть полный сценарий в этом gist
На этой диаграмме показан наш конечный продукт.
Рекомендации
Домашняя страница Proguard Руководство по запутыванию кода
Оригинал: “https://dev.to/iurysza/publishing-an-obfuscated-kotlin-library-2kbk”