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.
Im0rtality
źródło
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;
}
Omar Rehman
źródło
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.

Blundell
źródło
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
}
Zar E Ahmer
źródło
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.

Heath Borders
źródło
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
21

Może późno, ale iosched używa BuildConfig.DEBUG

urSus
źródło
czy jest teraz bezpieczny w użyciu? jest artykuł mówiący, że ma pewne problemy: digipom.com/be-careful-with-buildconfig-debug
programista Androida
To najlepsza odpowiedź!
Peter Fortuin
nie, jeśli piszesz bibliotekę innej firmy i nie znasz pakietu BuildConfig w czasie kompilacji.
Sam Dozor,
Sam, możesz to rozwinąć?
Agamemnus
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;
    }
}
Meanman
źródło
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.

Nikolay Elenkov
źródło
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 
}
Igor Gorjanc
źródło
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.

Im0rtality
źródło
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)

Rafael Ruiz Muñoz
źródło