Android编译系统分析六:apk签名的过程分析

Android编译系统分析系列文章已经是第六篇了,随着时间的流逝,随着工作中接触编译系统越来越多,对Android编译系统的理解也在一点点加深,这是令人欣慰的地方。最近遇到了系统apk签名的问题,于是专门分析了下签名部分的逻辑,将分析结果做个记录。
下面是其他五篇分析Android编译系统的文章,感兴趣的可以看下:
android编译系统分析(一)-source build/envsetup.sh与lunch
Android编译系统(二)-mm编译单个模块
android编译系统分析(三)-make
android编译系统(四)-实战:新增一个产品
Android编译系统分析(五)-system.img的生成过程

我们知道,当我们执行mm或者其他命令编译一个模块的时候,会在out\target\product\xxx\obj\APPS\xxxx生成三个apk,它们分别是:package.apk,package.apk.unaligned,package.apk.unsigned。其中package.apk应当是已经签过名的apk,package.apk.unsigned是没有签过名的apk,package.apk.unaligned是指没有使用zipalign工具优化过的apk,zipalign优化过的apk具有更高的效率。然而,按理讲package.apk是签过名的apk,可是,当我拿着这个apk来安装的时候,提示安装失败,重新使用自己的签名文件(系统也是使用该签名文件的,但不是build系统中默认的签名文件)进行签名后又可以安装,这是为什么呢?
package.apk确实是签过名的,可是当我们执行mm命令编译的时候,它是使用默认的系统签名文件签名的,如果一个公司使用它自己的签名文件给系统所有apk签名,就需要修改默认的系统签名文件的路径,改为自己的签名文件的路径,可是怎么改呢?
在我前面的博客: Android编译系统分析二:mm编译单个模块 一文中,我们分析了编译一个模块的具体过程,如果对编译一个模块不太清楚可参考下。当我们编译一个模块的时候,在package_internal.mk文件中,会有签名的相关逻辑:


# Pick a key to sign the package with.  If this package hasn't specified
# an explicit certificate, use the default.
# Secure release builds will have their packages signed after the fact,
# so it's ok for these private keys to be in the clear.
ifeq ($(LOCAL_CERTIFICATE),)
    LOCAL_CERTIFICATE := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
endif

ifeq ($(LOCAL_CERTIFICATE),EXTERNAL)
  # The special value "EXTERNAL" means that we will sign it with the
  # default devkey, apply predexopt, but then expect the final .apk
  # (after dexopting) to be signed by an outside tool.
  LOCAL_CERTIFICATE := $(DEFAULT_SYSTEM_DEV_CERTIFICATE)
  PACKAGES.$(LOCAL_PACKAGE_NAME).EXTERNAL_KEY := 1
endif

# If this is not an absolute certificate, assign it to a generic one.
ifeq ($(dir $(strip $(LOCAL_CERTIFICATE))),./)
    LOCAL_CERTIFICATE := $(dir $(DEFAULT_SYSTEM_DEV_CERTIFICATE))$(LOCAL_CERTIFICATE)
endif
private_key := $(LOCAL_CERTIFICATE).pk8
certificate := $(LOCAL_CERTIFICATE).x509.pem

$(LOCAL_BUILT_MODULE): $(private_key) $(certificate) $(SIGNAPK_JAR)
$(LOCAL_BUILT_MODULE): PRIVATE_PRIVATE_KEY := $(private_key)
$(LOCAL_BUILT_MODULE): PRIVATE_CERTIFICATE := $(certificate)

PACKAGES.$(LOCAL_PACKAGE_NAME).PRIVATE_KEY := $(private_key)
PACKAGES.$(LOCAL_PACKAGE_NAME).CERTIFICATE := $(certificate)

$(LOCAL_BUILT_MODULE): PRIVATE_ADDITIONAL_CERTIFICATES := $(foreach c,\
    $(LOCAL_ADDITIONAL_CERTIFICATES), $(c).x509.pem $(c).pk8)

从中我们可以看到签名文件的位置取决于DEFAULT_SYSTEM_DEV_CERTIFICATE变量的值,这个值的默认的定义是在build/core/config.mk中,在查看这个值之前,我们看下这段代码,发现最终的签名文件是存放在PRIVATE_ADDITIONAL_CERTIFICATES变量中的:

$(LOCAL_BUILT_MODULE): PRIVATE_ADDITIONAL_CERTIFICATES := $(foreach c,\
    $(LOCAL_ADDITIONAL_CERTIFICATES), $(c).x509.pem $(c).pk8)

这点,我们在追寻具体的签名过程的时候会用到。好了,继续回到build/core/config.mk中,看下DEFAULT_SYSTEM_DEV_CERTIFICATE的值:

# The default key if not set as LOCAL_CERTIFICATE
ifdef PRODUCT_DEFAULT_DEV_CERTIFICATE
  DEFAULT_SYSTEM_DEV_CERTIFICATE := $(PRODUCT_DEFAULT_DEV_CERTIFICATE)
else
  DEFAULT_SYSTEM_DEV_CERTIFICATE := build/target/product/security/testkey
endif

他又取决于PRODUCT_DEFAULT_DEV_CERTIFICATE变量的值,如果这个变量的值没有定义的话,DEFAULT_SYSTEM_DEV_CERTIFICATE 就是build/target/product/security/testkey了。因此我们要使用自己的签名文件的话,就需要重新定义PRODUCT_DEFAULT_DEV_CERTIFICATE变量的值,这个值从名字就可以看出来它是与具体产品相关的,很过产品会在device/xxx/xxx下的device.mk中配置该值。
比如,假设我们有这样的一个device.mk文件,我们可以这样配置:

ifdef PRODUCT_RELEASEKEY_PATH
    PRODUCT_DEFAULT_DEV_CERTIFICATE := $(PRODUCT_RELEASEKEY_PATH)
endif

然后把PRODUCT_RELEASEKEY_PATH值的配置写道一个函数中:

function use_release_key()
{
    echo "use relase key!!"
    if [ ! "$TARGET_PRODUCT" ] ;then
        echo Please do \"source env.sh\" and \"lunch\" first!
        return 1
    else
        # find android root path
        local T=$(gettop)
        if [ ! "$T" ]; then
            echo "Couldn't locate the top of the tree.  Try setting TOP."
            return 1
        fi

        #local device_name=`echo $TARGET_PRODUCT | sed -e "s/full_//"`
        export PRODUCT_RELEASEKEY_PATH=$T/device/xxx/xxx/security/releasekey
        echo -e "release key path:\n \e[31m${PRODUCT_RELEASEKEY_PATH}\e[0m"
        echo " using product release key ${PRODUCT_RELEASEKEY_PATH}"
    fi
}

注意,函数中的xxx要改成对应的值哦。
把这个函数放到build/envsetup.sh 文件中,这样,当我们source build/envsetup.sh文件以后,这个函数就可以使用了,然后我们执行use_release_key,这样我们就把系统默认的签名文件改为使用我们自己的签名文件了,然后执行make或者执行mm编译的结束后,对应的apk就会使用我们提供的签名文件签名了。

那么具体Android编译系统中是怎么签名的呢?

在之前我们说签名文件的位置最终是存放在了PRIVATE_ADDITIONAL_CERTIFICATES变量中,我们搜索这个变量发现签名的逻辑是在

# Sign a package using the specified key/cert.
#
define sign-package
$(hide) mv $@ $@.unsigned
$(hide) java -jar $(SIGNAPK_JAR) \
    $(PRIVATE_CERTIFICATE) $(PRIVATE_PRIVATE_KEY) \
    $(PRIVATE_ADDITIONAL_CERTIFICATES) $@.unsigned $@.signed
$(hide) mv $@.signed $@
endef

这断代码告诉了我们系统是怎么签名的,比如我们有一个hello.apk文件,我们要把它签名成hellosign.apk,把这些变量展开就是:
java -jar signapk.jar platform.x509.pem platform.pk8 hello.apk hellosign.apk

那么到底在什么地方使用这段代码来给apk签名呢?通过搜索我们发现,位置就在package_internal.mk文件中:

$(LOCAL_BUILT_MODULE): $(all_res_assets) $(jni_shared_libraries) $(full_android_manifest)
    @echo "target Package: $(PRIVATE_MODULE) ($@)"
ifdef LOCAL_JACK_ENABLED
    $(create-empty-package)
else
    $(if $(PRIVATE_SOURCE_ARCHIVE),\
      $(call initialize-package-file,$(PRIVATE_SOURCE_ARCHIVE),$@),\
      $(create-empty-package))
endif
    $(add-assets-to-package)
ifneq ($(jni_shared_libraries),)
    $(add-jni-shared-libs-to-package)
endif
ifeq ($(full_classes_jar),)
# We don't build jar, need to add the Java resources here.
    $(if $(PRIVATE_EXTRA_JAR_ARGS),$(call add-java-resources-to,$@))
else
    $(add-dex-to-package)
endif
ifdef LOCAL_JACK_ENABLED
    $(add-carried-jack-resources)
endif
ifdef LOCAL_DEX_PREOPT
ifneq (nostripping,$(LOCAL_DEX_PREOPT))
    $(call dexpreopt-remove-classes.dex,$@)
endif
endif
    $(sign-package)
    @# Alignment must happen after all other zip operations.
    $(align-package)

生成完一个apk后就会给他签名。
我们所困惑的zipalign这个时候也出现了,我们看下它的是怎么做的:

define align-package
$(hide) mv $@ $@.unaligned
$(hide) $(ZIPALIGN) \
    -f \
    $(ZIPALIGN_PAGE_ALIGN_FLAGS) \
    4 \
    $@.unaligned $@.aligned
$(hide) mv $@.aligned $@
endef

这里使用了zipalign工具。这个工具的作用我不太清楚,百度一下发现它的作用如下:
在Android SDK中包含了一个工具名为Zipalign,它可以优化你的APK程序包,我们都知道APK的MIME其实就是一个Zip压缩文件,通过Zipalign可以让你的应用程序运行更快,Android123猜测从原理上来讲应该是优化Zip文件的解压速度,毕竟这个工具的文件名为zip对齐。

在Android平台中,数据文件存储在apk文件中,可以多进程的访问,如果你开发过Win32可能知道程序的粒度对齐问题,不错虽然不是PE格式的文件,在Zip中一样,资源的访问可以通过更好的对其优化,而zipalign使用了4字节的边界对齐方式来影射内存,通过空间换时间的方式提高执行效率。
这样我们就知道这三个apk:package.apk,package.apk.unaligned,package.apk.unsigned他们分别生成的过程和时机了。

你可能感兴趣的:(Android编译系统,Android,build,sign,align)