如何检查APK是否已签名或“调试建立”?


121

据我所知,在Android中“发布版本”是签名的APK。如何从代码中检查它或Eclipse是否有某种秘密定义?

我需要它来调试从Web服务数据填充ListView项(不,logcat不是一个选项)。

我的想法:

  • 应用程序的android:debuggable,但由于某种原因看起来不可靠。
  • 硬编码设备ID不是一个好主意,因为我正在使用同一设备来测试签名的APK。
  • 在代码中的某处使用手动标记?有道理,但肯定会忘记更改,而且所有程序员都懒惰。

回滚Phil的编辑。这不是程序是否在市场上合法发行的问题。问题是程序仍处于“调试模式”。
Im0rtality 2011年

这种方式是最简单的方法:stackoverflow.com/a/23844716/2296787
MBH 2015年

Answers:


80

有多种方法可以检查应用程序是否使用调试或发行证书来构建,但是以下方法对我来说似乎是最好的。

根据Android文档“ 签名您的应用程序”中的信息,调试键包含以下主题专有名称:“ CN = Android Debug,O = Android,C = US ”。我们可以使用此信息来测试软件包是否用调试键签名,而无需将调试键签名硬编码到我们的代码中。

鉴于:

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

您可以通过以下方式实现isDebuggable方法:

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
为了解决多个进口匹配问题的帮助,类使用这里java.security.cert.X509Certificatejava.security.cert.CertificateExceptionandroid.content.pm.Signature。所有其他班级都没有为我呈现多次比赛
ChristianGarcía2012年

1
这些导入的编辑答案。谢谢!
2013年

在应用程序类的onCreate方法上运行是否足够有效?
android开发人员

我没有记下执行时间,但是我一直在我的应用程序中使用它,并且效率方面没有任何问题。
奥马尔·雷曼

可以将结果缓存以提高效率。
ftvs 2014年

138

要检查可调试标志,可以使用以下代码:

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

科特林:

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

有关更多信息,请参阅保护Android LVL应用程序安全

另外,如果您正确使用Gradle,则可以检查BuildConfig.DEBUG是对还是错。


看来这仍然可以检查资讯清单的android:debuggable
xster

2
第一个测试清单可调试的,不推荐使用。第二个不适用于库,库将拥有它自己的BuildConfig-无法导入使用Lib的App的BuildConfig。因此,标记的答案是“确定”
Christoph

无论库项目还是应用程序项目,此答案在任何情况下均适用。
Lavekush Agrawal

131

马克·墨菲(Mark Murphy)回答

最简单,最好的长期解决方案是使用BuildConfig.DEBUG。该boolean值将true用于调试版本,false否则:

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

8
这种方法的唯一缺点是,它在图书馆项目(aar)中不起作用。构建库时,这将导致错误,因此,即使使用该库的应用程序处于调试模式,该检查也将在库代码中导致错误。
Vito Andolini 2015年

24

如果要APK静态检查,可以使用

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

0如果APK无法调试,1则输出此消息。


3
这是验证最终apk的唯一解决方案。其他响应假定您有来源。
吉列尔莫·托巴尔

1
aapt住在这里/Users/USER_NAME/library/Android/sdk/build-tools/28.0.3/aapt
Casey


10

首先将其添加到您的build.gradle文件中,这还将允许并排运行调试和发布版本:

buildTypes {
    debug {
        applicationIdSuffix ".debug"
    }
}

添加此方法:

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

1
我更喜欢这个答案,因为它是可靠的。我确实需要在Google Maps API密钥中添加一个新的“允许的Android应用程序”条目(因为应用程序ID不同)。
巴兹(Baz)2015年

5

调试版本也用不同的密钥签名。它是由Eclipse自动生成的,其证书的有效期仅为一年。有什么问题 android:debuggable吗?您可以使用从代码中获取此值PackageManager


3

另一个选择,值得一提。如果仅在连接调试器时才需要执行一些代码,请使用以下代码:

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

0

解决了android:debuggable。这是读取项目时出现的错误,在某些情况下,项目上的调试标记未存储在记录中,因此if (m.debug && !App.isDebuggable(getContext()))始终会评估为false。我的错。


13
我知道这已经有一年多了,但是您应该接受@Omar Rehman的回答,而不是这个。虽然您发布的内容是您最终要做的,但它并不能真正回答您提出的问题,而Omar的解决方案似乎可以解决此问题,这意味着他应得到认可。
2012年

7
@Mah-欺负某人是不适当的,因为他们自己解决问题将近一年后才接受一个答案 而且,这甚至都忽略了您要提名的答案比他们提出的答案要复杂得多-实际上,它回答了所提出的问题,因为该问题是由错误引起的,导致错误地认为该标志是不可靠的。
克里斯·斯特拉顿

4
@ChrisStratton如果您认为我的回应是欺负,我想您不太会阅读互联网。我支持您有权像在评论中那样在评论中发表反对的观点,因此,我已经审阅了我的评论以及该问题的其他帖子,我支持我的原始评论:发帖人提出了一个合理的问题并由某人正确回答。基于他自己的“答案”,他最初的问题不是他首先要问的问题……APK的签名(发布或调试)对清单的影响完全为零。
2013年

3
@mah-由问问者决定,而不是由您决定满足他们实际应用需求的条件,包括您提出的区分是否很重要-或在这种情况下显然不是。更重要的是,您完全忽略了您提名的答案在满足他们的实际需求后将近一年发布。惩罚是因为某人在一年后的大部分时间不回来改变自己的接受方式,这构成了欺凌行为。
克里斯·斯特拉顿

3
@ChrisStratton也要由提问者询问他/她想要答案的实际问题,这种情况在此情况下未完成。您是正确的,当我做出的答案实际上是在回答发布的内容时,我忽略了,但是完全没有任何惩罚,只有合理的表达我的观点。如果您认为我在这里扮演恶霸,我强烈要求您将我的评论标记为滥用。但是,在此之前,您可能希望在这里检查自己的帖子。
2013年

0

我目前正在使用的Kotlin解决方案:

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

这样,我仍然可以在调试中签名,并将其报告给Crashlytics(例如,用于质量检查流程)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.