Ochrona kluczy ProGuardem

Jak zintegrować beanguard-client z własną aplikacją i zabezpieczyć klucze kryptograficzne przed dekompilacją obfuskacją ProGuard — w Twoim projekcie.

Dlaczego to jest konieczne

beanguard-client wymaga implementacji interfejsu BeanGuardConfiguration — zobacz Integrację klienta. Zwracane wartości (URL serwera, klucz publiczny RSA, sekret AES, klucz i sekret licencji) muszą być zakodowane w skompilowanym kodzie vendora — nie mogą pochodzić z pliku konfiguracyjnego ani zmiennej środowiskowej. Jeśli atakujący uruchomi dekompilator na Twoim JARze, zobaczy je jako czytelne stringi — dlatego musisz przepuścić swoją aplikację przez ProGuard.

beanguard-client samo w sobie nie jest obfuskowane — to biblioteka open source (Apache 2.0), jej kod źródłowy jest publicznie dostępny na GitHubie, więc obfuskowanie własnych wnętrzności niczego by nie ukryło. Cała ochrona opisana na tej stronie dotyczy wyłącznie kluczy, które Ty osadzasz w swojej implementacji BeanGuardConfiguration.

Krok 1 — implementacja BeanGuardConfiguration

Utwórz klasę implementującą BeanGuardConfiguration w swoim projekcie. Klucze pobierz z panelu administracyjnego serwera BeanGuard (zakładka Ustawienia → Klucze kryptograficzne), a klucz/sekret licencji — z panelu, w którym została ona wykupiona.

package com.example.myapp.licence;

import dev.beanguard.client.config.BeanGuardConfiguration;
import dev.beanguard.client.config.LicenceKeys;
import dev.beanguard.client.config.ServerConfig;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class MyBeanGuardConfiguration implements BeanGuardConfiguration {

    @Override
    public ServerConfig getServerConfig() {
        return new ServerConfig(
            "https://api.beanguard.dev",
            "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...",
            "dGhpcyBpcyBhIHNlY3JldCBrZXkgZm9yIEFFUy0yNTY="
        );
    }

    @Override
    public Optional<LicenceKeys> getLicenceKeys() {
        return Optional.of(new LicenceKeys(
            "11111111-1111-1111-1111-111111111111",
            "my-licence-secret"
        ));
    }

    @Override
    public Optional<String> loadLicence() {
        return Optional.empty();
    }

    @Override
    public void saveLicence(String licence) {
        // zapisz odebraną licencję lokalnie
    }
}

Klasy nie umieszczaj w pakiecie dev.beanguard.* — to Twój kod, nie kod biblioteki.

Krok 2 — ProGuard w projekcie vendora

Dodaj proguard-maven-plugin do pom.xml swojej aplikacji. Zakładamy, że budujesz fat-JAR (Spring Boot executable JAR via spring-boot-maven-plugin).

Opcja A: osobny moduł Maven (rekomendowana)

Wydziel implementację BeanGuardConfiguration do modułu myapp-licence-keys:

myapp/
├── myapp-app/          ← główna aplikacja Spring Boot
└── myapp-licence-keys/ ← tylko implementacja BeanGuardConfiguration, obfuskowany osobno
    └── pom.xml

W myapp-licence-keys/pom.xml dodaj:

<build>
    <plugins>
        <plugin>
            <groupId>com.github.wvengen</groupId>
            <artifactId>proguard-maven-plugin</artifactId>
            <version>2.6.1</version>
            <dependencies>
                <dependency>
                    <groupId>com.guardsquare</groupId>
                    <artifactId>proguard-base</artifactId>
                    <version>7.5.0</version>
                </dependency>
            </dependencies>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>proguard</goal>
                    </goals>
                </execution>
            </executions>
            <configuration>
                <injar>${project.build.finalName}.jar</injar>
                <outjar>${project.build.finalName}.jar</outjar>
                <outputDirectory>${project.build.directory}</outputDirectory>
                <obfuscate>true</obfuscate>
                <addMavenDescriptor>false</addMavenDescriptor>
                <libs>
                    <lib>${java.home}/jmods</lib>
                </libs>
                <options>
                    <option>-dontshrink</option>
                    <option>-dontoptimize</option>
                    <option>-keepattributes *Annotation*,Signature,Exceptions</option>
                    <option>-dontusemixedcaseclassnames</option>
                    <option>-dontwarn **</option>
                    <!--
                        Zachowaj adnotację @Component żeby Spring mógł wykryć bean przez component scan.
                        Sama nazwa klasy i metody zostaną zmienione przez ProGuard — to właśnie ta klasa
                        (Twoja implementacja) ma zostać obfuskowana, nie BeanGuardConfiguration.
                    -->
                    <option>-keepclassmembers class * implements dev.beanguard.client.config.BeanGuardConfiguration {
                        @org.springframework.stereotype.Component *;
                    }</option>
                </options>
            </configuration>
        </plugin>
    </plugins>
</build>

W myapp-app/pom.xml dodaj zależność do obfuskowanego modułu:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>myapp-licence-keys</artifactId>
    <version>${project.version}</version>
</dependency>

Opcja B: klucze jako tablica bajtów zamiast Stringa

Dekompilatory takie jak Fernflower lub CFR wyświetlają stringi w czytelnej formie nawet po obfuskacji nazw. Trudniejsza do odczytania jest tablica bajtów:

