標籤:
在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 中儲存。
Android數位簽章解析(二)