为发布版本签名

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 脚本将目标文件 .zip 作为输入,并生成一个新的目标文件 .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() 方法,然后 recovery 再次验证。RecoverySystem API 会根据存储在主系统文件 /system/etc/security/otacerts.zip (默认情况下)中的公钥检查签名。Recovery 会根据存储在 recovery 分区 RAM 磁盘文件 /res/keys 中的公钥检查签名。

默认情况下,构建生成的 target-files .zip 将 OTA 证书设置为与测试密钥匹配。在发布的映像上,必须使用不同的证书,以便设备可以验证更新包的真实性。将 -o 标志传递给 sign_target_files_apks(如上一节所示)会将测试密钥证书替换为您证书目录中的发布密钥证书。

通常,系统映像和 recovery 映像存储同一组 OTA 公钥。通过仅将密钥添加到 recovery 密钥集,可以对只能通过侧载安装的软件包进行签名(假设主系统的更新下载机制正在正确地针对 otacerts.zip 进行验证)。您可以通过在产品定义中设置 PRODUCT_EXTRA_RECOVERY_KEYS 变量来指定仅包含在 recovery 中的额外密钥

vendor/yoyodyne/tardis/products/tardis.mk
 [...]

PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload

这会将公钥 vendor/yoyodyne/security/tardis/sideload.x509.pem 包含在 recovery 密钥文件中,以便它可以安装使用该密钥签名的软件包。但是,额外的密钥包含在 otacerts.zip 中,因此对于使用此密钥签名的软件包,正确验证下载软件包的系统不会调用 recovery。

证书和私钥

每个密钥都包含两个文件:证书(扩展名为 .x509.pem)和私钥(扩展名为 .pk8)。私钥应保密,并且是签署软件包所必需的。密钥本身可能受密码保护。相比之下,证书仅包含密钥的公钥部分,因此可以广泛分发。它用于验证软件包是否已通过相应的私钥签名。

标准的 Android 构建使用五个密钥,所有这些密钥都位于 build/target/product/security

testkey
未另行指定密钥的软件包的通用默认密钥。
platform
核心平台软件包的测试密钥。
shared
在 home/contacts 进程中共享的事物的测试密钥。
media
媒体/下载系统软件包的测试密钥。
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 使用指定的发布密钥签名(runtime_apex_container 用于 APEX 文件,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 中间文件包含没有任何密码保护的私钥,因此在生成发布密钥时请周全地处置它。特别是,GNU shred 实用程序可能在网络或日志文件系统上无效。生成密钥时,您可以使用位于 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