Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 給Android的APK程序簽名和重新簽名的方法

給Android的APK程序簽名和重新簽名的方法

編輯:關於Android編程

簽名工具的使用
Android源碼編譯出來的signapk.jar既可給apk簽名,也可給rom簽名的。使用格式:

java –jar signapk.jar [-w] publickey.x509[.pem] privatekey.pk8 input.jar output.jar
  • -w 是指對ROM簽名時需使用的參數
  • publickey.x509[.pem] 是公鑰文件
  • privatekey.pk8 是指 私鑰文件
  • input.jar 要簽名的apk或者rom
  • output.jar 簽名後生成的apk或者rom

signapk.java

1) main函數
main函數會生成公鑰對象和私鑰對象,並調用addDigestsToManifest函數生成清單對象Manifest後,再調用signFile簽名。

 public static void main(String[] args) {
 //...
 boolean signWholeFile = false;
 int argstart = 0;
 /*如果對ROM簽名需傳遞-w參數*/
 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餓簽名類似,但是它會添加otacert文件*/
  if (signWholeFile) {
   SignApk.signWholeFile(inputJar, publicKeyFile, publicKey, 
    privateKey, outputFile);
  }
  else {
   JarOutputStream outputJar = new JarOutputStream(outputFile);
   outputJar.setLevel(9);
   /*addDigestsToManifest會生成Manifest對象,然後調用signFile進行簽名*/
   signFile(addDigestsToManifest(inputJar), inputJar, 
   publicKeyFile, publicKey, privateKey, outputJar);
   outputJar.close();
  }
 } catch (Exception e) {
  e.printStackTrace();
  System.exit(1);
 } finally {
  //...
 }
}

2) addDigestsToManifest
首先我們得理解Manifest文件的結構,Manifest文件裡用空行分割成多個段,每個段由多個屬性組成,第一個段的屬性集合稱為主屬性集合,其它段稱為普通屬性集合,普通屬性集合一般會有Name屬性,作為該屬性集合所在段的名字。Android的manifeset文件會為zip的所有文件各自建立一個段,這個段的Name屬性的值就是該文件的path+文件名,另外還有一個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);
   /*計算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);
 // 調用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); 
 // 非常關鍵的一步 生成 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,其實是對MANIFEST.MF的各個段再次計算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的各個段計算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計算摘要並加密得到數字簽名,使用的私鑰是privateKey,然後將數字簽名和公鑰一起存入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,其實也有最簡單的方法,即下載一個重新簽名的工具re-sign.jar,將apk拖進此工具的窗口就生成了重新簽名的apk了。下面我就來講講復雜的重新簽名的方式:采用命令行方法。
一、配置環境,需安裝jdk,sdk
二、在已成功安裝jdk的目錄中找到jarsigner.exe文件,本機的目錄如下:C:\Program Files\Java\jdk1.8.0_20\bin
三、去除准備重新簽名的apk本身的簽名(fantongyo.apk)
將apk以Winrar方式打開,刪除META-INF文件夾即可,並將此Apk文件拷貝至C:\Program Files\Java\jdk1.8.0_20\bin目錄中
Apk壓縮包內容解析:
1.META-INF目錄:存放簽名後的CERT和MANIFEST文件,用於識別軟件的簽名及版本信息
2.rest目錄:存放各種Android原始資源,包括:動畫anim、圖片drawable、布局layout、菜單、xml等等
3.AndroidManifest.xml編碼後的Android項目描述文件,包括了Android項目的名稱、版限、程序組件描述等等
4.Classes.dex編譯後Class被dx程序轉換成Dalvik虛擬機的可執行字節碼文件
5.Resources.arsc所有文本資源的編譯產物,裡面包含了各Location對應的字符串資源
四、重新簽名Apk文件
方法一:通過命令重新生成AndroidApk包簽名證書後再重新簽名Apk文件
1.在cmd中切換到jdk的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參數表示:要生成一個證書(版權、身份識別的安全證書)
-alias參數表示:證書有別名,-alias fantongyo.keystore表示證書別名為:fantongyo
-keyalg RSA表示加密類型,RSA表示需要加密,以防止別人盜取
-validity 20000表示有效時間20000天( K3
-keystore fantongyo.keystore表示要生成的證書名稱為fantongyo
*/

輸入完回車後屏幕顯示:
輸入keystore密碼:[密碼不回顯](一般建議使用20位,最好記下來後面還要用)
再次輸入新密碼:[密碼不回顯]( o' ^$ _( F( K& I0
您的名字與姓氏是什麼?
[Unknown]:fantongyo
您的組織單位名稱是什麼?
[Unknown]:fantong
您的組織名稱是什麼?
[Unknown]:life
您所在的城市或區域名稱是什麼?) 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目錄下,生成了一個簽名用的證書文件 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參數表示:顯示出簽名詳細信息
-keystore表示使用當前目錄中的fantongyo.keystore簽名證書文件。
-signedjar fantongyo_signed.apk表示簽名後生成的APK名稱,% v! a7 e2 v4 W# ]; Gfantongyo.apk表示未簽名 的APK Android軟件,fantongyo.keystore表示別名
*/
輸入完回車後屏幕顯示:
jar已簽名。

2016223143743819.jpg (681×380)

在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中切換到jdk的bin目錄中:cd C:\Program Files\Java\jdk1.8.0_20\bin 回車
4.再輸入以下的命令:復制代碼 代碼如下:jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore debug.keystore -storepass android -keypass android fantongyo.apk androiddebugkey回車
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文件)

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved