Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit def64f26 authored by Victor Hsieh's avatar Victor Hsieh
Browse files

Optimizations to reduce verity verification time

Test: with CPU locked to low freq, verification time of a 400 MB apk is
      reduced from about 2528 ms to 1942 ms, vs 915 ms of the old
      algorithm.  Writing directly into ByteBuffer's backing array saves
      around 100 ms but it does not work for DirectByteBuffer, thus I
      didn't implement this optimization.
Bug: 30972906
Change-Id: I00cf782e18a8351569eaf4593188c1ce6796a634
parent a8d534bf
Loading
Loading
Loading
Loading
+16 −13
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.DigestException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -66,7 +67,7 @@ abstract class ApkVerityBuilder {
     */
    static ApkVerityResult generateApkVerity(RandomAccessFile apk,
            SignatureInfo signatureInfo, ByteBufferFactory bufferFactory)
            throws IOException, SecurityException, NoSuchAlgorithmException {
            throws IOException, SecurityException, NoSuchAlgorithmException, DigestException {
        assertSigningBlockAlignedAndHasFullPages(signatureInfo);

        long signingBlockSize =
@@ -112,6 +113,7 @@ abstract class ApkVerityBuilder {
        private final ByteBuffer mOutput;

        private final MessageDigest mMd;
        private final byte[] mDigestBuffer = new byte[DIGEST_SIZE_BYTES];
        private final byte[] mSalt;

        private BufferedDigester(byte[] salt, ByteBuffer output) throws NoSuchAlgorithmException {
@@ -129,21 +131,22 @@ abstract class ApkVerityBuilder {
         * consumption will continuous from there.
         */
        @Override
        public void consume(ByteBuffer buffer) {
        public void consume(ByteBuffer buffer) throws DigestException {
            int offset = buffer.position();
            int remaining = buffer.remaining();
            while (remaining > 0) {
                int allowance = (int) Math.min(remaining, BUFFER_SIZE - mBytesDigestedSinceReset);
                mMd.update(slice(buffer, offset, offset + allowance));
                // Optimization: set the buffer limit to avoid allocating a new ByteBuffer object.
                buffer.limit(buffer.position() + allowance);
                mMd.update(buffer);
                offset += allowance;
                remaining -= allowance;
                mBytesDigestedSinceReset += allowance;

                if (mBytesDigestedSinceReset == BUFFER_SIZE) {
                    byte[] digest = mMd.digest();
                    mOutput.put(digest);

                    mMd.reset();
                    mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
                    mOutput.put(mDigestBuffer);
                    // After digest, MessageDigest resets automatically, so no need to reset again.
                    mMd.update(mSalt);
                    mBytesDigestedSinceReset = 0;
                }
@@ -152,12 +155,12 @@ abstract class ApkVerityBuilder {

        /** Finish the current digestion if any. */
        @Override
        public void finish() {
        public void finish() throws DigestException {
            if (mBytesDigestedSinceReset == 0) {
                return;
            }
            byte[] digest = mMd.digest();
            mOutput.put(digest);
            mMd.digest(mDigestBuffer, 0, mDigestBuffer.length);
            mOutput.put(mDigestBuffer);
        }

        private void fillUpLastOutputChunk() {
@@ -174,7 +177,7 @@ abstract class ApkVerityBuilder {
     * digest the remaining.
     */
    private static void consumeByChunk(DataDigester digester, DataSource source, int chunkSize)
            throws IOException {
            throws IOException, DigestException {
        long inputRemaining = source.size();
        long inputOffset = 0;
        while (inputRemaining > 0) {
@@ -191,7 +194,7 @@ abstract class ApkVerityBuilder {

    private static void generateApkVerityDigestAtLeafLevel(RandomAccessFile apk,
            SignatureInfo signatureInfo, byte[] salt, ByteBuffer output)
            throws IOException, NoSuchAlgorithmException {
            throws IOException, NoSuchAlgorithmException, DigestException {
        BufferedDigester digester = new BufferedDigester(salt, output);

        // 1. Digest from the beginning of the file, until APK Signing Block is reached.
@@ -230,7 +233,7 @@ abstract class ApkVerityBuilder {

    private static byte[] generateApkVerityTree(RandomAccessFile apk, SignatureInfo signatureInfo,
            byte[] salt, int[] levelOffset, ByteBuffer output)
            throws IOException, NoSuchAlgorithmException {
            throws IOException, NoSuchAlgorithmException, DigestException {
        // 1. Digest the apk to generate the leaf level hashes.
        generateApkVerityDigestAtLeafLevel(apk, signatureInfo, salt, slice(output,
                    levelOffset[levelOffset.length - 2], levelOffset[levelOffset.length - 1]));
+2 −1
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package android.util.apk;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.security.DigestException;

/**
 * {@link DataSource} which provides data from a {@link ByteBuffer}.
@@ -42,7 +43,7 @@ class ByteBufferDataSource implements DataSource {

    @Override
    public void feedIntoDataDigester(DataDigester md, long offset, int size)
            throws IOException {
            throws IOException, DigestException {
        // There's no way to tell MessageDigest to read data from ByteBuffer from a position
        // other than the buffer's current position. We thus need to change the buffer's
        // position to match the requested offset.
+3 −2
Original line number Diff line number Diff line
@@ -17,11 +17,12 @@
package android.util.apk;

import java.nio.ByteBuffer;
import java.security.DigestException;

interface DataDigester {
    /** Consumes the {@link ByteBuffer}. */
    void consume(ByteBuffer buffer);
    void consume(ByteBuffer buffer) throws DigestException;

    /** Finishes the digestion. Must be called after the last {@link #consume(ByteBuffer)}. */
    void finish();
    void finish() throws DigestException;
}
+3 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.util.apk;

import java.io.IOException;
import java.security.DigestException;

/** Source of data to be digested. */
interface DataSource {
@@ -32,5 +33,6 @@ interface DataSource {
     * @param offset offset of the region inside this data source.
     * @param size size (in bytes) of the region.
     */
    void feedIntoDataDigester(DataDigester md, long offset, int size) throws IOException;
    void feedIntoDataDigester(DataDigester md, long offset, int size)
            throws IOException, DigestException;
}
+2 −1
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import java.io.FileDescriptor;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.DirectByteBuffer;
import java.security.DigestException;

/**
 * {@link DataSource} which provides data from a file descriptor by memory-mapping the sections
@@ -55,7 +56,7 @@ class MemoryMappedFileDataSource implements DataSource {

    @Override
    public void feedIntoDataDigester(DataDigester md, long offset, int size)
            throws IOException {
            throws IOException, DigestException {
        // IMPLEMENTATION NOTE: After a lot of experimentation, the implementation of this
        // method was settled on a straightforward mmap with prefaulting.
        //