Android数字签名解析(二)

在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() + "
");
            for (Map.Entry<Object, Object> att : entry.getValue().entrySet()) {
                print.print(att.getKey() + ": " + att.getValue() + "
");
            }
            print.print("
");
            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('
');
            cout.write('
');
        }
    }


三、生成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 中保存。



原文地址:https://www.cnblogs.com/bhlsheji/p/5124168.html