您的位置:首页 > 移动开发 > Android开发

Android数字签名解析(二)

2016-01-12 14:00 323 查看

在Android数字签名解析(一)中,介绍了android进行签名的两种方式,当中用密钥对进行签名用到了signapk.jar这个java库。

以下我们就看看signapk签名实现过程,signapk的源代码在build/tools/signapk/下。



一、生成MANIFEST.MF文件

//对apk包中的每一个文件(非目录和非签名文件),生成SHA1的摘要信息。再对这个信息进行Base64编码。
Manifest manifest = addDigestsToManifest(inputJar);


//将上面得到的信息。写入MANIFEST.MF
je = new JarEntry(JarFile.MANIFEST_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
manifest.write(outputJar);


二、 生成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);


对 整个 MANIFEST.MF 进行 SHA1 计算。并将摘要信息存入 CERT.SF 中 。然后对之前计算的全部摘要信息使用SHA1再次计

算,将结果也写入 CERT.SF 中, 关键代码在 writeSignatureFile(manifest,
baos)中,

/** Write a .SF file with a digest of the specified manifest. */
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");

// Digest of the entire manifest
manifest.write(print);
print.flush();
main.putValue("SHA1-Digest-Manifest",
new String(Base64.encode(md.digest()), "ASCII"));

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');
}
}


三、生成CERT.RSA文件

<span style="white-space:pre">	</span>je = new JarEntry(CERT_RSA_NAME);
je.setTime(timestamp);
outputJar.putNextEntry(je);
writeSignatureBlock(new CMSProcessableByteArray(signedData),
publicKey, privateKey, outputJar);


关键代码在writeSignatureBlock(new CMSProcessableByteArray(signedData)中

/** Sign data and write the digital signature to 'out'. */
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();
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());
}


把之前生成的CERT.SF文件,用私有密钥计算出签名,
然后将签名以及公钥信息写入 CERT.RSA 中保存。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: