編輯:關於Android編程
最近做安卓項目中使用到了百度地圖的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進行安全校驗的方法之一(推薦)就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持本站。
在Android應用中,圖片裁剪也是一個經常用到的功能。Android系統中可以用隱式意圖調用系統應用進行裁剪,但是這樣做在不同的手機可能表現出不同的效果,甚至在某些奇葩
先看效果: 現在很多的應用效果都需要做的炫些,像UC,以及天天靜聽,效果很炫的,源碼已經對外開放了,有興趣的可以去研究下的 上源碼main.xml&l
廢話不多說先貼效果圖,跟代碼,後面再詳解:效果圖如下效果圖如下由於項目公司數據保密性加了馬賽克。貼代碼代碼如下:package com.tony.linechart;im
Android 目前支持下面幾個版本的OpenGL ES API : OpenGL ES 1.0 和 1.1 :Android 1.0和更高的版本支持這個API規范。 O