Obfuscator — narzędzie przekształcające kod źródłowy w trudną do odczytania formę bez zmiany jego funkcjonalności. Obfuscator jest stosowany do ochrony własności intelektualnej, utrudniania analizy kodu i zapobiegania reverse engineeringowi. Według Android Developers Documentation, obfuskacja przez R8 i ProGuard jest standardowym etapem kompilacji produkcyjnej aplikacji Android.
Najważniejsze
Obfuscator — program wykonujący obfuskację: przekształcenie czytelnego kodu w równoważny funkcjonalnie, ale nieczytelny dla człowieka. Główne zadania obfuskatora to zmiana nazw identyfikatorów (identifier mangling), usuwanie informacji debugowania, zaciemnianie przepływu sterowania (control flow obfuscation) i szyfrowanie literałów string (string encryption).
Obfuskacja nie jest szyfrowaniem. Zaszyfrowany kod nie może być wykonany bez deszyfracji. Kod obfuskowany jest wykonywany bezpośrednio przez JVM, ART lub silnik JavaScript, ale człowiekowi bardzo trudno go zrozumieć. Obfuskacja nie daje absolutnej ochrony — zmotywowany specjalista zawsze może przywrócić logikę przez deobfuskator lub debugowanie w runtime.
Pierwszy komercyjny obfuskator ProGuard pojawił się w 2002 roku jako narzędzie dla apletów Java. Wraz z rozwojem Android (2008) ProGuard stał się standardem dla rozwoju mobilnego. W 2018 Google wydała R8 jako zamiennik ProGuard dla Android Gradle Plugin 3.4. R8 jest 2–3 razy szybszy od ProGuard i generuje bardziej kompaktowy kod bajtowy dzięki głębokiej optymalizacji na poziomie SSA (Static Single Assignment) — formatu pośredniej reprezentacji umożliwiającego analizę przepływu danych.
W rozwoju web obfuskacja ewoluowała od prostych zamian (YUI Compressor, 2007) do złożonych transformatorów AST (Obfuscator.io, 2016). Nowoczesne obfuskatory JavaScript używają control flow flattening, opaque predicates (warunki zawsze true lub false, ale nieoczywiste dla analizatora) i szyfrowanie stringów z samodeszyfrowaniem w runtime. Jscrambler (2012) integruje obfuskację z ochroną przed debuggerem i mechanizmami DRM.
Zakres zastosowania obfuskacji jest szeroki. W rozwoju mobilnym obfuskatory chronią kod przed kradzieżą przez dekompilatory APK (jadx, APKTool, dex2jar). W rozwoju web obfuskacja JavaScript chroni algorytmy, klucze API i logikę biznesową strony klienckiej. W bibliotekach i SDK obfuskacja zapobiega wykorzystywaniu kodu przez konkurentów.
| Technika | Przed obfuskacją | Po obfuskacji |
|---|---|---|
| Zmiana nazw klas | NetworkManager | a |
| Zmiana nazw metod | sendRequest() | b() |
| Szyfrowanie stringów | "API_KEY" | decrypt("x9fK2p") |
| Zaciemnianie warunków | if (a > b) | if (a > b ? true : false) |
Identifier mangling (zmiana nazw identyfikatorów) — najpopularniejsza metoda. Nazwy klas, metod, pól i zmiennych są zastępowane krótkimi, nieinformacyjnymi stringami: a, b, c, aa, ab. Utrudnia to zrozumienie przeznaczenia każdego elementu kodu. ProGuard i R8 używają identycznych nazw dla różnych typów (klasa A, pole A, metoda A), dodatkowo zaciemniając analizę.
Control flow obfuscation (zaciemnianie przepływu sterowania) zmienia strukturę kodu tak, że liniowa sekwencja staje się nieoczywista. Dodawane są martwe gałęzie (dead branches), warunki są odwracane (if (!a) zamiast if (a)), wstawiane są operatory goto (break/continue z etykietami). Utrudnia to analizę przez dekompilator i debugger.
// Kod źródłowy
function authenticate(token) {
const url = "https://api.example.com/auth";
const headers = { Authorization: "Bearer " + token };
return fetch(url, { method: "POST", headers });
}// Po Obfuscator.io w trybie high
const _0x4f2e = ["https://api.example.com/auth",
"Authorization", "Bearer ", "POST"];
(function(_0x5a3b, _0x4f2e) {
const _0x1c2d = function(_0x3e4f) {
while (--_0x3e4f) {
_0x5a3b["push"](_0x5a3b["shift"]());
}
};
_0x1c2d(++_0x4f2e);
}(_0x4f2e, _0x1c2d));
function _0x1c2d(_0x5a3b, _0x4f2e) {
return _0x4f2e[_0x5a3b];
}
function _0x3e4f(_0x1c2d) {
const _0x5a3b = _0x1c2d(0, "https://api.example.com/auth");
const _0x4f2e = { Authorization: "Bearer " + _0x1c2d };
return fetch(_0x5a3b, { method: "POST", headers: _0x4f2e });
}Obfuscator.io dodał tablice stringów, funkcję samowywołującą do mieszania tablicy, zmienił nazwy wszystkich identyfikatorów i zastąpił stringi indeksami tablicy. Kod źródłowy z 5 linii zamienił się w 20+ nieczytelnych linii, ale funkcjonalność authenticate(token) została w pełni zachowana. Deobfuskacja jest możliwa przez analizę AST, ale wymaga czasu.
ProGuard — klasyczny obfuskator dla Java i Android, używany od 2002 roku. ProGuard wykonuje trzy zadania: kompresję (shrinking — usuwanie nieużywanych klas i metod), optymalizację (optymalizacja kodu bajtowego) i obfuskację (zmiana nazw identyfikatorów). ProGuard jest wbudowany w Android Gradle Plugin przez plik proguard-rules.pro z regułami wykluczeń dla bibliotek.
R8 — nowocześniejszy obfuskator, włączony do Android Gradle Plugin od AGP 3.4. R8 wykonuje te same funkcje co ProGuard, ale szybciej (napisany od nowa w Kotlinie) i efektywniej (lepiej optymalizuje kod bajtowy dla ART Runtime). Konfiguracja R8 odbywa się przez te same pliki proguard-rules.pro co dla ProGuard. Aby włączyć R8, wystarczy ustawić minifyEnabled true w build.gradle.
Plik mapping — wynik działania R8/ProGuard, zawierający mapowanie między oryginalnymi a obfuskowanymi nazwami klas, metod i pól. Plik mapping jest krytyczny dla analizy raportów błędów: bez niego stack trace będzie zawierać a.a.b zamiast com.example.app.MainActivity.onCreate. Firebase Crashlytics i Sentry automatycznie ładują pliki mapping i przywracają oryginalne nazwy w raportach.
Pliki mapping należy przesyłać do Firebase lub Sentry przy każdej publikacji nowej wersji aplikacji. Jeśli plik mapping zostanie zgubiony lub nie zostanie przesłany, wszystkie raporty błędów po obfuskacji staną się nieczytelne. Android Gradle Plugin automatycznie zapisuje plik mapping w build/outputs/mapping/release/mapping.txt. Do Firebase używa się wtyczki Crashlytics Gradle Plugin, która przesyła mapping przy kompilacji wersji release.
// app/build.gradle — obfuskacja przez R8
android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile(
"proguard-android-optimize.txt"),
"proguard-rules.pro"
}
}
}# proguard-rules.pro — reguły zachowania
# Zachować model danych dla Gson
-keep class com.example.model.** { *; }
# Zachować klasy dla interfejsów Retrofit
-keep,allowobfuscation interface com.example.api.*
# Nie obfuskowac publicznych aktywności
-keep class * extends android.app.Activity {
public protected *;
}
# Usunąć logi w produkcji
-assumenosideeffects class android.util.Log {
public static boolean isLoggable(...);
public static int v(...);
public static int d(...);
}Reguły -keep w proguard-rules.pro są krytyczne — bez nich R8 usunie lub zmieni nazwy klas i metod używanych przez refleksję (Gson, Retrofit, Room). assumenosideeffects usuwa wywołania Log.v i Log.d z kodu produkcyjnego. Biblioteki takie jak Gson, Retrofit i OkHttp dostarczają gotowe reguły w proguard.txt wewnątrz AAR.
Obfuscator.io — najpopularniejszy otwarty obfuskator JavaScript z obsługą zmiany nazw identyfikatorów, szyfrowania stringów, zaciemniania przepływu sterowania (control flow flattening) i ochrony przed debugowaniem (debug protection). Konfiguracja obfuskacji odbywa się przez JSON-konfig lub CLI. Darmowa wersja obsługuje podstawowe metody; wersja Enterprise dodaje polimorficzny kod i samoochronę.
Jscrambler — komercyjny obfuskator JavaScript z rozszerzoną ochroną: transformacje polimorficzne (każde uruchomienie generuje nowy obfuskowany kod), ochrona przed debuggerami (detekcja DevTools), ochrona przed zrzutami ekranu (self-defending) i mechanizmy wygaszania (kod przestaje działać po określonej dacie). Jscrambler jest używany w aplikacjach bankowych i systemach DRM.
// obfuscate.js — konfiguracja Obfuscator.io
const JavaScriptObfuscator = require("javascript-obfuscator");
const fs = require("fs");
const code = fs.readFileSync("app.js", "utf8");
const result = JavaScriptObfuscator.obfuscate(code, {
compact: true,
controlFlowFlattening: true,
controlFlowFlatteningThreshold: 0.75,
numbersToExpressions: true,
simplify: false,
stringArray: true,
stringArrayThreshold: 0.8,
debugProtection: true,
disableConsoleOutput: true,
});
fs.writeFileSync("app.obfuscated.js", result.code);Parametry Obfuscator.io: controlFlowFlattening: 0.75 zaciemnia przepływ sterowania w 75% bloków; stringArray: true przenosi stringi do tablicy; debugProtection zapobiega otwieraniu DevTools; disableConsoleOutput usuwa console.log. Im wyższe progi, tym dłuższy czas obfuskacji i większy rozmiar kodu, ale tym trudniejsza analiza.
Obfuskacja nie chroni przed analizą w runtime. Atakujący może uruchomić aplikację w debuggerze (Frida, Objection, Xposed) i przechwytywać metody w czasie rzeczywistym. Obfuskacja chroni przed analizą statyczną (dekompilacja APK, odczytywanie kodu bajtowego), ale nie przed dynamiczną. Do ochrony przed analizą w runtime wymagane są dodatkowe środki: SSL Pinning, Root Detection, Integrity Verification.
Rozmiar aplikacji po obfuskacji może wzrosnąć o 20–50%. Control flow obfuscation dodaje martwe gałęzie i duplikuje warunki — zwiększa to ilość kodu bajtowego. String encryption zamienia krótkie literały stringów na wywołania decrypt(), co również zwiększa rozmiar. Dla aplikacji mobilnych jest to krytyczne, ponieważ rozmiar APK bezpośrednio wpływa na konwersję w Google Play.
Wydajność również cierpi. Control flow obfuscation dodaje dodatkowe sprawdzenia i rozgałęzienia, zwiększając czas wykonania metod o 5–15%. String encryption dodaje wywołanie decrypt przy każdym dostępie do stringa. Dla krytycznych wydajnościowo funkcji (onDraw w Android, render w React) obfuskację należy wyłączać przez reguły -keep.
Często zadawane pytania
Szyfrowanie sprawia, że kod jest niewykonywalny bez deszyfracji — do wykonania wymagany jest deszyfrator. Obfuskacja sprawia, że kod jest nieczytelny, ale wykonywalny bezpośrednio. Szyfrowanie daje silniejszą ochronę, ale wymaga ładowacza-deszyfratora, który sam może być przeanalizowany.
Deobfuskacja jest możliwa, ale pracochłonna. Narzędzia takie jak jadx, JEB Decompiler i UnConfuser przywracają kod bajtowy z częściową deobfuskacją. Całkowite przywrócenie kodu źródłowego z oryginalnymi nazwami jest niemożliwe — nazwy są tracone bezpowrotnie. Nowoczesne obfuskatory (R8, ProGuard) są odporne na automatyczną deobfuskację.
Google Play nie wymaga obfuskacji, ale zdecydowanie ją zaleca przez minifyEnabled w build.gradle. Aplikacje bez obfuskacji są łatwo dekompilowane przez APKTool i jadx, co czyni je podatnymi na kradzież kluczy API, modyfikację i piractwo. Większość dużych aplikacji używa R8 lub ProGuard.
Raporty błędów po obfuskacji zawierają obfuskowane nazwy (a.b.c zamiast com.example.app.MainActivity). Do przywrócenia używa się plików mapping generowanych przez R8/ProGuard. Plik mapping musi być przesłany do Firebase Crashlytics lub Sentry w celu automatycznej deobfuskacji stack trace.
String Encryption — zastąpienie literałów string (klucze API, URL, komunikaty) zaszyfrowanymi danymi z wywołaniem funkcji decrypt w runtime. Chroni to poufne stringi przed odczytaniem przez proste wyszukiwanie w zdekompilowanym kodzie. R8/ProGuard obsługują string encryption przez regułę -encryptstrings.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również