Jak sprawdzić, czy plik APK jest podpisany, czy „kompilacja do debugowania”?


121

O ile mi wiadomo, w Androidzie „wydanie kompilacji” jest podpisanym plikiem APK. Jak to sprawdzić z kodu, czy Eclipse ma jakieś tajne definicje?

Potrzebuję tego do debugowania wypełniania elementów ListView z danych usługi internetowej (nie, logcat nie jest opcją).

Moje myśli:

  • Aplikacje android:debuggable, ale z jakiegoś powodu nie wyglądają na wiarygodne.
  • Zakodowanie identyfikatora urządzenia nie jest dobrym pomysłem, ponieważ używam tego samego urządzenia do testowania podpisanych plików APK.
  • Używasz ręcznej flagi gdzieś w kodzie? Prawdopodobne, ale na pewno zapomnę kiedyś o zmianie, a wszyscy programiści są leniwi.

Wycofana edycja Phila. Nie chodzi o to, czy program jest legalnie dystrybuowany na rynku, czy nie. Chodzi o to, czy program jest nadal w „trybie debugowania”.
Im0rtality

Najłatwiej to zrobić w ten sposób: stackoverflow.com/a/23844716/2296787
MBH

Odpowiedzi:


80

Istnieją różne sposoby sprawdzenia, czy aplikacja jest zbudowana przy użyciu certyfikatu debugowania lub wydania, ale następujący sposób wydaje mi się najlepszy.

Zgodnie z informacją w dokumentacji systemu Android. Podpisywanie aplikacji , klucz debugowania zawiera nazwę wyróżniającą podmiotu: „ CN = Android Debug, O = Android, C = US ”. Możemy użyć tych informacji do sprawdzenia, czy pakiet jest podpisany kluczem debugowania bez zakodowania podpisu klucza debugowania na stałe w naszym kodzie.

Dany:

import android.content.pm.Signature;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

Możesz zaimplementować metodę isDebuggable w ten sposób:

private static final X500Principal DEBUG_DN = new X500Principal("CN=Android Debug,O=Android,C=US");
private boolean isDebuggable(Context ctx)
{
    boolean debuggable = false;

    try
    {
        PackageInfo pinfo = ctx.getPackageManager().getPackageInfo(ctx.getPackageName(),PackageManager.GET_SIGNATURES);
        Signature signatures[] = pinfo.signatures;

        CertificateFactory cf = CertificateFactory.getInstance("X.509");

        for ( int i = 0; i < signatures.length;i++)
        {   
            ByteArrayInputStream stream = new ByteArrayInputStream(signatures[i].toByteArray());
            X509Certificate cert = (X509Certificate) cf.generateCertificate(stream);       
            debuggable = cert.getSubjectX500Principal().equals(DEBUG_DN);
            if (debuggable)
                break;
        }
    }
    catch (NameNotFoundException e)
    {
        //debuggable variable will remain false
    }
    catch (CertificateException e)
    {
        //debuggable variable will remain false
    }
    return debuggable;
}

6
Aby pomóc w rozwiązywaniu wielu skojarzeń importu, klasy używane są tu java.security.cert.X509Certificate, java.security.cert.CertificateExceptioni android.content.pm.Signature. Wszystkie inne klasy nie prezentują dla mnie wielu meczów
Christian García

1
Edytowana odpowiedź z tymi importami. Dzięki!
Cory Petosky

czy jest wystarczająco wydajny, aby można go było uruchomić w metodzie onCreate klasy aplikacji?
programista Androida,

Nie zanotowałem czasu wykonania, ale używam go w mojej aplikacji i nie mam żadnych problemów z wydajnością.
Omar Rehman

Wynik można zapisać w pamięci podręcznej w celu zwiększenia wydajności.
ftvs

138

Aby sprawdzić flagę debugowalną, możesz użyć tego kodu:

boolean isDebuggable =  ( 0 != ( getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) );

Kotlin:

val isDebuggable = 0 != applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE

Aby uzyskać więcej informacji, zobacz Zabezpieczanie aplikacji Android LVL .

Alternatywnie, jeśli poprawnie używasz Gradle, możesz sprawdzić, czy BuildConfig.DEBUGjest to prawda, czy fałsz.


wygląda na to, że nadal sprawdza plik manifestu android: debuggable
xster

2
Pierwsza z nich testuje pod kątem debugowania Manifest, jest to przestarzałe. Drugi nie jest możliwy dla bibliotek, biblioteka będzie miała własną BuildConfig - nie będzie w stanie zaimportować BuildConfig aplikacji, która używa Lib. Dlatego zaznaczona odpowiedź brzmi „ok”
Christoph

Ta odpowiedź będzie działać we wszystkich przypadkach, niezależnie od projektu biblioteki lub projektu aplikacji.
Lavekush Agrawal

131

Odpowiedział Mark Murphy

Najprostszym i najlepszym długoterminowym rozwiązaniem jest użycie BuildConfig.DEBUG. To jest booleanwartość, która będzie truedotyczyła kompilacji do debugowania, w falseprzeciwnym razie:

if (BuildConfig.DEBUG) {
  // do something for a debug build
}

8
Jedyną wadą tego podejścia jest to, że nie będzie działać w projektach bibliotecznych (aar). Gdy biblioteki są budowane, spowoduje to fałsz, więc nawet jeśli aplikacja korzystająca z biblioteki jest w trybie debugowania, to sprawdzenie spowoduje, że w kodzie biblioteki będzie wartość false.
Vito Andolini

24

Jeśli chcesz sprawdzić APKstatycznie, możesz użyć

aapt dump badging /path/to/apk | grep -c application-debuggable

