Ionic签名校验验证

  • 1. 为什么要进行签名校验
  • 2. 查看安卓证书信息
  • 3. 编写检验文件
  • 3.1 编写校验文件`SignCheck.java`
  • 3.2 修改MainActivity.java
  • 3.3 其他方案
  • 4. 打包
  • 4.1 移除android平台
  • 4.2 增加android平台版本
  • 4.3 覆盖`MainActivity.java`
  • 4.4 使用证书进行打包
  • 5. 打包完成安装apk
  • 6. 加固原理


1. 为什么要进行签名校验

  1. 有些应用会使用工具对应用进行扫描进行判断所存在的风险,特别一些国企,金融,安全类等应用,其中扫描有一项是应用签名未校验风险
  2. 危害的风险描述: 签名证书是对App开发者身份的唯一标识,开发者可利用签名证书有效降低App的盗版率,。未进行签名证书的App,可能被反编译后进行二次打包。重新打包签名的应用,可能导致App被仿冒盗版,影响其合法收入,甚至可能被添钓鱼代码、病毒代码、恶意代码,导致用户敏感信息泄露或者恶意攻击。
  3. 因此针对这个需求需要对应用进行处理,可以检测App程序启动时是否校验签名证书。增加签名证书的校验代码,降低App被二次打包的几率。
  4. 通常会对应用进行几步操作: 1.增加签名校验。 2.对App进行加固。3.对加固的App进行二次签名。

2. 查看安卓证书信息

Ionic移动开发完成后,需要进行打包操作,此时需要使用证书进行打包。

证书可以自己进行生成。那如何查看证书生成后的信息呢?

# 在证书的文件根目录运行
keytool -list -v -keystore mykeystorefile(证书名称)
# 输入密钥库口令,这时请输入证书生成时所用到的密码

钥库类型: JKS
密钥库提供方: SUN

您的密钥库包含 1 个条目

别名: com.zhangguoye.app
创建日期: 2018-1-26
条目类型: PrivateKeyEntry
证书链长度: 1
证书[1]:
所有者: CN=com.zhangguoye.app, OU=com.zhangguoye.app, O=com.zhangguoye.app
发布者: CN=com.zhangguoye.app, OU=com.zhangguoye.app, O=com.zhangguoye.app
序列号: 1230f945
有效期开始日期: Tue Feb 26 15:04:52 CST 2018, 截止日期: Sat Feb 20 15:04:52 CST 2020
证书指纹:
         MD5: 12:34:56:68:6A:78:22:90:12:FE:12:95:12F:6E:29:12
         SHA1: 12:23:45:E1:D1:01:A2:12:37:B2:E3:B1:79:21:16:FD:0B:8D:3E:EB
         SHA256: 12:10:37:15:73:D1:11:F1:32:23:33:63:12:9A:12:12:ED:12:12:12:12:2D:02:12:22:4A:52:62:92:51:22:01
         签名算法名称: S*********
         版本: 3

扩展: 

.....
*******************************************
*******************************************

3. 编写检验文件

3.1 编写校验文件SignCheck.java

package com.zhangguoye.app; // 你的包名
import android.app.AlertDialog;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.DialogInterface;
import android.util.Log;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;



public class SignCheck {
  private Context context;
  private String cer = null;
  private String realCer = null;
  private static final String TAG = "SignCheck";

  public SignCheck(Context context) {
    this.context = context;
    this.cer = getCertificateSHA1Fingerprint();
  }

  public SignCheck(Context context, String realCer) {
    this.context = context;
    this.realCer = realCer;
    this.cer = getCertificateSHA1Fingerprint();
  }

  public String getRealCer() {
    return realCer;
  }

  /**
   * 设置正确的签名
   *
   * @param realCer
   */
  public void setRealCer(String realCer) {
    this.realCer = realCer;
  }

  /**
   * 获取应用的签名
   *
   * @return
   */
  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 进制转换
  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();
  }

  /**
   * 检测签名是否正确
   * @return true 签名正常 false 签名不正常
   */
  public boolean check() {

    if (this.realCer != null) {
      cer = cer.trim();
      realCer = realCer.trim();
      if (this.cer.equals(this.realCer)) {
        return true;
      }
    }else {
      Log.e(TAG, "未给定真实的签名 SHA-1 值");
    }
    return false;
  }

  public void checkSuccess() {
    new AlertDialog.Builder(context)
      .setTitle("签名校验成功")
      .setMessage("成功!")
      .setPositiveButton("确定", null)
      .show();
  }

  public void showCheckErrorTips() {
    new AlertDialog.Builder(context)
      .setTitle("签名校验失败")
      .setMessage("存在签名异常,请在官方下载最新的APP!http://zhangguoye.com")
      .setCancelable(false)
      .setPositiveButton("确定", new DialogInterface.OnClickListener() {

        @Override
        public void onClick(DialogInterface dialog, int which) {
          System.exit(0);
        }

      })
      .show();
  }
}

3.2 修改MainActivity.java

修改MainActivity.java,增加检验签名方法

package com.zhangguoye.app; // 你的包名

import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Base64;
import org.apache.cordova.*;
import java.io.UnsupportedEncodingException;

public class MainActivity extends CordovaActivity {

    public static String param = "";

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // ...
        // 此处增加方法调用
        singCheck();
        // ...
    }

	/**
	* 增加签名检验方法
	**/
    private void singCheck() {
      SignCheck signCheck = new SignCheck(this, "12:23:45:E1:D1:01:A2:12:37:B2:E3:B1:79:21:16:FD:0B:8D:3E:EB"); // 第2步查看签名指纹的SHA1
      if (signCheck.check()) {
        // 签名正常
      } else {
      	// 签名异常
        signCheck.showCheckErrorTips();
      }
    }
}

3.3 其他方案

可以编写安卓的方法,通过ionic层使用javascript调用进行通信,同样实现运行时进行签名的校验。
可以封装成签名校验的cordova插件,供ionic项目进行使用。

4. 打包

4.1 移除android平台

ionic cordova platform rm android

4.2 增加android平台版本

ionic cordova platform add android

若需要指定版本则

ionic cordova platform add android@6.5.0

4.3 覆盖MainActivity.java

  • 在目录下找到此文件并覆盖
    platforms/android/src/com/zhangguoye 因为目录可能跟你的不同,你可以直接在项目里搜索此文件进行覆盖
  • MainActivity.java当前目录加入文件SignCheck.java

4.4 使用证书进行打包

ionic cordova build android --device --minifycss --minfyjs --optimizejs --prod --release -- -- --keystore=./certification/keystore/zhangguoye --alias=com.zhangguoye.app
  • 注意keystore=./certification/keystore/zhangguoye --alias=com.zhangguoye.app 这一段是配置成你自己的证书路径和证书名称、证书别名

5. 打包完成安装apk

打包完成,安装apk并启动。

此时若是App被人使用其他证书进行二次签名后,会有对应的提示。

确定后会关闭APP

ios未签名应用 app未签名会怎么样_android

6. 加固原理

这里有一篇介绍安卓加固的原理文章