簽署發布版本

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

測試密鑰
未指定密鑰的包的通用默認密鑰。
平台
作為核心平台一部分的包的測試密鑰。
共享
在家庭/聯繫人進程中共享的東西的測試密鑰。
媒體
作為媒體/下載系統一部分的包的測試密鑰。
網絡棧
作為網絡系統一部分的包的測試密鑰。 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 分區)中的工作目錄,以確保不會無意中暴露中間體。

創建圖像文件

獲得簽名目標文件.zip 後,您需要創建映像,以便將其放到設備上。要從目標文件創建簽名映像,請從 Android 樹的根目錄運行以下命令:

img_from_target_files signed-target-files.zip signed-img.zip
生成的文件signed-img.zip包含所有 .img 文件。要將映像加載到設備上,請按如下方式使用 fastboot:
fastboot update signed-img.zip