給Android的APK程序簽名和重新簽名的方法
簽名工具的使用
Android源碼編譯出來的signapk.jar既可給apk簽名,也可給rom簽名的。使用格式:
java –jar signapk.jar [-w] publickey.x509[.pem] privatekey.pk8 input.jar output.jar
- -w 是指對ROM簽名時(shí)需使用的參數(shù)
- publickey.x509[.pem] 是公鑰文件
- privatekey.pk8 是指 私鑰文件
- input.jar 要簽名的apk或者rom
- output.jar 簽名后生成的apk或者rom
signapk.java
1) main函數(shù)
main函數(shù)會(huì)生成公鑰對象和私鑰對象,并調(diào)用addDigestsToManifest函數(shù)生成清單對象Manifest后,再調(diào)用signFile簽名。
public static void main(String[] args) { //... boolean signWholeFile = false; int argstart = 0; /*如果對ROM簽名需傳遞-w參數(shù)*/ if (args[0].equals("-w")) { signWholeFile = true; argstart = 1; } // ... try { File publicKeyFile = new File(args[argstart+0]); X509Certificate publicKey = readPublicKey(publicKeyFile); PrivateKey privateKey = readPrivateKey(new File(args[argstart+1])); inputJar = new JarFile(new File(args[argstart+2]), false); outputFile = new FileOutputStream(args[argstart+3]); /*對ROM簽名,讀者可自行分析,和Apk餓簽名類似,但是它會(huì)添加otacert文件*/ if (signWholeFile) { SignApk.signWholeFile(inputJar, publicKeyFile, publicKey, privateKey, outputFile); } else { JarOutputStream outputJar = new JarOutputStream(outputFile); outputJar.setLevel(9); /*addDigestsToManifest會(huì)生成Manifest對象,然后調(diào)用signFile進(jìn)行簽名*/ signFile(addDigestsToManifest(inputJar), inputJar, publicKeyFile, publicKey, privateKey, outputJar); outputJar.close(); } } catch (Exception e) { e.printStackTrace(); System.exit(1); } finally { //... } }
2) addDigestsToManifest
首先我們得理解Manifest文件的結(jié)構(gòu),Manifest文件里用空行分割成多個(gè)段,每個(gè)段由多個(gè)屬性組成,第一個(gè)段的屬性集合稱為主屬性集合,其它段稱為普通屬性集合,普通屬性集合一般會(huì)有Name屬性,作為該屬性集合所在段的名字。Android的manifeset文件會(huì)為zip的所有文件各自建立一個(gè)段,這個(gè)段的Name屬性的值就是該文件的path+文件名,另外還有一個(gè)SHA1-Digest的屬性,該屬性的值是對文件的sha1摘要用base64編碼得到的字符串。
Manifest示例:
Manifest-Version: 1.0 Created-By: 1.6.0-rc (Sun Microsystems Inc.) Name: res/drawable-hdpi/user_logout.png SHA1-Digest: zkQSZbt3Tqc9myEVuxc1dzMDPCs= Name: res/drawable-hdpi/contacts_cancel_btn_pressed.png SHA1-Digest: mSVZvKpvKpmgUJ9oXDJaTWzhdic= Name: res/drawable/main_head_backgroud.png SHA1-Digest: fe1yzADfDGZvr0cyIdNpGf/ySio=
Manifest-Version屬性和Created-By所在的段就是主屬性集合,其它屬性集合就是普通屬性集合,這些普通屬性集合都有Name屬性,作為該段的名字。
addDigestsToManifest源代碼:
private static Manifest addDigestsToManifest(JarFile jar) throws IOException, GeneralSecurityException { Manifest input = jar.getManifest(); Manifest output = new Manifest(); Attributes main = output.getMainAttributes(); if (input != null) { main.putAll(input.getMainAttributes()); } else { main.putValue("Manifest-Version", "1.0"); main.putValue("Created-By", "1.0 (Android SignApk)"); } MessageDigest md = MessageDigest.getInstance("SHA1"); byte[] buffer = new byte[4096]; int num; // We sort the input entries by name, and add them to the // output manifest in sorted order. We expect that the output // map will be deterministic. TreeMap<String, JarEntry> byName = new TreeMap<String, JarEntry>(); for (Enumeration<JarEntry> e = jar.entries(); e.hasMoreElements(); ) { JarEntry entry = e.nextElement(); byName.put(entry.getName(), entry); } for (JarEntry entry: byName.values()) { String name = entry.getName(); if (!entry.isDirectory() && !name.equals(JarFile.MANIFEST_NAME) && !name.equals(CERT_SF_NAME) && !name.equals(CERT_RSA_NAME) && !name.equals(OTACERT_NAME) && (stripPattern == null || !stripPattern.matcher(name).matches())) { InputStream data = jar.getInputStream(entry); /*計(jì)算sha1*/ while ((num = data.read(buffer)) > 0) { md.update(buffer, 0, num); } Attributes attr = null; if (input != null) attr = input.getAttributes(name); attr = attr != null ? new Attributes(attr) : new Attributes(); /*base64編碼sha1值得到SHA1-Digest屬性的值*/ attr.putValue("SHA1-Digest", new String(Base64.encode(md.digest()), "ASCII")); output.getEntries().put(name, attr); } } return output; }
3) signFile
先將inputjar的所有文件拷貝至outputjar,然后生成Manifest.MF,CERT.SF和CERT.RSA
public static void signFile(Manifest manifest, JarFile inputJar, File publicKeyFile, X509Certificate publicKey, PrivateKey privateKey, JarOutputStream outputJar) throws Exception { // Assume the certificate is valid for at least an hour. long timestamp = publicKey.getNotBefore().getTime() + 3600L * 1000; JarEntry je; // 拷貝文件 copyFiles(manifest, inputJar, outputJar, timestamp); // 生成MANIFEST.MF je = new JarEntry(JarFile.MANIFEST_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); manifest.write(outputJar); // 調(diào)用writeSignatureFile 生成CERT.SF je = new JarEntry(CERT_SF_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); ByteArrayOutputStream baos = new ByteArrayOutputStream(); writeSignatureFile(manifest, baos); byte[] signedData = baos.toByteArray(); outputJar.write(signedData); // 非常關(guān)鍵的一步 生成 CERT.RSA je = new JarEntry(CERT_RSA_NAME); je.setTime(timestamp); outputJar.putNextEntry(je); writeSignatureBlock(new CMSProcessableByteArray(signedData), publicKey, privateKey, outputJar); }
4) writeSignatureFile
生成CERT.SF,其實(shí)是對MANIFEST.MF的各個(gè)段再次計(jì)算Sha1摘要得到CERT.SF。
private static void writeSignatureFile(Manifest manifest, OutputStream out) throws IOException, GeneralSecurityException { Manifest sf = new Manifest(); Attributes main = sf.getMainAttributes(); //添加屬性 main.putValue("Signature-Version", "1.0"); main.putValue("Created-By", "1.0 (Android SignApk)"); MessageDigest md = MessageDigest.getInstance("SHA1"); PrintStream print = new PrintStream( new DigestOutputStream(new ByteArrayOutputStream(), md), true, "UTF-8"); // 添加Manifest.mf的sha1摘要 manifest.write(print); print.flush(); main.putValue("SHA1-Digest-Manifest", new String(Base64.encode(md.digest()), "ASCII")); //對MANIFEST.MF的各個(gè)段計(jì)算sha1摘要 Map<String, Attributes> entries = manifest.getEntries(); for (Map.Entry<String, Attributes> entry : entries.entrySet()) { // Digest of the manifest stanza for this entry. print.print("Name: " + entry.getKey() + "\r\n"); for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) { print.print(att.getKey() + ": " + att.getValue() + "\r\n"); } print.print("\r\n"); print.flush(); Attributes sfAttr = new Attributes(); sfAttr.putValue("SHA1-Digest", new String(Base64.encode(md.digest()), "ASCII")); sf.getEntries().put(entry.getKey(), sfAttr); } CountOutputStream cout = new CountOutputStream(out); sf.write(cout); // A bug in the java.util.jar implementation of Android platforms // up to version 1.6 will cause a spurious IOException to be thrown // if the length of the signature file is a multiple of 1024 bytes. // As a workaround, add an extra CRLF in this case. if ((cout.size() % 1024) == 0) { cout.write('\r'); cout.write('\n'); } }
5) writeSignatureBlock
采用SHA1withRSA算法對CERT.SF計(jì)算摘要并加密得到數(shù)字簽名,使用的私鑰是privateKey,然后將數(shù)字簽名和公鑰一起存入CERT.RSA。這里使用了開源庫bouncycastle來簽名。
private static void writeSignatureBlock( CMSTypedData data, X509Certificate publicKey, PrivateKey privateKey, OutputStream out) throws IOException, CertificateEncodingException, OperatorCreationException, CMSException { ArrayList<X509Certificate> certList = new ArrayList<X509Certificate>(1); certList.add(publicKey); JcaCertStore certs = new JcaCertStore(certList); CMSSignedDataGenerator gen = new CMSSignedDataGenerator(); //簽名算法是SHA1withRSA ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA1withRSA") .setProvider(sBouncyCastleProvider) .build(privateKey); gen.addSignerInfoGenerator( new JcaSignerInfoGeneratorBuilder( new JcaDigestCalculatorProviderBuilder() .setProvider(sBouncyCastleProvider) .build()) .setDirectSignature(true) .build(sha1Signer, publicKey)); gen.addCertificates(certs); CMSSignedData sigData = gen.generate(data, false); ASN1InputStream asn1 = new ASN1InputStream(sigData.getEncoded()); DEROutputStream dos = new DEROutputStream(out); dos.writeObject(asn1.readObject()); }
采用命令行重新簽名APK
重新簽名apk,其實(shí)也有最簡單的方法,即下載一個(gè)重新簽名的工具re-sign.jar,將apk拖進(jìn)此工具的窗口就生成了重新簽名的apk了。下面我就來講講復(fù)雜的重新簽名的方式:采用命令行方法。
一、配置環(huán)境,需安裝jdk,sdk
二、在已成功安裝jdk的目錄中找到j(luò)arsigner.exe文件,本機(jī)的目錄如下:C:\Program Files\Java\jdk1.8.0_20\bin
三、去除準(zhǔn)備重新簽名的apk本身的簽名(fantongyo.apk)
將apk以Winrar方式打開,刪除META-INF文件夾即可,并將此Apk文件拷貝至C:\Program Files\Java\jdk1.8.0_20\bin目錄中
Apk壓縮包內(nèi)容解析:
1.META-INF目錄:存放簽名后的CERT和MANIFEST文件,用于識(shí)別軟件的簽名及版本信息
2.rest目錄:存放各種Android原始資源,包括:動(dòng)畫anim、圖片drawable、布局layout、菜單、xml等等
3.AndroidManifest.xml編碼后的Android項(xiàng)目描述文件,包括了Android項(xiàng)目的名稱、版限、程序組件描述等等
4.Classes.dex編譯后Class被dx程序轉(zhuǎn)換成Dalvik虛擬機(jī)的可執(zhí)行字節(jié)碼文件
5.Resources.arsc所有文本資源的編譯產(chǎn)物,里面包含了各Location對應(yīng)的字符串資源
四、重新簽名Apk文件
方法一:通過命令重新生成AndroidApk包簽名證書后再重新簽名Apk文件
1.在cmd中切換到j(luò)dk的bin目錄中:cd C:\Program Files\Java\jdk1.8.0_20\bin 回車
2.再輸入以下的命令:
Keytool -genkey -alias fantongyo.keystore -keyalg RSA -validity 20000 -keystore fantongyo.keystore /*解釋:keytool工具是Java JDK自帶的證書工具 -genkey參數(shù)表示:要生成一個(gè)證書(版權(quán)、身份識(shí)別的安全證書) -alias參數(shù)表示:證書有別名,-alias fantongyo.keystore表示證書別名為:fantongyo -keyalg RSA表示加密類型,RSA表示需要加密,以防止別人盜取 -validity 20000表示有效時(shí)間20000天( K3 -keystore fantongyo.keystore表示要生成的證書名稱為fantongyo */
輸入完回車后屏幕顯示:
輸入keystore密碼:[密碼不回顯](一般建議使用20位,最好記下來后面還要用)
再次輸入新密碼:[密碼不回顯]( o' ^$ _( F( K& I0
您的名字與姓氏是什么?
[Unknown]:fantongyo
您的組織單位名稱是什么?
[Unknown]:fantong
您的組織名稱是什么?
[Unknown]:life
您所在的城市或區(qū)域名稱是什么?) L# V' |. E0 f; {
[Unknown]:shenzhen
您所在的州或省份名稱是什么?
[Unknown]:guangdong
該單位的兩字母國家代碼是什么
[Unknown]:CN
CN=fantongyo, U=fantong, O=fantong team, L=shenzhen, ST=guangdong, C=CN正確嗎?
[否]:Y
輸入< mine.keystore>的主密碼
(如果和keystore密碼相同,按回車):
查看C:\Program Files\Java\jdk1.8.0_20\bin目錄下,生成了一個(gè)簽名用的證書文件 fantongyo.keystore
3.重新簽名Apk文件
在cmd中輸入:jarsigner –verbose –keystore fantongyo.keystore –signedjar fantongyo_signed.apk fantongyo.apk fantongyo.keystore
/*解釋:* ^, {& k1 Z. M* P/ M+ K5 n5 hjarsigner是Java的簽名工具# K8 ~% s# Y. @6 P
-verbose參數(shù)表示:顯示出簽名詳細(xì)信息
-keystore表示使用當(dāng)前目錄中的fantongyo.keystore簽名證書文件。
-signedjar fantongyo_signed.apk表示簽名后生成的APK名稱,% v! a7 e2 v4 W# ]; Gfantongyo.apk表示未簽名 的APK Android軟件,fantongyo.keystore表示別名
*/
輸入完回車后屏幕顯示:
jar已簽名。
在C:\Program Files\Java\jdk1.8.0_20\bin目錄下已重新生成fantongyo_signed.apk文件
方法二、以android自帶的debug.keystore重新簽名Apk文件
1.打開eclipse,菜單欄Window—>Preferences—>Android—>Build—>Default debug keystore目錄(我的編輯器顯示:C:\Users\Administrator\.android\debug.keystore)
2.將debug.keystore文件拷貝至C:\Program Files\Java\jdk1.8.0_20\bin目錄下
3.在cmd中切換到j(luò)dk的bin目錄中:cd C:\Program Files\Java\jdk1.8.0_20\bin 回車
4.再輸入以下的命令:
5.在sdk中找到zipalign文件,我電腦的目錄為:E:\SoftWare\adt-bundle-windows-x86-20140702\sdk\build-tools\android-4.4W
在cmd中切換到sdk的存放zipalign.exe文件的目錄中:
cd E:\SoftWare\adt-bundle-windows-x86-20140702\sdk\build-tools\android-4.4W
6.再輸入:zipalign 4 fantongyo.apk fantongyo_signed.apk即可(fantongyo_signed.apk是 重新簽名后的apk文件)
- Android簽名機(jī)制介紹:生成keystore、簽名、查看簽名信息等方法
- android應(yīng)用簽名詳細(xì)步驟
- Android獲取apk程序簽名信息代碼示例
- Android APK使用Debug簽名重新打包 Eclipse更改默認(rèn)Debug簽名
- Android Apk去掉簽名以及重新簽名的方法
- iOS 基于AFNetworking下自簽名證書配置的方法
- iOS中的ipa重簽名(逆向必備)
- iOS之Https自簽名證書認(rèn)證及數(shù)據(jù)請求的封裝原理
- IOS 簽名錯(cuò)誤codesign failed with exit code 1解決方法
- Android和iOS包批量重簽名
相關(guān)文章
Android簡單實(shí)現(xiàn)計(jì)算器功能
這篇文章主要為大家詳細(xì)介紹了Android簡單實(shí)現(xiàn)計(jì)算器功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08android GridView多選效果的實(shí)例代碼
在使用 GridView的時(shí)候,有時(shí)需要多選上面顯示的類容,比如批量刪除上面顯示的圖片,批量上傳圖片等。這個(gè)時(shí)候我們可以使用層疊圖來實(shí)現(xiàn),效果如下:2013-06-06Android 實(shí)現(xiàn)微信,微博,微信朋友圈,QQ分享的功能
這篇文章主要介紹了Android 實(shí)現(xiàn)微信,微博,微信朋友圈,QQ分享的功能的相關(guān)資料,需要的朋友可以參考下2016-12-12Android中的Intent Filter匹配規(guī)則簡介
這篇文章主要為大家詳細(xì)介紹了Android中的Intent Filter匹配規(guī)則,感興趣的小伙伴們可以參考一下2016-04-04Material Design系列之Behavior上滑顯示返回頂部按鈕
這篇文章主要為大家詳細(xì)介紹了Material Design系列之Behavior上滑顯示返回頂部按鈕的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09