Android 操作系統映像在兩個地方使用加密簽名:
- 映像中的每個
.apk
文件都必須簽名。 Android 的包管理器以兩種方式使用.apk
簽名:- 替換應用程序時,必須使用與舊應用程序相同的密鑰對其進行簽名,才能訪問舊應用程序的數據。這適用於通過覆蓋
.apk
來更新用戶應用程序,以及使用安裝在/data
下的較新版本覆蓋系統應用程序。 - 如果兩個或多個應用程序想要共享一個用戶 ID(以便它們可以共享數據等),它們必須使用相同的密鑰進行簽名。
- 替換應用程序時,必須使用與舊應用程序相同的密鑰對其進行簽名,才能訪問舊應用程序的數據。這適用於通過覆蓋
- OTA 更新包必須使用系統預期的密鑰之一進行簽名,否則安裝過程將拒絕它們。
釋放鍵
Android 樹在build/target/product/security
下包含測試密鑰。使用make
構建 Android 操作系統映像將使用測試密鑰對所有.apk
文件進行簽名。由於測試密鑰是公開的,任何人都可以使用相同的密鑰簽署他們自己的 .apk 文件,這可能允許他們替換或劫持您的操作系統映像中內置的系統應用程序。因此,使用一組只有您可以訪問的特殊發布密鑰對任何公開發布或部署的 Android 操作系統映像進行簽名至關重要。
要生成您自己獨特的發布密鑰集,請從 Android 樹的根目錄運行以下命令:
subject='/C=US/ST=California/L=Mountain View/O=Android/OU=Android/CN=Android/emailAddress=android@android.com'
mkdir ~/.android-certs
for x in releasekey platform shared media networkstack; do \ ./development/tools/make_key ~/.android-certs/$x "$subject"; \ done
應更改$subject
以反映您組織的信息。您可以使用任何目錄,但要小心選擇一個已備份且安全的位置。一些供應商選擇使用強密碼加密他們的私鑰並將加密的密鑰存儲在源代碼控制中;其他人將他們的釋放密鑰完全存儲在其他地方,例如在氣隙計算機上。
要生成發布映像,請使用:
make dist
sign_target_files_apks \ -o \ # explained in the next section --default_key_mappings ~/.android-certs out/dist/*-target_files-*.zip \ signed-target_files.zip
sign_target_files_apks
腳本將 target-files .zip
作為輸入,並生成一個新的 target-files .zip
,其中所有.apk
文件都已使用新密鑰進行了簽名。新簽名的圖像可以在signed-target_files.zip
中的IMAGES/
下找到。
簽署OTA包
可以使用以下過程將簽名的目標文件 zip 轉換為簽名的 OTA 更新 zip:
ota_from_target_files \
-k (--package_key)
signed-target_files.zip \
signed-ota_update.zip
簽名和側載
旁加載不會繞過恢復的正常包簽名驗證機制——在安裝包之前,恢復將驗證它是否使用與恢復分區中存儲的公鑰匹配的私鑰之一進行簽名,就像通過-空氣。
從主系統接收的更新包通常會驗證兩次:一次由主系統使用 android API 中的RecoverySystem.verifyPackage()
方法,然後再次通過恢復。 RecoverySystem API 根據存儲在主系統中的公鑰檢查簽名,文件/system/etc/security/otacerts.zip
(默認情況下)。恢復檢查與存儲在恢復分區 RAM 磁盤文件/res/keys
中的公鑰的簽名。
默認情況下,構建生成的目標文件.zip
設置 OTA 證書以匹配測試密鑰。在發布的映像上,必須使用不同的證書,以便設備可以驗證更新包的真實性。如上一節所示,將-o
標誌傳遞給sign_target_files_apks
測試密鑰證書替換為 certs 目錄中的發布密鑰證書。
通常系統映像和恢復映像存儲同一組 OTA 公鑰。通過將密鑰添加到僅恢復密鑰集,可以對只能通過旁加載安裝的包進行簽名(假設主系統的更新下載機制正確地對 otacerts.zip 進行驗證)。您可以通過在產品定義中設置 PRODUCT_EXTRA_RECOVERY_KEYS 變量來指定僅包含在恢復中的額外密鑰:
vendor/yoyodyne/tardis/products/tardis.mk
[...] PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload
這包括恢復密鑰文件中的公鑰vendor/yoyodyne/security/tardis/sideload.x509.pem
,以便它可以安裝使用它簽名的包。但是,額外的密鑰不包含在 otacerts.zip 中,因此正確驗證下載包的系統不會為使用此密鑰簽名的包調用恢復。
證書和私鑰
每個密鑰有兩個文件:證書,擴展名為 .x509.pem,私鑰,擴展名為 .pk8。私鑰應該保密,並且是簽署包所必需的。密鑰本身可能受密碼保護。相比之下,證書僅包含密鑰的公開部分,因此可以廣泛分發。它用於驗證包是否已由相應的私鑰簽名。
標準的 Android 構建使用五個密鑰,所有這些密鑰都位於build/target/product/security
中:
- 測試密鑰
- 未指定密鑰的包的通用默認密鑰。
- 平台
- 作為核心平台一部分的包的測試密鑰。
- 共享
- 在家庭/聯繫人進程中共享的東西的測試密鑰。
- 媒體
- 作為媒體/下載系統一部分的包的測試密鑰。
各個包通過在其 Android.mk 文件中設置 LOCAL_CERTIFICATE 來指定這些鍵之一。 (如果未設置此變量,則使用 testkey。)您還可以通過路徑名指定完全不同的鍵,例如:
device/yoyodyne/apps/SpecialApp/Android.mk
[...] LOCAL_CERTIFICATE := device/yoyodyne/security/special
現在構建使用device/yoyodyne/security/special.{x509.pem,pk8}
密鑰來簽署 SpecialApp.apk。該構建只能使用不受密碼保護的私鑰。
高級簽名選項
APK 簽名密鑰替換
簽名腳本sign_target_files_apks
適用於為構建生成的目標文件。構建時使用的證書和私鑰的所有信息都包含在目標文件中。運行簽名腳本進行發布簽名時,可以根據密鑰名稱或 APK 名稱替換簽名密鑰。
使用--key_mapping
和--default_key_mappings
標誌來指定基於鍵名的鍵替換:
-
--key_mapping src_key = dest_key
標誌指定一次替換一個鍵。 -
--default_key_mappings dir
標誌指定一個包含五個鍵的目錄,以替換build/target/product/security
中的所有鍵;這相當於使用--key_mapping
五次來指定映射。
build/target/product/security/testkey = dir/releasekey build/target/product/security/platform = dir/platform build/target/product/security/shared = dir/shared build/target/product/security/media = dir/media build/target/product/security/networkstack = dir/networkstack
使用--extra_apks apk_name1,apk_name2,... = key
標誌來指定基於 APK 名稱的簽名密鑰替換。如果key
為空,腳本會將指定的 APK 視為預簽名。
對於假設的 tardis 產品,您需要六個受密碼保護的密鑰:五個用於替換build/target/product/security
中的五個,一個用於替換上例中 SpecialApp 所需的附加密鑰device/yoyodyne/security/special
。如果密鑰位於以下文件中:
vendor/yoyodyne/security/tardis/releasekey.x509.pem vendor/yoyodyne/security/tardis/releasekey.pk8 vendor/yoyodyne/security/tardis/platform.x509.pem vendor/yoyodyne/security/tardis/platform.pk8 vendor/yoyodyne/security/tardis/shared.x509.pem vendor/yoyodyne/security/tardis/shared.pk8 vendor/yoyodyne/security/tardis/media.x509.pem vendor/yoyodyne/security/tardis/media.pk8 vendor/yoyodyne/security/tardis/networkstack.x509.pem vendor/yoyodyne/security/tardis/networkstack.pk8 vendor/yoyodyne/security/special.x509.pem vendor/yoyodyne/security/special.pk8 # NOT password protected vendor/yoyodyne/security/special-release.x509.pem vendor/yoyodyne/security/special-release.pk8 # password protected
然後你會像這樣簽署所有應用程序:
./build/make/tools/releasetools/sign_target_files_apks \
--default_key_mappings vendor/yoyodyne/security/tardis \
--key_mapping vendor/yoyodyne/security/special=vendor/yoyodyne/security/special-release \
--extra_apks PresignedApp= \
-o tardis-target_files.zip \
signed-tardis-target_files.zip
這帶來了以下內容:
Enter password for vendor/yoyodyne/security/special-release key> Enter password for vendor/yoyodyne/security/tardis/networkstack key> Enter password for vendor/yoyodyne/security/tardis/media key> Enter password for vendor/yoyodyne/security/tardis/platform key> Enter password for vendor/yoyodyne/security/tardis/releasekey key> Enter password for vendor/yoyodyne/security/tardis/shared key> signing: Phone.apk (vendor/yoyodyne/security/tardis/platform) signing: Camera.apk (vendor/yoyodyne/security/tardis/media) signing: NetworkStack.apk (vendor/yoyodyne/security/tardis/networkstack) signing: Special.apk (vendor/yoyodyne/security/special-release) signing: Email.apk (vendor/yoyodyne/security/tardis/releasekey) [...] signing: ContactsProvider.apk (vendor/yoyodyne/security/tardis/shared) signing: Launcher.apk (vendor/yoyodyne/security/tardis/shared) NOT signing: PresignedApp.apk (skipped due to special cert string) rewriting SYSTEM/build.prop: replace: ro.build.description=tardis-user Eclair ERC91 15449 test-keys with: ro.build.description=tardis-user Eclair ERC91 15449 release-keys replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys signing: framework-res.apk (vendor/yoyodyne/security/tardis/platform) rewriting RECOVERY/RAMDISK/default.prop: replace: ro.build.description=tardis-user Eclair ERC91 15449 test-keys with: ro.build.description=tardis-user Eclair ERC91 15449 release-keys replace: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/test-keys with: ro.build.fingerprint=generic/tardis/tardis/tardis:Eclair/ERC91/15449:user/release-keys using: vendor/yoyodyne/security/tardis/releasekey.x509.pem for OTA package verification done.
在提示用戶輸入所有受密碼保護的密鑰的密碼後,腳本會使用發布密鑰對輸入目標.zip
中的所有 APK 文件進行重新簽名。在運行命令之前,還可以將ANDROID_PW_FILE
環境變量設置為臨時文件名;然後該腳本調用您的編輯器以允許您輸入所有密鑰的密碼(這可能是一種更方便的輸入密碼的方法)。
APEX 簽名密鑰替換
Android 10 引入了用於安裝較低級別系統模塊的APEX 文件格式。如APEX 簽名中所述,每個 APEX 文件都使用兩個密鑰進行簽名:一個用於 APEX 內的迷你文件系統映像,另一個用於整個 APEX。
簽署發佈時,APEX 文件的兩個簽名密鑰將替換為發布密鑰。文件系統有效負載密鑰由--extra_apex_payload
標誌指定,整個 APEX 文件簽名密鑰由--extra_apks
標誌指定。
對於 tardis 產品,假設您對com.android.conscrypt.apex
、 com.android.media.apex
和com.android.runtime.release.apex
APEX 文件具有以下密鑰配置。
name="com.android.conscrypt.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED" name="com.android.media.apex" public_key="PRESIGNED" private_key="PRESIGNED" container_certificate="PRESIGNED" container_private_key="PRESIGNED" name="com.android.runtime.release.apex" public_key="vendor/yoyodyne/security/testkeys/com.android.runtime.avbpubkey" private_key="vendor/yoyodyne/security/testkeys/com.android.runtime.pem" container_certificate="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.x509.pem" container_private_key="vendor/yoyodyne/security/testkeys/com.google.android.runtime.release_container.pk8"
您有以下包含發布密鑰的文件:
vendor/yoyodyne/security/runtime_apex_container.x509.pem vendor/yoyodyne/security/runtime_apex_container.pk8 vendor/yoyodyne/security/runtime_apex_payload.pem
以下命令在發布簽名期間覆蓋com.android.runtime.release.apex
和com.android.tzdata.apex
的簽名密鑰。特別是, com.android.runtime.release.apex
使用指定的發布密鑰(對於 APEX 文件的runtime_apex_container
和對於文件圖像有效負載的runtime_apex_payload
)進行簽名。 com.android.tzdata.apex
被視為預簽名。所有其他 APEX 文件均由目標文件中列出的默認配置處理。
./build/make/tools/releasetools/sign_target_files_apks \
--default_key_mappings vendor/yoyodyne/security/tardis \
--extra_apks com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_container \
--extra_apex_payload_key com.android.runtime.release.apex=vendor/yoyodyne/security/runtime_apex_payload.pem \
--extra_apks com.android.media.apex= \
--extra_apex_payload_key com.android.media.apex= \
-o tardis-target_files.zip \
signed-tardis-target_files.zip
運行上述命令會給出以下日誌:
[...] signing: com.android.runtime.release.apex container (vendor/yoyodyne/security/runtime_apex_container) : com.android.runtime.release.apex payload (vendor/yoyodyne/security/runtime_apex_payload.pem) NOT signing: com.android.conscrypt.apex (skipped due to special cert string) NOT signing: com.android.media.apex (skipped due to special cert string) [...]
其他選項
sign_target_files_apks
簽名腳本重寫構建屬性文件中的構建描述和指紋,以反映構建是簽名構建。 --tag_changes
標誌控制對指紋進行的編輯。使用-h
運行腳本以查看有關所有標誌的文檔。
手動生成密鑰
Android 使用具有公共指數 3 的 2048 位 RSA 密鑰。您可以使用來自openssl.org的 openssl 工俱生成證書/私鑰對:
# generate RSA keyopenssl genrsa -3 -out temp.pem 2048
Generating RSA private key, 2048 bit long modulus ....+++ .....................+++ e is 3 (0x3) # create a certificate with the public part of the keyopenssl req -new -x509 -key temp.pem -out releasekey.x509.pem -days 10000 -subj '/C=US/ST=California/L=San Narciso/O=Yoyodyne, Inc./OU=Yoyodyne Mobility/CN=Yoyodyne/emailAddress=yoyodyne@example.com'
# create a PKCS#8-formatted version of the private keyopenssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt
# securely delete the temp.pem fileshred --remove temp.pem
上面給出的 openssl pkcs8 命令會創建一個沒有密碼的 .pk8 文件,適用於構建系統。要創建一個使用密碼保護的 .pk8(您應該為所有實際的發布密鑰執行此操作),請將-nocrypt
參數替換為-passout stdin
;然後 openssl 將使用從標準輸入讀取的密碼加密私鑰。不打印任何提示,因此如果 stdin 是終端,則程序實際上只是在等待您輸入密碼時會出現掛起。 -passout 參數可以使用其他值來從其他位置讀取密碼;有關詳細信息,請參閱openssl 文檔。
temp.pem 中間文件包含沒有任何密碼保護的私鑰,因此在生成發布密鑰時要謹慎處理。特別是,GNUshred 實用程序在網絡或日誌文件系統上可能無效。在生成密鑰時,您可以使用位於 RAM 磁盤(例如 tmpfs 分區)中的工作目錄,以確保不會無意中暴露中間體。
創建圖像文件
獲得簽名目標文件.zip 後,您需要創建映像,以便將其放到設備上。要從目標文件創建簽名映像,請從 Android 樹的根目錄運行以下命令:
img_from_target_files signed-target-files.zip signed-img.zip生成的文件
signed-img.zip
包含所有 .img 文件。要將映像加載到設備上,請按如下方式使用 fastboot:fastboot update signed-img.zip