簽署版本以供發布

Android 作業系統映像在兩個地方使用加密簽章:

  1. 映像中的每個.apk檔案都必須經過簽署。 Android 的套件管理器以兩種方式使用.apk簽署:
    • 當應用程式被替換時,必須使用與舊應用程式相同的金鑰對其進行簽名,才能存取舊應用程式的資料。這對於透過覆蓋.apk來更新使用者應用程式以及使用安裝在/data下的較新版本覆蓋系統應用程式都適用。
    • 如果兩個或多個應用程式想要共用使用者 ID(以便它們可以共用資料等),則必須使用相同的金鑰對它們進行簽署。
  2. 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.zipIMAGES/下找到。

簽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中:

測試鍵
未另行指定金鑰的包的通用預設金鑰。
平台
屬於核心平台一部分的軟體包的測試密鑰。
共享
測試家庭/聯絡人程序中共享的事物的金鑰。
媒體
測試屬於媒體/下載系統的軟體包的金鑰。
網路堆疊
測試屬於網路系統一部分的軟體包的密鑰。 Networkstack 金鑰用於對設計為模組化系統元件的二進位進行簽署。如果您的模組更新是單獨建置的,並作為預先建置整合在裝置映像中,則可能不需要在 Android 原始程式碼樹中產生網路堆疊金鑰。

各個套件透過在其 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