android 关于利用签名的SHA1进行安全校验的方法之一(推荐)

最近做安卓项目中使用到了百度地图的API,在申请百度地图key的时候,需要我们填入“签名的SHA1”和“客户端包名”,然后百度为我们生成一个key。

于是就引发了思考,百度为何需要我们客户端签名的SHA1值呢?

第一想法就是:百度拿我们输入的参数SHA1和包名进行一些列算法计算,生成一个key返回给我们。

为了证明这个想法,写了demo进行测试,android获取包名的方法很简单,但是我们还需要从客户端中获取keystore的指纹SHA1。

进行各种资料的查找和分析,才得出方法。

一、首先,科普一下apk包下的META-INF目录

我们已经知道的是:Android对每一个Apk文件都会进行签名,在Apk文件安装时,系统会对其签名信息进行比对,判断程序的完整性,从而决定该Apk文件是否可以安装,在一定程度上达到安全的目的。

给定一个Apk文件,解压,可以看到一个META-INFO文件夹,在该文件夹下有三个文件:分别为MANIFEST.MF、CERT.SF和CERT.RSA。这三个文件分别表征以下含义:

(1)MANIFEST.MF:这是摘要文件。程序遍历Apk包中的所有文件(entry),对非文件夹非签名文件的文件,逐个用SHA1生成摘要信息,再用Base64进行编码。如果你改变了apk包中的文件,那么在apk安装校验时,改变后的文件摘要信息与MANIFEST.MF的检验信息不同,于是程序就不能成功安装。

说明:如果攻击者修改了程序的内容,有重新生成了新的摘要,那么就可以通过验证,所以这是一个非常简单的验证。

(2)CERT.SF:这是对摘要的签名文件。对前一步生成的MANIFEST.MF,使用SHA1-RSA算法,用开发者的私钥进行签名。在安装时只能使用公钥才能解密它。解密之后,将它与未加密的摘要信息(即,MANIFEST.MF文件)进行对比,如果相符,则表明内容没有被异常修改。

说明:在这一步,即使开发者修改了程序内容,并生成了新的摘要文件,但是攻击者没有开发者的私钥,所以不能生成正确的签名文件(CERT.SF)。系统在对程序进行验证的时候,用开发者公钥对不正确的签名文件进行解密,得到的结果和摘要文件(MANIFEST.MF)对应不起来,所以不能通过检验,不能成功安装文件。

(3)CERT.RSA文件中保存了公钥、所采用的加密算法等信息。

说明:系统对签名文件进行解密,所需要的公钥就是从这个文件里取出来的。

结论:从上面的总结可以看出,META-INFO里面的说那个文件环环相扣,从而保证Android程序的安全性。(只是防止开发者的程序不被攻击者修改,如果开发者的公私钥对对攻击者得到或者开发者开发出攻击程序,Android系统都无法检测出来。)

我们将apk包解包,然后使用命令 keytool -printcert -file CERT.RSA 查看CERT.RSA,如图所示:

答案很明显,CERT.RSA文件中存放了关于签名的信息。

当我们使用如下代码获取证书信息的时候:

