¿Cómo comprobar si el APK está firmado o "depurar compilación"?

121

Por lo que yo sé, en Android "versión de compilación" está firmado APK. ¿Cómo verificarlo desde el código o Eclipse tiene algún tipo de definición secreta?

Necesito esto para depurar elementos de ListView de llenado de datos de servicios web (no, logcat no es una opción).

Mis pensamientos:

  • Aplicación android:debuggable, pero por alguna razón no parece confiable.
  • Codificar el ID del dispositivo no es una buena idea, porque estoy usando el mismo dispositivo para probar APK firmados.
  • ¿Utiliza la bandera manual en algún lugar del código? Plausible, pero definitivamente me olvidaré de cambiar en algún momento, además de que todos los programadores son vagos.
Im0rtality
fuente
La edición de Phil retrocedió. No se trata de que el programa se distribuya legalmente en el mercado o no. Se trata de un programa todavía en "modo de depuración".
Im0rtality
Esta es la forma más fácil de hacerlo: stackoverflow.com/a/23844716/2296787
MBH

Respuestas:

80

Hay diferentes formas de verificar si la aplicación se compila mediante depuración o certificado de liberación, pero la siguiente forma me parece la mejor.

De acuerdo con la información en la documentación de Android Firma de su aplicación , la clave de depuración contiene el siguiente nombre distinguido del sujeto: " CN = Android Debug, O = Android, C = US ". Podemos usar esta información para probar si el paquete está firmado con la clave de depuración sin codificar la firma de la clave de depuración en nuestro código.

Dado:

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

Puede implementar un método isDebuggable de esta manera:

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
fuente
6
Para ayudar a resolver varios emparejamientos de importación, las clases que se utilizan aquí son java.security.cert.X509Certificate, java.security.cert.CertificateExceptiony android.content.pm.Signature. Todas las demás clases no presentan coincidencias múltiples para mí
Christian García
1
Respuesta editada con estas importaciones. ¡Gracias!
Cory Petosky
¿Es lo suficientemente eficiente como para ejecutarse en el método onCreate de la clase de aplicación?
desarrollador de Android
No he notado el tiempo de ejecución, pero lo he estado usando en mi aplicación y no tengo ningún problema con la eficiencia.
Omar Rehman
El resultado se puede almacenar en caché para mayor eficiencia.
ftvs
138

Para verificar la bandera depurable, puede usar este código:

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

Kotlin:

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

Para obtener más información, consulte Protección de aplicaciones LVL de Android .

Alternativamente, si está usando Gradle correctamente, puede verificar si BuildConfig.DEBUGes verdadero o falso.

Blundell
fuente
parece que esto todavía verifica android: debuggable del manifiesto
xster
2
La primera prueba para Manifest debuggable, esto está en desuso. El segundo no es posible para las bibliotecas, la biblioteca tendrá su propio BuildConfig, no podrá importar el BuildConfig de la aplicación, que está usando Lib. Por lo tanto, la respuesta marcada es "ok"
Christoph
Esta respuesta funcionará en todos los casos, independientemente del proyecto de la biblioteca o el proyecto de aplicación.
Lavekush Agrawal
131

Respondido por Mark Murphy

La solución más simple y mejor a largo plazo es usar BuildConfig.DEBUG. Este es un booleanvalor que será truepara una compilación de depuración, de lo falsecontrario:

if (BuildConfig.DEBUG) {
  // do something for a debug build
}
Zar E Ahmer
fuente
8
El único inconveniente de este enfoque es que no funcionará en proyectos de bibliotecas (aar). Cuando se crean bibliotecas, esto dará como resultado falso, por lo que incluso si una aplicación que usa la biblioteca está en modo de depuración, esta verificación resultará en falso dentro del código de la biblioteca.
Vito Andolini
24

Si desea verificar APKestáticamente, puede usar

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

Esto genera 0si APKno se puede depurar y 1si lo es.

Heath Borders
fuente
3
esta es la única solución, para verificar sobre el apk final. Las otras respuestas asumen que tienes la fuente.
Guillermo Tobar
1
aaptvive aquí/Users/USER_NAME/library/Android/sdk/build-tools/28.0.3/aapt
Casey
21

Quizás tarde, pero iosched usa BuildConfig.DEBUG