Te wyjścia 0, jeśli APKnie jest debuggable i 1jeśli to jest.


3
jest to jedyne rozwiązanie, które można zweryfikować w ostatnim apk. Inne odpowiedzi zakładają, że masz źródło.
Guillermo Tobar,

1
aaptmieszka tutaj/Users/USER_NAME/library/Android/sdk/build-tools/28.0.3/aapt
Casey


10

Najpierw dodaj to do pliku build.gradle, pozwoli to również na równoległe uruchamianie kompilacji debugowania i wydania:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Dodaj tę metodę:

public static boolean isDebug(Context context) {
    String pName = context.getPackageName();
    if (pName != null && pName.endsWith(".debug")) {
        return true;
    } else {
        return false;
    }
}

1
Wolę tę odpowiedź, ponieważ jest wiarygodna. Musiałem jednak dodać nowy wpis „dozwolona aplikacja na Androida” do mojego klucza API Map Google (ponieważ identyfikator aplikacji jest inny).
Baz

5

Kompilacja debugowania jest również podpisywana, tylko z innym kluczem. Jest generowany automatycznie przez Eclipse, a jego certyfikat jest ważny tylko przez rok. O co chodzi android:debuggable? Możesz uzyskać tę wartość z kodu za pomocą PackageManager.


3

Inna opcja, o której warto wspomnieć. Jeśli chcesz wykonać jakiś kod tylko wtedy, gdy dołączony jest debugger, użyj tego kodu:

if (Debug.isDebuggerConnected() || Debug.waitingForDebugger()) { 
    //code to be executed 
}

0

Rozwiązany z android:debuggable. Był to błąd w odczycie elementu, w którym w niektórych przypadkach flaga debugowania elementu nie była zapisywana w rekordzie i if (m.debug && !App.isDebuggable(getContext()))zawsze była oceniana false. Mój błąd.


13
Zdaję sobie sprawę, że to już ponad rok, jednak powinieneś zaakceptować odpowiedź @Omar Rehman, a nie tę. Chociaż to, co opublikowałeś, jest tym, co ostatecznie zrobiłeś, tak naprawdę nie odpowiada to na pytanie, które zadałeś, podczas gdy rozwiązanie Omara wydaje się to robić, co oznacza, że ​​zasługuje na uznanie.
mah

7
@Mah - to całkowicie niewłaściwe znęcanie się nad kimś, kto nie przyjął odpowiedzi, która została opublikowana prawie rok po tym, jak sam rozwiązał swój problem! I to nawet ignoruje, o ile bardziej skomplikowana jest odpowiedź, którą nominujesz, niż ta, z którą poszli - co w rzeczywistości odpowiada na zadane pytanie, ponieważ pytanie zostało wywołane przez błąd prowadzący do błędnego wrażenia, że ​​flaga jest niewiarygodna .
Chris Stratton

4
@ChrisStratton, jeśli uważasz, że moja odpowiedź dotyczyła zastraszania, myślę, że nie czytasz dużo w Internecie. Popieram twoje prawo do opublikowania swojego przeciwnego punktu widzenia w komentarzu, tak jak to zrobiłeś, w wyniku czego przejrzałem swój komentarz i inne posty w tym pytaniu i podtrzymuję swój oryginalny komentarz: plakat zadał pytanie, które było uzasadnione i ktoś poprawnie odpowiedział. Opierając się na jego własnej „odpowiedzi”, jego pierwotne pytanie nie jest tym, o co chciał zapytać… Podpis APK (zwolnienie lub debugowanie) nie ma dokładnie żadnego wpływu na manifest.
mah

3
@mah - to od pytającego, a nie od ciebie, zależy, co spełnia ich rzeczywistą potrzebę zastosowania, w tym czy rozróżnienie, które podniesiesz, jest ważne - lub, jak najwyraźniej w tym przypadku, nie. Co ważniejsze, całkowicie przeoczasz, że nominowana przez Ciebie odpowiedź została opublikowana prawie rok po tym, jak ich rzeczywiste potrzeby zostały zaspokojone . Jest to kara kogoś, kto nie wrócił i nie zmienił akceptacji przez większość roku później, co stanowi znęcanie się.
Chris Stratton,

3
@ChrisStratton to również osoba pytająca może zadać rzeczywiste pytanie, na które chce odpowiedzi - coś, czego w tym przypadku nie zrobiono. Masz rację, że przeoczyłem, kiedy padła odpowiedź, która faktycznie odpowiada na to, co zostało zamieszczone, jednak nie ma żadnej kary, jest tylko rozsądne wyrażenie mojej opinii. Jeśli uważasz, że ja tu działam łobuzem, zdecydowanie proszę o oznaczenie mojego komentarza jako nadużycia. Jednak zanim to zrobisz, możesz chcieć przejrzeć tutaj swoje własne posty.
mah

0

Rozwiązanie w Kotlinie, którego w tej chwili używam:

@SuppressLint("PackageManagerGetSignatures")
@Suppress("DEPRECATION")
fun isSigned(context: Context?): Boolean {
    return (context?.packageManager?.getPackageInfo(context.packageName, PackageManager.GET_SIGNATURES)?.signatures?.firstOrNull()?.toByteArray()
            ?.let {
                return@let CertificateFactory.getInstance("X.509").generateCertificate(ByteArrayInputStream(it))
            } as? X509Certificate)
            ?.issuerDN
            ?.name
            ?.contains("O=Android", ignoreCase = false) ?: true
}

w ten sposób nadal mogę ZALOGOWAĆ się w debugowaniu, a te zostaną zgłoszone do Crashlytics (na przykład w przypadku procesu kontroli jakości)

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.