搜索:
首页 >> 软件编程 >> android编程 >> 给Android的APK程序签名和重新签名的方法

给Android的APK程序签名和重新签名的方法

Asp之家 2019-3-20 作者:饭饭 投递文章

签名工具的使用
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),