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/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
签名和旁加载
旁加载不会绕过 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
这包括恢复密钥文件中的公钥 vendor/yoyodyne/security/tardis/sideload.x509.pem
,因此它可以安装用此密钥签名的软件包。但 otacerts.zip 中不包含额外的密钥,因此正确验证下载包的系统不会针对用此密钥签名的 apk 包调用 Recovery。
证书和私钥
每个密钥都包含两个文件:一个是扩展名为 .x509.pem 的证书,另一个是扩展名为 .pk8 的私钥。 私钥需要加以保密,并用于对 apk 包进行签名。 密钥本身也可能受密码保护。相比之下,证书只包含公开的一半密钥,因此可以大范围地分发。证书用于验证某个 apk 包是否由相应的私钥进行签名。
标准 Android build 使用四个密钥,所有这些密钥都位于
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
现在,build 使用 device/yoyodyne/security/special.{x509.pem,pk8}
密钥对 SpecialApp.apk 进行签名。此 build 仅可使用不受密码保护的私钥。
高级签名选项
APK 签名密钥替换
签名脚本 sign_target_files_apks
适用于为 build 生成的目标文件。构建时使用的证书和私钥的所有相关信息都包含在目标文件中。在运行签名脚本以针对发布进行签名时,可以基于密钥名称或 APK 名称替换签名密钥。
使用 --key_mapping
和 --default_key_mappings
标志根据键名指定密钥替换:
--key_mapping src_key=dest_key
标志用于每次指定一个替换密钥。--default_key_mappings dir
标志指定一个具有五个键的目录,以替换build/target/product/security
中的所有密钥;这相当于使用 5 次--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.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
的签名密钥。尤其是,com.android.runtime.release.apex
是采用指定的发布密钥(对于 APEX 文件,为 runtime_apex_container
,并且文件映像负载为 runtime_apex_payload
)的签名。系统会将 com.android.tzdata.apex
视为 PRESIGNED。所有其他 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
签名脚本会在 build 属性文件中重写 build 描述和指纹,表明这是一个已签名的 build。--tag_changes
标志可以控制对指纹所做的编辑。使用 -h
运行脚本可以查看所有标志上的文档。
手动生成密钥
Android 使用公开指数为 3 的 2048 位 RSA 密钥。您可以使用 openssl.org 提供的 openssl 工具来生成证书/私钥对:
# generate RSA keyopenssl 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 keyopenssl 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 keyopenssl pkcs8 -in temp.pem -topk8 -outform DER -out releasekey.pk8 -nocrypt
# securely delete the temp.pem fileshred --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