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他们分别生成的过程和时机了。