Android中如何进行签名校验和完整性校验

前言

签名校验和完整性校验主要是针对于二次打包的检测防范措施,如果没有签名校验和完整性校验功能,应用可能被恶意攻击者二次打包,被盗版的风险大大增加,同时也可能进行任意代码修改。

针对上面的问题,这章我们就对我们的App进行运行时签名校验。

准备

项目代码
项目代码的主要实现类SignatureChecker,欢迎查看。

验证默认签名信息

1.获取应用的APK

  • 在编写好项目以后,我们通过点击build->build apk.
  • 文件在项目的app/build/outputs/apk/debug/app-debug.apk
    Android中如何进行签名校验和完整性校验_第1张图片

2. 获取调试应用的keystore

  • 打开finder程序,Go -> Home -> ./android/debug.keystore
    在这里插入图片描述
  • 签署您的应用
  • 使用keytool命令查看android模式调试debug.keystore的签名信息,默认密码为空
    Android中如何进行签名校验和完整性校验_第2张图片
    命令:keytool -list -v -keystore debug.keystore

3.信息对比

  • 检查apk文件中的签名信息
    Android中如何进行签名校验和完整性校验_第3张图片
    上面的检查命令:keytool -printcert -file CERT.RSA

  • 对比信息Android中如何进行签名校验和完整性校验_第4张图片
    在这里完全可以看出,我们的apk默认的是经过androidstudio的debug.keystore来签名过的APK。

对APK进行重新签名

创建两个keystore

  • 创建两个KeyStore分别命名keyStoreAkeyStoreB
    Android中如何进行签名校验和完整性校验_第5张图片
  • 创建一个未签名的Apk,命名为app-release-unsigned
    Android中如何进行签名校验和完整性校验_第6张图片

对APK进行签名

  • 使用KeyStoreA文件对app-release-unsigned进行签名,签名后的APK为app-release-keystoreA
jarsigner -verbose -keystore /Users/martin/Downloads/newsign/keystore/keystoreA -signedjar /Users/martin/Downloads/newsign/des/app-release-keystoreA.apk /Users/martin/Downloads/newsign/src/app-release-unsigned.apk android

命令解析

jarsigner的参数说明

  • keystore 参数指定您的私钥的绝对路径,例如:/Users/martin/Downloads/newsign/keystore/keystoreA
  • signedjar 参数指定签名后apk文件存放绝对的路径,例如 /Users/martin/Downloads/newsign/des/app-release-keystoreA.apk
  • [未签名的文件路径] 指定要签名apk文件的绝对路径,也就是您从我们这里下载到的,例如 /Users/martin/Downloads/newsign/src/app-release-unsigned.apk
  • [您的证书名称] 是指您创建密钥时您设置的证书名称

PS:参考MAC对APK包进行重新签名

签名成功:
Android中如何进行签名校验和完整性校验_第7张图片

删除APK的签名信息并重新打包

  • 解压app-release-keystoreA删除META-INFO文件夹下的三个文件

    • ANDROID.RSA
    • ANDROID.SF
    • MANIFEST.MF
      Android中如何进行签名校验和完整性校验_第8张图片
      注意:这里的三个文件的名称因为工具的原因可能会根据改变,我们删除的时候,看他的后缀名称就好。
  • 重新打包删除后的app-release-keystoreA文件,后缀名改为APK。
    Android中如何进行签名校验和完整性校验_第9张图片

  • 使用我们的keyStoreBapp-release-keystoreA进行重新签名,生成app-release-keystoreB

jarsigner -verbose -keystore /Users/martin/Downloads/newsign/keystore/keystoreB -signedjar /Users/martin/Downloads/newsign/des/app-release-keystoreB.apk /Users/martin/Downloads/newsign/src/app-release-keystoreA.apk androidx

Android中如何进行签名校验和完整性校验_第10张图片

防止APP被盗版验证

KeyStoreA和KeyStoreB的签名MD5比较

  • 安装keystoreA签名过的APK文件。
adb install -r /Users/martin/Downloads/newsign/des/app-release-keystoreA.apk

生成MD5值:

8ad65c2224d9c303250c7c49a3672323
  • 修改签名,使用KeyStoreB重新签名
    命令:
adb install -r /Users/martin/Downloads/newsign/des/app-release-keystoreA.apk

生成MD5值:

8fe878055ba594ee41ba8a4b2e0ca3e5

签名_A和B的签名信息比较
注意:这里的MD5值不和KeyStore显示的格式一样,是因为我格式再次进行了MD5加密。

