当前位置: 移动技术网 > IT编程>移动开发>Android > 给Android的APK程序签名和重新签名的方法

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

2019年07月24日  | 移动技术网IT编程  | 我要评论

蒙面歌王第三期,怪诞心理学txt,cf大脚官网

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

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网