Android OS images use cryptographic signatures in two places:
- Each
.apk
file inside the image must be signed. Android's Package Manager uses an.apk
signature in two ways:- When an application is replaced, it must be signed by the same key as the
old application in order to get access to the old application's data. This holds
true both for updating user apps by overwriting the
.apk
, and for overriding a system app with a newer version installed under/data
. - If two or more applications want to share a user ID (so they can share data, etc.), they must be signed with the same key.
- When an application is replaced, it must be signed by the same key as the
old application in order to get access to the old application's data. This holds
true both for updating user apps by overwriting the
- OTA update packages must be signed with one of the keys expected by the system or the installation process will reject them.
Release keys
The Android tree includes test-keys under
build/target/product/security
. Building an Android OS image
using make
will sign all .apk
files using the
test-keys. Since the test-keys are publicly known, anybody can sign their own
.apk files with the same keys, which may allow them to replace or hijack system
apps built into your OS image. For this reason it is critical to sign any
publicly released or deployed Android OS image with a special set of
release-keys that only you have access to.
To generate your own unique set of release-keys, run these commands from the root of your Android tree:
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
should be changed to reflect your organization's
information. You can use any directory, but be careful to pick a
location that is backed up and secure. Some vendors choose to encrypt
their private key with a strong passphrase and store the encrypted key
in source control; others store their release keys somewhere else entirely,
such as on an air-gapped computer.
To generate a release image, use:
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
The sign_target_files_apks
script takes a target-files
.zip
as input and produces a new target-files .zip
in
which all the .apk
files have been signed with new keys. The newly
signed images can be found under IMAGES/
in
signed-target_files.zip
.
Sign OTA packages
A signed target-files zip can be converted into a signed OTA update zip using the following procedure:
ota_from_target_files \
-k (--package_key)
signed-target_files.zip \
signed-ota_update.zip
Signatures and sideloading
Sideloading does not bypass recovery's normal package signature verification mechanism—before installing a package, recovery will verify that it is signed with one of the private keys matching the public keys stored in the recovery partition, just as it would for a package delivered over-the-air.
Update packages received from the main system are typically verified twice:
once by the main system, using the
RecoverySystem.verifyPackage()
method in the android API, and then again by
recovery. The RecoverySystem API checks the signature against public keys
stored in the main system, in the file /system/etc/security/otacerts.zip
(by default). Recovery checks the signature against public keys stored
in the recovery partition RAM disk, in the file /res/keys
.
By default, the target-files .zip
produced by the build sets the
OTA certificate to match the test key. On a released image, a different
certificate must be used so that devices can verify the authenticity of the
update package. Passing the -o
flag to
sign_target_files_apks
, as shown in the previous section, replaces
the test key certificate with the release key certificate from your certs
directory.
Normally the system image and recovery image store the same set of OTA public keys. By adding a key to just the recovery set of keys, it is possible to sign packages that can be installed only via sideloading (assuming the main system's update download mechanism is correctly doing verification against otacerts.zip). You can specify extra keys to be included only in recovery by setting the PRODUCT_EXTRA_RECOVERY_KEYS variable in your product definition:
vendor/yoyodyne/tardis/products/tardis.mk
[...] PRODUCT_EXTRA_RECOVERY_KEYS := vendor/yoyodyne/security/tardis/sideload
This includes the public key
vendor/yoyodyne/security/tardis/sideload.x509.pem
in the recovery
keys file so it can install packages signed
with it. The extra key is not included in otacerts.zip though, so
systems that correctly verify downloaded packages do not invoke recovery for
packages signed with this key.
Certificates and private keys
Each key comes in two files: the certificate, which has the extension .x509.pem, and the private key, which has the extension .pk8. The private key should be kept secret and is needed to sign a package. The key may itself be protected by a password. The certificate, in contrast, contains only the public half of the key, so it can be distributed widely. It is used to verify a package has been signed by the corresponding private key.
The standard Android build uses five keys, all of which reside in
build/target/product/security
:
- testkey
- Generic default key for packages that do not otherwise specify a key.
- platform
- Test key for packages that are part of the core platform.
- shared
- Test key for things that are shared in the home/contacts process.
- media
- Test key for packages that are part of the media/download system.
Individual packages specify one of these keys by setting LOCAL_CERTIFICATE in their Android.mk file. (testkey is used if this variable is not set.) You can also specify an entirely different key by pathname, e.g.:
device/yoyodyne/apps/SpecialApp/Android.mk
[...] LOCAL_CERTIFICATE := device/yoyodyne/security/special
Now the build uses the device/yoyodyne/security/special.{x509.pem,pk8}
key to sign SpecialApp.apk. The build can use only private keys that
are not password protected.
Advanced signing options
APK signing key replacement
The signing script sign_target_files_apks
works on the target
files generated for a build. All the information on certificates and private
keys used at build time is included in the target files. When running the
signing script to sign for release, signing keys can be replaced based on key
name or APK name.
Use the --key_mapping
and --default_key_mappings
flags to specify key replacement based on key names:
- The
--key_mapping src_key=dest_key
flag specifies the replacement for one key at a time. - The
--default_key_mappings dir
flag specifies a directory with five keys to replace all the keys inbuild/target/product/security
; it's equivalent to using--key_mapping
five times to specify the mappings.
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
Use the
--extra_apks apk_name1,apk_name2,...=key
flag
to specify the signing key replacements based on APK names. If
key
is left empty, the script treats the specified APKs
as pre-signed.
For the hypothetical tardis product, you need six password-protected keys:
five to replace the five in build/target/product/security
, and one
to replace the additional key device/yoyodyne/security/special
required by SpecialApp in the example above. If the keys were in the following
files:
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
Then you would sign all the apps like this:
./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
This brings up the following:
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.
After prompting the user for passwords for all password-protected keys, the
script re-signs all the APK files in the input target .zip
with the
release keys. Before running the command, you can also set the
ANDROID_PW_FILE
environment variable to a temporary filename; the
script then invokes your editor to allow you to enter passwords for all keys
(this may be a more convenient way to enter passwords).
APEX signing key replacement
Android 10 introduces the APEX file format for installing lower-level system modules. As explained in APEX signing, each APEX file is signed with two keys: one for the mini file system image within an APEX and the other for the entire APEX.
When signing for release, the two signing keys for an APEX file are replaced
with release keys. The file system payload key is specified with the
--extra_apex_payload
flag and the entire APEX file signing key is
specified with the --extra_apks
flag.
For the tardis product, assume that you have the following key configuration
for the com.android.conscrypt.apex
,
com.android.media.apex
, and
com.android.runtime.release.apex
APEX files.
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"
And you have the following files that contain the release keys:
vendor/yoyodyne/security/runtime_apex_container.x509.pem vendor/yoyodyne/security/runtime_apex_container.pk8 vendor/yoyodyne/security/runtime_apex_payload.pem
The following command overrides the signing keys for
com.android.runtime.release.apex
and
com.android.tzdata.apex
during release signing. In particular,
com.android.runtime.release.apex
is signed with the specified
release keys (runtime_apex_container
for the APEX file, and
runtime_apex_payload
for the file image payload).
com.android.tzdata.apex
is treated as pre-signed. All other APEX
files are handled by the default configuration as listed in the target files.
./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
Running the above command gives the following logs:
[...] 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) [...]
Other options
The sign_target_files_apks
signing script rewrites the build
description and fingerprint in the build properties files to reflect that the
build is a signed build. The --tag_changes
flag controls what edits
are made to the fingerprint. Run the script with -h
to see
documentation on all flags.
Manually generate keys
Android uses 2048-bit RSA keys with public exponent 3. You can generate certificate/private key pairs using the openssl tool from openssl.org:
# 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
The openssl pkcs8 command given above creates a .pk8 file with no
password, suitable for use with the build system. To create a .pk8 secured
with a password (which you should do for all actual release keys), replace the
-nocrypt
argument with -passout stdin
; then openssl
will encrypt the private key with a password read from standard input. No
prompt is printed, so if stdin is the terminal the program will appear to hang
when it's really just waiting for you to enter a password. Other values can be
used for the-passout argument to read the password from other locations; for
details, see the
openssl documentation.
The temp.pem intermediate file contains the private key without any kind of password protection, so dispose of it thoughtfully when generating release keys. In particular, the GNUshred utility may not be effective on network or journaled filesystems. You can use a working directory located in a RAM disk (such as a tmpfs partition) when generating keys to ensure the intermediates are not inadvertently exposed.
Create image files
When you have signed-target_files.zip
, you need to
create the image so you can put it onto a device.
To create the signed image from the target files, run
the following command from the root of the Android
tree:
img_from_target_files signed-target_files.zip signed-img.zip
signed-img.zip
, contains all the .img
files.
To load an image onto a device, use fastboot as
follows:
fastboot update signed-img.zip