簽署要發布的版本

Android 作業系統映像檔會在兩個位置使用加密簽章:

  1. 圖片中的每個 .apk 檔案都必須經過簽署。Android 的套件管理工具會以兩種方式使用 .apk 簽章:
    • 取代應用程式時,必須使用與舊應用程式相同的金鑰簽署,才能存取舊應用程式的資料。無論是透過覆寫 .apk 更新使用者應用程式,還是透過覆寫 /data 下安裝的新版系統應用程式,都是如此。
    • 如果兩個以上的應用程式要共用使用者 ID (以便共用資料等),就必須使用相同的金鑰簽署。
  2. OTA 更新套件必須使用系統預期的其中一個金鑰簽署,否則安裝程序會拒絕更新。

發布金鑰

Android 樹狀結構包含 build/target/product/security 下的 test-keys。使用 make 建構 Android OS 映像檔時,系統會使用 test-keys 簽署所有 .apk 檔案。由於測試金鑰是公開資訊,任何人都能使用相同金鑰簽署自己的 .apk 檔案,因此可能會取代或劫持作業系統映像檔內建的系統應用程式。因此,請務必使用只有您能存取的特殊「發布金鑰」,簽署任何公開發布或部署的 Android OS 映像檔。

如要產生專屬的發布金鑰組合,請在 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 指令碼會將目標檔案 .zip 做為輸入內容,並產生新的目標檔案 .zip,其中所有 .apk 檔案都已使用新金鑰簽署。新簽署的映像檔會顯示在 signed-target_files.zipIMAGES/ 下方。

簽署 OTA 套件

簽署的 target-files 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 中:

testkey
未指定金鑰的套件會使用這個預設金鑰。
平台
核心平台套件的測試金鑰。
共用
測試在住家/聯絡人程序中分享的項目。
媒體
媒體/下載系統中套件的測試金鑰。
networkstack
屬於網路系統的套件測試金鑰。networkstack 金鑰用於簽署設計為模組化系統元件 的二進位檔。如果模組更新是分開建構,並以預先建構的形式整合至裝置映像檔,您可能不需要在 Android 來源樹狀結構中產生 networkstack 金鑰。

個別套件會在 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.apexcom.android.media.apexcom.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.apexcom.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 key
openssl 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 key
openssl 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 key
openssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt

# securely delete the temp.pem file
shred --remove temp.pem

上述 openssl pkcs8 指令會建立「沒有」密碼的 .pk8 檔案,適用於建構系統。如要建立以密碼保護的 .pk8 檔案 (所有實際發布金鑰都應這麼做),請將 -nocrypt 引數替換為 -passout stdin,然後 openssl 會使用從標準輸入讀取的密碼加密私密金鑰。系統不會列印任何提示,因此如果 stdin 是終端機,程式會顯示為掛斷,但實際上只是在等待您輸入密碼。您可以使用其他值做為 -passout 引數,從其他位置讀取密碼;詳情請參閱 openssl 說明文件

temp.pem 中繼檔案含有私密金鑰,且未受任何密碼保護,因此產生發布金鑰時,請謹慎處理這個檔案。特別是 GNUshred 公用程式可能無法有效處理網路或記錄檔案系統。產生金鑰時,您可以使用位於 RAM 磁碟 (例如 tmpfs 分割區) 中的工作目錄,確保中介項目不會意外曝光。

建立圖片檔案

取得 signed-target_files.zip 後,您必須建立映像檔,才能將其放到裝置上。如要從目標檔案建立已簽署的映像檔,請從 Android 樹狀結構的根目錄執行下列指令:

img_from_target_files signed-target_files.zip signed-img.zip
產生的 signed-img.zip 檔案會包含所有 .img 檔案。 如要在裝置上載入映像檔,請使用 fastboot,如下所示:
fastboot update signed-img.zip