urSus
fuente
¿ahora es seguro de usar? hay un artículo que dice que tiene algunos problemas: digipom.com/be-careful-with-buildconfig-debug
desarrollador de Android
¡Esta es la mejor respuesta!
Peter Fortuin
no si está escribiendo una biblioteca de terceros y no conoce el paquete de BuildConfig en tiempo de compilación.
Sam Dozor
Sam, ¿puedes dar más detalles sobre esto?
Agamemnus
10

Primero agregue esto a su archivo build.gradle, esto también permitirá la ejecución en paralelo de versiones de depuración y liberación:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

Agrega este método:

public static boolean isDebug(Context context) {
    String pName = context.getPackageName();
    if (pName != null && pName.endsWith(".debug")) {
        return true;
    } else {
        return false;
    }
}
Hombre malo
fuente
1
Prefiero esta respuesta porque es confiable. Sin embargo, necesitaba agregar una nueva entrada de "aplicación de Android permitida" a mi clave API de Google Maps (ya que la identificación de la aplicación es diferente).
Baz
5

También se firma una compilación de depuración, solo que con una clave diferente. Eclipse lo genera automáticamente y su certificado es válido solo por un año. ¿Cuál es el problema android:debuggable? Puede obtener este valor del código usando PackageManager.

Nikolay Elenkov
fuente
3

Otra opción digna de mención. Si necesita ejecutar algún código solo cuando se adjunta el depurador, use este código:

if (Debug.isDebuggerConnected() || Debug.waitingForDebugger()) { 
    //code to be executed 
}
Igor Gorjanc
fuente
0

Resuelto con android:debuggable. Hubo un error en la lectura del elemento donde, en algunos casos, el indicador de depuración en el elemento no se almacenaba en el registro y if (m.debug && !App.isDebuggable(getContext()))siempre se evaluaba false. Culpa mía.

Im0rtality
fuente
13
Me doy cuenta de que esto tiene más de un año, sin embargo, debería haber aceptado la respuesta de @Omar Rehman, no esta. Si bien lo que ha publicado es lo que finalmente hizo, en realidad no responde a la pregunta que hizo, mientras que la solución de Omar parece hacerlo, lo que significa que él merece el crédito aceptado.
mah
7
@Mah: ¡es absolutamente inapropiado intimidar a alguien por no aceptar una respuesta que se publicó casi un año después de que resolvieron su problema ellos mismos! Y eso es incluso ignorando cuánto más complicada es la respuesta que nominarías que la que eligieron, lo que de hecho responde a la pregunta formulada, ya que la pregunta fue provocada por un error que dio la impresión errónea de que la bandera no es confiable. .
Chris Stratton
4
@ChrisStratton, si cree que mi respuesta fue intimidación, creo que no lee mucho Internet. Apoyo su derecho a publicar su punto de vista opuesto en un comentario como lo hizo, y como resultado, revisé mi comentario y las otras publicaciones en esta pregunta, y mantengo mi comentario original: el póster hizo una pregunta que era legítima y correctamente respondido por alguien. Basado en su propia "respuesta", su pregunta original no es lo que quería hacer en primer lugar ... la firma de un APK (liberación o depuración) tiene exactamente cero relación con el manifiesto.
mah
3
@mah: depende del solicitante, no de usted, decidir qué satisface su necesidad real de aplicación, incluso si la distinción que plantea es importante o, como aparentemente en este caso, no. Más importante aún, pasa por alto por completo que la respuesta que nominaría se publicó casi un año después de que se satisfizo su necesidad real . Es castigar a alguien por no regresar para cambiar su aceptación más de un año después, lo que constituye acoso.
Chris Stratton
3
@ChrisStratton también depende del autor de la pregunta hacer la pregunta real a la que quiere una respuesta, algo que no se hizo en este caso. Tiene razón en que pasé por alto cuando se hizo la respuesta que en realidad responde a lo que se publicó, sin embargo, no hay ningún castigo, solo hay una expresión razonable de mi opinión. Si cree que estoy actuando como un matón, le pido encarecidamente que marque mi comentario como abuso. Sin embargo, antes de hacerlo, es posible que desee examinar sus propias publicaciones aquí.
mah
0

Solución en Kotlin que estoy usando en este momento:

@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
}

de esa manera todavía puedo FIRMAR en la depuración y se informará a Crashlytics (por ejemplo, para el proceso de control de calidad)

Rafael Ruiz Muñoz
fuente