给Android的APK程序签名和重新签名的方法
给Android的APK程序签名和重新签名的方法
发布时间:2016-12-28 来源:查字典编辑
摘要:签名工具的使用Android源码编译出来的signapk.jar既可给apk签名,也可给rom签名的。使用格式:java–jarsignap...

签名工具的使用

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() + "rn"); for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) { print.print(att.getKey() + ": " + att.getValue() + "rn"); } print.print("rn"); 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 FilesJavajdk1.8.0_20bin

三、去除准备重新签名的apk本身的签名(fantongyo.apk)

将apk以Winrar方式打开,删除META-INF文件夹即可,并将此Apk文件拷贝至C:Program FilesJavajdk1.8.0_20bin目录中

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 FilesJavajdk1.8.0_20bin 回车

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 FilesJavajdk1.8.0_20bin目录下,生成了一个签名用的证书文件 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已签名。

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

在C:Program FilesJavajdk1.8.0_20bin目录下已重新生成fantongyo_signed.apk文件

方法二、以android自带的debug.keystore重新签名Apk文件

1.打开eclipse,菜单栏Window—>Preferences—>Android—>Build—>Default debug keystore目录(我的编辑器显示:C:UsersAdministrator.androiddebug.keystore)

2.将debug.keystore文件拷贝至C:Program FilesJavajdk1.8.0_20bin目录下

3.在cmd中切换到jdk的bin目录中:cd C:Program FilesJavajdk1.8.0_20bin 回车

4.再输入以下的命令:复制代码 代码如下:jarsigner -digestalg SHA1 -sigalg MD5withRSA -keystore debug.keystore -storepass android -keypass android fantongyo.apk androiddebugkey回车

5.在sdk中找到zipalign文件,我电脑的目录为:E:SoftWareadt-bundle-windows-x86-20140702sdkbuild-toolsandroid-4.4W

在cmd中切换到sdk的存放zipalign.exe文件的目录中:

cd E:SoftWareadt-bundle-windows-x86-20140702sdkbuild-toolsandroid-4.4W

6.再输入:zipalign 4 fantongyo.apk fantongyo_signed.apk即可(fantongyo_signed.apk是 重新签名后的apk文件)

推荐文章
猜你喜欢
附近的人在看
推荐阅读
拓展阅读
相关阅读
网友关注
最新安卓软件开发学习
热门安卓软件开发学习
编程开发子分类