@Override
public ServerConfig getServerConfig() {
    // Każdy bajt to jeden znak URL-a — dekompilator pokaże tablicę liczb, nie string
    byte[] encoded = {
        104, 116, 116, 112, 115, 58, 47, 47, 97, 112, 105, 46,
        98, 101, 97, 110, 103, 117, 97, 114, 100, 46, 100, 101, 118,
        // ... pozostałe bajty
    };
    return new ServerConfig(new String(encoded), "...", "...");
}

Wygeneruj tablicę bajtów z gotowego tekstu:

python3 -c "
payload = 'https://api.beanguard.dev'
print(', '.join(str(b) for b in payload.encode('utf-8')))
"

Tę technikę możesz łączyć z ProGuardem — daje dwie warstwy utrudnienia.

Krok 3 — weryfikacja obfuskacji

Po zbudowaniu sprawdź, że nazwa Twojej klasy jest zmieniona:

jar tf target/myapp-licence-keys-*.jar | grep "com/example"

Prawidłowy output — nazwa klasy zmieniona na jedną literę lub krótki ciąg:

com/example/myapp/licence/a.class

Nieprawidłowy output — klasa widoczna pod oryginalną nazwą:

com/example/myapp/licence/MyBeanGuardConfiguration.class

Sprawdź też, czy Spring nadal wykrywa bean:

mvn spring-boot:run

W logach przy starcie powinien pojawić się komunikat z BeanGuard o poprawnym wczytaniu licencji.

Krok 4 — konfiguracja

BeanGuardConfiguration nie pochodzi z application.yml — to jest zamierzone. URL serwera i klucze są w kodzie, w implementacji dostarczonej przez Twój projekt (Krok 1). beanguard-client nie czyta żadnych property'ów Springa dla tej konfiguracji — bean BeanGuardServer powstaje automatycznie, gdy tylko w kontekście Springa znajdzie się bean typu BeanGuardConfiguration.

Obfuskacja beanguard-client razem z aplikacją

Jeśli obfuskujesz całą swoją aplikację w jednym przebiegu ProGuard/R8 (typowa praktyka dla komercyjnego software, niezależna od BeanGuarda) i chcesz w tym samym przebiegu przetworzyć też klasy beanguard-client — np. żeby ukryć sam fakt, że aplikacja używa biblioteki licencyjnej — możesz dołączyć jar beanguard-client do swoich -injars. Wtedy jednak musisz jawnie zachować kilka klas, na które Twój kod (i Spring) odwołuje się po nazwie:

# Punkt wejścia autokonfiguracji Spring Boot — referencjonowany po pełnej nazwie
# klasy w zasobie META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
# ProGuard nie przepisuje plików zasobów, więc ta nazwa klasy musi pozostać bez zmian.
-keep class dev.beanguard.client.BeanGuardClientAutoConfiguration { *; }

# Interfejs, który implementujesz (Krok 1) i typy, które konstruujesz bezpośrednio.
-keep public interface dev.beanguard.client.config.BeanGuardConfiguration { *; }
-keep public class dev.beanguard.client.config.ServerConfig { *; }
-keep public class dev.beanguard.client.config.LicenceKeys { *; }

# Opcjonalny interfejs rozszerzenia (Krok 1) — pomiń, jeśli go nie implementujesz.
-keep public interface dev.beanguard.client.usage.UsageRegistry { *; }

# Spring musi znaleźć te metody refleksyjnie po adnotacji.
-keepclassmembers class * {
    @org.springframework.scheduling.annotation.Scheduled *;
    @org.springframework.context.event.EventListener *;
}

# Adnotacje pointcutów AspectJ muszą przetrwać obfuskację, inaczej weaving AOP
# (walidacja licencji) przestaje działać po dołączeniu tych klas do przebiegu.
-keepattributes *Annotation*,Signature,Exceptions,InnerClasses,EnclosingMethod

LicenceRegistry i LicenceStatus (dev.beanguard.client.registries.*) możesz swobodnie zostawić bez reguły -keep — zostaną spójnie przemianowane razem z resztą Twojego programu w tym samym przebiegu, bo cały Twój kod jest w tym samym -injars. Jeśli wolisz, żeby pozostały czytelne (np. do debugowania na produkcji), dodaj:

-keep public interface dev.beanguard.client.registries.LicenceRegistry { *; }
-keep public enum dev.beanguard.client.registries.LicenceStatus { *; }

Co ProGuard chroni, a czego nie

Wektor atakuPo obfuskacji
Dekompilator odczytuje nazwę klasyWidzi a.class zamiast MyBeanGuardConfiguration
Dekompilator odczytuje nazwy metodWidzi a(), b() zamiast getServerConfig()
Dekompilator odczytuje treść stringaNadal widzi klucz — chyba że użyjesz tablicy bajtów
Java agent hookuje metodę w runtimeMoże odczytać klucz — poza zakresem ProGuard CE
Heap dump działającej JVMMoże znaleźć klucz w pamięci — poza zakresem ProGuard CE

Ochrona przed pełną ekstrakcją kluczy (Java agenty, heap dump) wymaga narzędzi komercyjnych (DexGuard, Dotfuscator) lub modeli sprzętowych (HSM). Dla typowego use-case'u obfuskacja ProGuardem jest wystarczającym deterrentem.

Czy ta strona była pomocna?