可以看出来,即使是同样的APK,但是使用不同的签名还是会出现不同的APK值。所以,我们可以在应用中做校验,当MD5值不一样时,就不是官方正版的APK了。

对App进行运行时签名校验

刚才上面已经说过了,不同的签名文件对APK打包后,肯定签名信息也是不同的,那么我们就可以针对这点,来对签名进行校验,来确保我们的App是否被别人"盗版"。这里我对SHA1和MD5都做了校验比较。

SignatureValidator

SHA1的校验代码。

public class SignatureValidator {
	...
		public String getCertificateSHA1Fingerprint() {
        //获取包管理器
        PackageManager pm = context.getPackageManager();
 
        //获取当前要获取 SHA1 值的包名,也可以用其他的包名,但需要注意,
        //在用其他包名的前提是,此方法传递的参数 Context 应该是对应包的上下文。
        String packageName = context.getPackageName();
 
        //返回包括在包中的签名信息
        int flags = PackageManager.GET_SIGNATURES;
 
        PackageInfo packageInfo = null;
 
        try {
            //获得包的所有内容信息类
            packageInfo = pm.getPackageInfo(packageName, flags);
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
 
        //签名信息
        Signature[] signatures = packageInfo.signatures;
        byte[] cert = signatures[0].toByteArray();
 
        //将签名转换为字节数组流
        InputStream input = new ByteArrayInputStream(cert);
 
        //证书工厂类,这个类实现了出厂合格证算法的功能
        CertificateFactory cf = null;
 
        try {
            cf = CertificateFactory.getInstance("X509");
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        //X509 证书,X.509 是一种非常通用的证书格式
        X509Certificate c = null;
 
        try {
            c = (X509Certificate) cf.generateCertificate(input);
        } catch (Exception e) {
            e.printStackTrace();
        }
 
        String hexString = null;
 
        try {
            //加密算法的类,这里的参数可以使 MD4,MD5 等加密算法
            MessageDigest md = MessageDigest.getInstance("SHA1");
 
            //获得公钥
            byte[] publicKey = md.digest(c.getEncoded());
 
            //字节到十六进制的格式转换
            hexString = byte2HexFormatted(publicKey);
 
        } catch (NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        } catch (CertificateEncodingException e) {
            e.printStackTrace();
        }
        return hexString;
    }
    
    /**
     * 这里是将获取到得编码进行16 进制转换
     *
     * @param arr
     * @return
     */
    private String byte2HexFormatted(byte[] arr) {
        StringBuilder str = new StringBuilder(arr.length * 2);
 
        for (int i = 0; i <arr.length; i++) {
            String h = Integer.toHexString(arr[i]);
            int l =h.length();
            if (l == 1)
                h = "0" + h;
            if (l > 2)
                h = h.substring(l - 2, l);
            str.append(h.toUpperCase());
            if (i < (arr.length - 1))
                str.append(':');
        }
        return str.toString();
    }
    
    /**
     * 检测SHA1签名是否正确
     * @return true 签名正常 false 签名不正常
     */
    public boolean checkSHA1() {
        if (this.realCer != null) {
            cer = EncryptUtil.MD5(getCertificateSHA1Fingerprint().trim());//再次MD5加密
            realCer = realCer.trim();
            if (this.cer.equals(this.realCer)) {
                return true;
            }
        }else {
            Log.e(TAG, "未给定真实的签名 SHA-1 值");
        }
        return false;
    }
	...
}

上面的代码需要注意的一个地方就是cer = EncryptUtil.MD5(getCertificateSHA1Fingerprint().trim()),这里我对Keystore的签名SHA1信息再次做了MD5的加密,这样就确保了我们的密钥的安全性。

使用方法

    /**
     * 验证SHA1
     */
    public void checkSHA1(String sha1) {
        SignatureValidator signatureValidator = new SignatureValidator(this, sha1);
        if (signatureValidator.checkSHA1()) {
            showToast("ok");
        } else {
            showDialog();
        }
        String signature_sha1 = signatureValidator.getCertificateSHA1Fingerprint();
        signaturText.setText("SHA1:\t" + signature_sha1);
        Log.d(AppConfig.TAG, signature_sha1);
    }

参考文章

  • Android中签名原理和安全性分析之META-INF文件讲解
  • Android中解决破解签名验证之后导致的登录授权失效问题
  • Android App運行時簽名校驗
  • APP 完整性校验

你可能感兴趣的:(安全与逆向)