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

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

给Android的APK程序签名和重新签名的⽅法
签名⼯具的使⽤
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已签名。

在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⽂件)。

相关文档
最新文档