對要發佈的版本進行簽名
Android 操作系統映像在兩個地方使用加密簽名:
- 映像中的所有 .apk 文件都必須經過簽名。Android 軟件包管理器通過下列兩種方式使用 .apk 簽名:
更換應用時,必須使用與舊應用相同的密鑰對其簽名,才能存取舊應用的數據。無論是通過覆蓋 .apk 來更新用戶應用,還是使用安裝在 /data
下的新版本應用來覆蓋系統應用,這一點都適用。 如果兩個或多個應用想要共享同一個用戶
ID(方便共享數據等),則必須使用相同的密鑰對它們進行簽名。
- 必須使用符合系統預期的密鑰對 OTA 更新包進行簽名,否則在安裝過程中 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/[email protected]'
mkdir ~/.android-certs
for x in releasekey platform shared media; do \
./development/tools/make_key ~/.android-certs/$x "$subject"; \
done
您需要對 $subject 進行更改以反映貴組織的信息。您可以使用任何目錄,但要注意選擇一個已備份且安全的位置。部分供應商會使用強密碼加密私鑰,並將其存儲在源代碼控制系統中;其他供應商則將他們的發佈密鑰完全存儲在其他地方,如氣隙阻隔的計算機上。
要生成發佈映像,請使用以下命令:
make dist
./build/tools/releasetools/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.zip 中的 IMAGES/ 目錄下找到新簽名的映像。
對 OTA 更新包進行簽名
您可以按照以下步驟將已簽名的目標文件 zip 轉換爲已簽名的 OTA 更新 zip:
./build/tools/releasetools/ota_from_target_files \
--key_mapping ~/.android-certs/releasekey \
signed-target_files.zip \
signed-ota_update.zip
簽名和旁加載
旁加載不會繞過 Recovery 流程中的正常軟件包簽名驗證機制。在安裝一個軟件包之前,Recovery 會驗證該軟件包是否由與 Recovery 分區中存儲的公鑰相匹配的私鑰進行簽名,這與利用無線方式傳輸的軟件包的處理方式一樣。
從主系統收到的更新包通常要經過兩次驗證:一次是由主系統使用 Android API 中的 RecoverySystem.verifyPackage() 方法進行驗證,另一次是通過 Recovery 進行驗證。RecoverySystem API 對照存儲在主系統 /system/etc/security/otacerts.zip 文件(默認情況下)中的公鑰對簽名進行檢查。Recovery 對照存儲在 Recovery 分區 RAM 磁盤中的 /res/keys 文件中的公鑰對簽名進行檢查。
默認情況下,由此版本生成的目標文件 .zip 會將 OTA 證書設置爲與測試密鑰相匹配。在發佈的映像上,必須使用不同的證書,這樣設備才能驗證更新包的真實性。如前面一部分所示,將 -o 標誌傳遞到 sign_target_files_apks 即可將測試密鑰證書替換成您的證書目錄中的發佈密鑰證書。
通常情況下,系統映像和 Recovery 映像存儲的是相同的 OTA 公鑰集。通過將密鑰僅添加至 Recovery 密鑰集,可對只能通過旁加載安裝的 apk 包(假設主系統的更新下載機制正確地對照 otacerts.zip 進行驗證)簽名。您可以通過在產品定義中設置 PRODUCT_EXTRA_RECOVERY_KEYS 變量來指定其他僅可納入 Recovery 中的密鑰:
vendor/yoyodyne/tardis/products/tardis.mk
[...]
PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload
其中包括 Recovery 密鑰文件中的公鑰 vendor/yoyodyne/security/tardis/sideload.x509.pem
,因此它可以安裝用此公鑰簽名的 apk 包。但 otacerts.zip 中不包含額外的密鑰,因此正確驗證下載包的系統不會針對用此密鑰簽名的 apk 包調用 Recovery。
證書和私鑰
每個密鑰都包含兩個文件:一個是擴展名爲 .x509.pem 的證書,另一個是擴展名爲 .pk8 的私鑰。私鑰需要加以保密,並用於對 apk 包進行簽名。密鑰本身也可能受密碼保護。相比之下,證書只包含公開的一半密鑰,因此可以大範圍地分發。證書被用於驗證某個 apk 包是否由相應的私鑰進行簽名。
標準 Android 版本使用四個密鑰,所有這些密鑰都位於 build/target/product/security
中:
- testkey 適用於未另外指定密鑰的 apk 包的通用默認密鑰。
- 平臺 適用於核心平臺所包含的 apk 包的測試密鑰。
- 共享 適用於家庭/聯繫人進程中的共享內容的測試密鑰。
- 媒體 適用於媒體/下載系統所包含的 apk 包的測試密鑰。
單個 apk 包通過在其 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
使用 --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/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/tools/releasetools/sign_target_files_apks \
--default_key_mappings vendor/yoyodyne/security/tardis \
--key_mapping vendor/yoyodyne/special=vendor/yoyodyne/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/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: 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
的簽名密鑰。特別是,系統會使用指定的發佈密鑰(對於 APEX 文件,爲 runtime_apex_container;對於文件映像負載,則爲 runtime_apex_payload)爲 com.android.runtime.release.apex 簽名。 com.android.tzdata.apex 會被視爲已預簽名。所有其他 APEX 文件都由目標文件中列出的默認配置處理。
./build/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/[email protected]'
# 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 樹形結構的根目錄下運行以下命令:
./build/tools/releasetools/img_from_target_files signed-target-files.zip signed-img.zip
生成的文件 signed-img.zip 中包含所有 .img 文件。要將映像加載到設備上,請使用 fastboot,如下所示:
fastboot update signed-img.zip