/**
	 * 获取指定包名程序的签名信息
	 *
	 * @param context
	 * @param packName
	 * @author SHANHY
	 */
	public static void getSingInfo(Context context, String packName) {
		try {
			PackageInfo packageInfo = context.getPackageManager().getPackageInfo(packName, PackageManager.GET_SIGNATURES);
			Signature[] signs = packageInfo.signatures;
			Signature sign = signs[0];
			parseSignature(sign.toByteArray());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void parseSignature(byte[] signature) {
		try {
			CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
			X509Certificate cert = (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(signature));
			String pubKey = cert.getPublicKey().toString();
			String signNumber = cert.getSerialNumber().toString();
			Log.d(TAG, "pubKey = " + pubKey);// 输出的是16进制的公钥
			Log.d(TAG, "signNumber = " + signNumber);
		} catch (Exception e) {
			e.printStackTrace();
		}
}

查看API可以发现,X509Certificate类并没有提供直接可以获取SHA1值的方法。

研究签名文件的加密算法之后,我们看可以根据publicKey自己计算出SHA1的值,计算方法为:

> 通过 X509Certificate 的 getEncoded() 方法获得公钥的字节数组。

> 使用 MessageDigest 对字节数组进行SHA1的 degest 处理,得到新的字节数组。

> 然后使用16进制对得到新字节数组进行转换,最终得到证书文件的指纹SHA1。

更详细的加密算法,大家可以进一步去研究 java 签名证书规则。

我们还有一个折中的方法就是,在客户端启动的时候,调用服务端接口把文件CERT.RSA通过流的方式传递给服务端,服务端使用上面截图中命令的方式进行处理,得到SHA1值然后返回给客户端,客户端将该值记录在内存变量中使用。

注:我们得到的证书指纹SHA1值,建议放在内存变量中使用即可,无需存储到缓存文件中。

下面附上一些其他有关证书方面的一些代码

JAVA 获取私钥信息的代码:

public static void main(String[] args) {

		try {
			// 用证书的私钥解密 - 该私钥存在生成该证书的密钥库中
			FileInputStream fis2 = new FileInputStream("G:\\test.keystore");
			KeyStore ks = KeyStore.getInstance("JKS"); // 加载证书库
			char[] kspwd = "test".toCharArray(); // 证书库密码
			char[] keypwd = "test".toCharArray(); // 证书密码
			String alias = "test";// 别名
			ks.load(fis2, kspwd); // 加载证书
			PrivateKey pk2 = (PrivateKey) ks.getKey(alias, keypwd); // 获取证书私钥
			fis2.close();
//			Cipher c2 = Cipher.getInstance("RSA/ECB/PKCS1Padding");//jdk默认标准
			Cipher c2 = Cipher.getInstance("RSA/ECB/NoPadding");//android默认标准
			c2.init(Cipher.DECRYPT_MODE, pk2);

			String priKey = pk2.toString();
			System.out.println("priKey = " + priKey);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

JAVA 获取公钥信息的代码:

public static void main(String[] args) throws Exception {
		X509Certificate certificate = readSignatureBlock(new FileInputStream("G:/META-INF/CERT.RSA"));
		RSAPublicKey pk = (RSAPublicKey) certificate.getPublicKey();
		sun.security.rsa.RSAPublicKeyImpl ppk = (sun.security.rsa.RSAPublicKeyImpl) pk;
		System.out.println(ppk.getModulus());// 十进制公钥
		System.out.println(ppk.getModulus().toString(16));// 十六进制公钥
		System.out.println(ppk.getPublicExponent());// 65537
		System.out.println(ppk.getAlgorithm());
		System.out.println(ppk.getFormat());
		System.out.println(ppk.getAlgorithmId());
		System.out.println(ppk.getPublicExponent().toString(16));// 10001
		System.out.println(new BigInteger(ppk.getEncoded()).toString(16));
		// 其中new
		// BigInteger(ppk.getEncoded()).toString(16)的字符串包含字符串ppk.getModulus().toString(16)和字符串ppk.getPublicExponent().toString(16)。

		// 也可以读取整个RSA文件的字节流,从中获取公钥及Modulus,序列号等信息。
		InputStream in2 = new FileInputStream("G:/META-INF/CERT.RSA");
		byte[] bs = new byte[2048];
		int len = in2.read(bs);
		byte[] bs2 = new byte[len];
		System.arraycopy(bs, 0, bs2, 0, len);
		System.out.println(new BigInteger(bs2).toString(16));// 公钥的16进制字符串也在其中。
	}

SHA1加密代码:

public class SHA1 {
  private final int[] abcde = {
      0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0
    };
  // 摘要数据存储数组
  private int[] digestInt = new int[5];
  // 计算过程中的临时数据存储数组
  private int[] tmpData = new int[80];
  // 计算sha-1摘要
  private int process_input_bytes(byte[] bytedata) {
    // 初试化常量
    System.arraycopy(abcde, 0, digestInt, 0, abcde.length);
    // 格式化输入字节数组,补10及长度数据
    byte[] newbyte = byteArrayFormatData(bytedata);
    // 获取数据摘要计算的数据单元个数
    int MCount = newbyte.length / 64;
    // 循环对每个数据单元进行摘要计算
    for (int pos = 0; pos < MCount; pos++) {
      // 将每个单元的数据转换成16个整型数据,并保存到tmpData的前16个数组元素中
      for (int j = 0; j < 16; j++) {
        tmpData[j] = byteArrayToInt(newbyte, (pos * 64) + (j * 4));
      }
      // 摘要计算函数
      encrypt();
    }
    return 20;
  }
  // 格式化输入字节数组格式
  private byte[] byteArrayFormatData(byte[] bytedata) {
    // 补0数量
    int zeros = 0;
    // 补位后总位数
    int size = 0;
    // 原始数据长度
    int n = bytedata.length;
    // 模64后的剩余位数
    int m = n % 64;
    // 计算添加0的个数以及添加10后的总长度
    if (m < 56) {
      zeros = 55 - m;
      size = n - m + 64;
    } else if (m == 56) {
      zeros = 63;
      size = n + 8 + 64;
    } else {
      zeros = 63 - m + 56;
      size = (n + 64) - m + 64;
    }
    // 补位后生成的新数组内容
    byte[] newbyte = new byte[size];
    // 复制数组的前面部分
    System.arraycopy(bytedata, 0, newbyte, 0, n);
    // 获得数组Append数据元素的位置
    int l = n;
    // 补1操作
    newbyte[l++] = (byte) 0x80;
    // 补0操作
    for (int i = 0; i < zeros; i++) {
      newbyte[l++] = (byte) 0x00;
    }
    // 计算数据长度,补数据长度位共8字节,长整型
    long N = (long) n * 8;
    byte h8 = (byte) (N & 0xFF);
    byte h7 = (byte) ((N >> 8) & 0xFF);
    byte h6 = (byte) ((N >> 16) & 0xFF);
    byte h5 = (byte) ((N >> 24) & 0xFF);
    byte h4 = (byte) ((N >> 32) & 0xFF);
    byte h3 = (byte) ((N >> 40) & 0xFF);
    byte h2 = (byte) ((N >> 48) & 0xFF);
    byte h1 = (byte) (N >> 56);
    newbyte[l++] = h1;
    newbyte[l++] = h2;
    newbyte[l++] = h3;
    newbyte[l++] = h4;
    newbyte[l++] = h5;
    newbyte[l++] = h6;
    newbyte[l++] = h7;
    newbyte[l++] = h8;
    return newbyte;
  }
  private int f1(int x, int y, int z) {
    return (x & y) | (~x & z);
  }
  private int f2(int x, int y, int z) {
    return x ^ y ^ z;
  }
  private int f3(int x, int y, int z) {
    return (x & y) | (x & z) | (y & z);
  }
  private int f4(int x, int y) {
    return (x << y) | x >>> (32 - y);
  }
  // 单元摘要计算函数
  private void encrypt() {
    for (int i = 16; i <= 79; i++) {
      tmpData[i] = f4(tmpData[i - 3] ^ tmpData[i - 8] ^ tmpData[i - 14] ^
          tmpData[i - 16], 1);
    }
    int[] tmpabcde = new int[5];
    for (int i1 = 0; i1 < tmpabcde.length; i1++) {
      tmpabcde[i1] = digestInt[i1];
    }
    for (int j = 0; j <= 19; j++) {
      int tmp = f4(tmpabcde[0], 5) +
        f1(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +
        tmpData[j] + 0x5a827999;
      tmpabcde[4] = tmpabcde[3];
      tmpabcde[3] = tmpabcde[2];
      tmpabcde[2] = f4(tmpabcde[1], 30);
      tmpabcde[1] = tmpabcde[0];
      tmpabcde[0] = tmp;
    }
    for (int k = 20; k <= 39; k++) {
      int tmp = f4(tmpabcde[0], 5) +
        f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +
        tmpData[k] + 0x6ed9eba1;
      tmpabcde[4] = tmpabcde[3];
      tmpabcde[3] = tmpabcde[2];
      tmpabcde[2] = f4(tmpabcde[1], 30);
      tmpabcde[1] = tmpabcde[0];
      tmpabcde[0] = tmp;
    }
    for (int l = 40; l <= 59; l++) {
      int tmp = f4(tmpabcde[0], 5) +
        f3(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +
        tmpData[l] + 0x8f1bbcdc;
      tmpabcde[4] = tmpabcde[3];
      tmpabcde[3] = tmpabcde[2];
      tmpabcde[2] = f4(tmpabcde[1], 30);
      tmpabcde[1] = tmpabcde[0];
      tmpabcde[0] = tmp;
    }
    for (int m = 60; m <= 79; m++) {
      int tmp = f4(tmpabcde[0], 5) +
        f2(tmpabcde[1], tmpabcde[2], tmpabcde[3]) + tmpabcde[4] +
        tmpData[m] + 0xca62c1d6;
      tmpabcde[4] = tmpabcde[3];
      tmpabcde[3] = tmpabcde[2];
      tmpabcde[2] = f4(tmpabcde[1], 30);
      tmpabcde[1] = tmpabcde[0];
      tmpabcde[0] = tmp;
    }
    for (int i2 = 0; i2 < tmpabcde.length; i2++) {
      digestInt[i2] = digestInt[i2] + tmpabcde[i2];
    }
    for (int n = 0; n < tmpData.length; n++) {
      tmpData[n] = 0;
    }
  }
  // 4字节数组转换为整数
  private int byteArrayToInt(byte[] bytedata, int i) {
    return ((bytedata[i] & 0xff) << 24) | ((bytedata[i + 1] & 0xff) << 16) |
    ((bytedata[i + 2] & 0xff) << 8) | (bytedata[i + 3] & 0xff);
  }
  // 整数转换为4字节数组
  private void intToByteArray(int intValue, byte[] byteData, int i) {
    byteData[i] = (byte) (intValue >>> 24);
    byteData[i + 1] = (byte) (intValue >>> 16);
    byteData[i + 2] = (byte) (intValue >>> 8);
    byteData[i + 3] = (byte) intValue;
  }
  // 将字节转换为十六进制字符串
  private static String byteToHexString(byte ib) {
    char[] Digit = {
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C',
        'D', 'E', 'F'
      };
    char[] ob = new char[2];
    ob[0] = Digit[(ib >>> 4) & 0X0F];
    ob[1] = Digit[ib & 0X0F];
    String s = new String(ob);
    return s;
  }
  // 将字节数组转换为十六进制字符串
  private static String byteArrayToHexString(byte[] bytearray) {
    String strDigest = "";
    for (int i = 0; i < bytearray.length; i++) {
      strDigest += byteToHexString(bytearray[i]);
    }
    return strDigest;
  }
  // 计算sha-1摘要,返回相应的字节数组
  public byte[] getDigestOfBytes(byte[] byteData) {
    process_input_bytes(byteData);
    byte[] digest = new byte[20];
    for (int i = 0; i < digestInt.length; i++) {
      intToByteArray(digestInt[i], digest, i * 4);
    }
    return digest;
  }
  // 计算sha-1摘要,返回相应的十六进制字符串
  public String getDigestOfString(byte[] byteData) {
    return byteArrayToHexString(getDigestOfBytes(byteData));
  }
  public static void main(String[] args) {
    String data = "1234556";
    System.out.println(data);
    String digest = new SHA1().getDigestOfString(data.getBytes());
    System.out.println(digest);

    // System.out.println( ToMD5.convertSHA1(data).toUpperCase());
  }
} 

以上这篇android 关于利用签名的SHA1进行安全校验的方法之一(推荐)就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Android Studio获取SHA1值实例详解

    Android Studio获取SHA1值实例详解 前言 使用百度地图的小伙伴们都会知道获取百度地图的密钥需要SHA1和包名,在Eclipse中,我们可以很方便的得知SHA1值,如下图: 但是在Android Studio中,该怎么获取SHA1的值呢?不要着急,马上呈上~ Android Studio获取SHA1值 强大的Android Studio为我们提供了Terminal这个工具,我们可以通过他进行相应命令从而获取所需内容. Java中提供了Keytool工具去让我们管理证书,那么接下来我

  • Android 百度地图Sha1获取的方法

    Android 百度地图Sha1获取的方法 场景一 由于最近项目钟要用到定位功能因此肯定需要用到地图以及地位功能,相信大家也知道目前国内比较出名的地图像百度.高德.腾讯等这些还是用到比较多的,于是思考了一下决定还是用百度,相信老司机们都知道的哈! 第一步到百度开发者平台注册一个账号一般是手机号或者短信动态验证码登录我注册了所以这里不再赘述 第二步就是创建一个应用程序这个名字一般是随意取的这个不用太在意如下图所示 3.获取Sha1这里列举两种方式如果其他有更好的办法可以在下面留言哈! 第一种就是通

  • Android中获取sha1证书指纹数据的方法

    前言 在Android开发中,经常要获取sha1证书指纹.例如:在嵌入高德地图的时候,就需要使用这个东东.这个东西在Eclipse中可以直接获取到,但是在Android Studio中,并没有直接提供,需要通过别的手段手动获取. 我们可以使用Java 的一个工具:keytool 来获取这个值.keytool 是jdk提供的一个工具,只要你的java环境变量配置的ok. 直接在命令行输入keytool就可以执行这个命令. 获取sha1的命令为: keytool -list -keystore *.

  • android 关于利用签名的SHA1进行安全校验的方法之一(推荐)

    最近做安卓项目中使用到了百度地图的API,在申请百度地图key的时候,需要我们填入"签名的SHA1"和"客户端包名",然后百度为我们生成一个key. 于是就引发了思考,百度为何需要我们客户端签名的SHA1值呢? 第一想法就是:百度拿我们输入的参数SHA1和包名进行一些列算法计算,生成一个key返回给我们. 为了证明这个想法,写了demo进行测试,android获取包名的方法很简单,但是我们还需要从客户端中获取keystore的指纹SHA1. 进行各种资料的查找和分析

  • Android studio利用gradle打jar包并混淆的方法详解

    本文主要介绍了Android studio利用gradle打jar包并混淆的方法,下面话不多说,来看看详细的介绍吧. 首先打jar包的配置很简单,使用jar的task,可以参考gradle官方文档,具体代码如下: task buildJar(type: Jar, dependsOn: ['assembleRelease']) { destinationDir = file('build/outputs/jar/') appendix = "" baseName = "&quo

  • Android Apk去掉签名以及重新签名的方法

    Android Apk去掉签名以及重新签名的方法 Android开发中很重要的一部就是用自己的密钥给Apk文件签名,不经过签名的Apk文件一般是无法安装的,就算装了最后也是失败. 网上流传的"勾选允许安装未知来源的应用"其实跟签不签名没啥关系,说白了就是允许安装不从电子市场上下载的应用而已. 近几日需要修改一个Apk中JNI调用的.so文件,苦于没有apk源代码,只好研究了一下签名相关的问题.当然有很多第三方工具可以做到,但其实JDK中已经提供了强大的签名工具jarsigner. 1.

  • Android中利用动态加载实现手机淘宝的节日特效

    相信去年圣诞节打开过手机淘宝的童鞋都会对当时的特效记忆犹新吧:全屏飘雪,旁边还有个小雪人来控制八音盒背景音乐的播放,让人有种身临其境的感觉,甚至忍不住想狠狠购物了呢(误),大概就是下面这个样子滴: 嗯,确实很炫,那么我们一步步去分析是如何实现的: 一.实现下雪的 View 首先,最上面一层的全屏雪花极有可能是一个顶层的View,而这个View是通过动态加载去控制显示的(不更新淘宝也能看到这个效果).那么我们先得实现雪花效果的 View,人生苦短,拿来就用.打开 gank.io,搜索"雪花&quo

  • Android:利用SharedPreferences实现自动登录

    本文介绍了Android:利用SharedPreferences实现自动登录,具体如下: 主要代码: public class LoginActivity extends Activity { private EditText username; private EditText userpassword; private CheckBox remember; private CheckBox autologin; private Button login; private SharedPref

  • Android实现获取签名及公钥的方法

    本文实例讲述了Android实现获取签名及公钥的方法.分享给大家供大家参考.具体如下: 1. java代码如下: private byte[] getSign(Context context) { PackageManager pm = context.getPackageManager(); List<PackageInfo> apps = pm .getInstalledPackages(PackageManager.GET_SIGNATURES); Iterator<Package

  • Android仿银行客户签名并且保存签名的截图文件并命名为本地时间

    首先需要一个自定义view用来签字使用,可以修改颜色和画笔的粗细,可以擦拭重新画 package com.android.tcm.view; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.Canvas; import android.graphics.Color; import androi

  • Android获取apk签名指纹的md5值(防止重新被打包)的实现方法

    本文实例讲述了Android获取apk签名指纹的md5值以防止重新被打包的实现方法.分享给大家供大家参考,具体如下: 做个记录(这里只是Java层的签名校验,java层容易被破解,我建议apk加固下) 获取md5值来进行Apk签名校验, 可以防止apk重新被打包. 下面我说说怎么获取apk签名的md5值(有三种方法) 1.用代码获取签名指纹的md5值 /** * MD5加密 * @param byteStr 需要加密的内容 * @return 返回 byteStr的md5值 */ public

  • Android中利用viewflipper动画切换屏幕效果

    整个项目的 package com.example.viewflipper; import android.R.integer; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.GestureDetector.OnDoubleTapListener; import android.view.Menu; import android.view.Me

  • Android中利用NetworkInfo判断网络状态时出现空指针(NullPointerException)问题的解决方法

    在Android中,很多人会用如下的方法判断当前网络是否可用: /** * 获取当前网络状态(是否可用) */ public static boolean isNetworkAvailable() { boolean isAalable = false; ConnectivityManager connManager = (ConnectivityManager) BaseApplication.getApplication().getSystemService(Context.CONNECTI

随机推荐