Android數字簽名解析(二)

在Android字簽名解析(一)中,介紹了android進行簽名的兩種方式,當中用密鑰對進行簽名用到了signapk.jar這個java庫。

如下咱們就看看signapk簽名實現過程, signapk的源代碼在build/tools/signapk/下。


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

2、 生成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');
        }
    }


3、生成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 中保存。
相關文章
相關標籤/搜索