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他們分別生成的過程和時機了。


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
粤ICP备14056181号  © 2014-2021 ITdaan.com