Builds für die Veröffentlichung signieren

In Android-Betriebssystem-Images werden kryptografische Signaturen an zwei Stellen verwendet:

  1. Jede .apk-Datei im Image muss signiert sein. Der Paketmanager von Android verwendet eine .apk-Signatur auf zwei Arten:
    • Wenn eine Anwendung ersetzt wird, muss sie mit demselben Schlüssel wie die alte Anwendung signiert sein, um auf die Daten der alten Anwendung zugreifen zu können. Das gilt sowohl für das Aktualisieren von Nutzeranwendungen durch Überschreiben der .apk-Datei als auch für das Überschreiben einer Systemanwendung mit einer neueren Version, die unter /data installiert ist.
    • Wenn zwei oder mehr Anwendungen eine Nutzer-ID gemeinsam verwenden möchten (z. B. um Daten gemeinsam zu nutzen), müssen sie mit demselben Schlüssel signiert sein.
  2. OTA-Updatepakete müssen mit einem der Schlüssel signiert sein, die vom System erwartet werden. Andernfalls werden sie vom Installationsprozess abgelehnt.

Release-Schlüssel

Der Android-Baum enthält Testschlüssel unter build/target/product/security. Wenn Sie ein Android-Betriebssystem-Image mit make erstellen, werden alle .apk-Dateien mit den Testschlüsseln signiert. Da die Testschlüssel öffentlich bekannt sind, kann jeder seine eigenen `.apk`-Dateien mit denselben Schlüsseln signieren. Dadurch können Systemanwendungen, die in Ihr Betriebssystem-Image integriert sind, möglicherweise ersetzt oder missbraucht werden. Aus diesem Grund ist es wichtig, alle öffentlich veröffentlichten oder bereitgestellten Android-Betriebssystem-Images mit einem speziellen Satz von Release-Schlüsseln zu signieren, auf die nur Sie Zugriff haben.

Führen Sie die folgenden Befehle im Stammverzeichnis Ihres Android-Baums aus, um einen eigenen eindeutigen Satz von Release-Schlüsseln zu generieren:

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 sollte so geändert werden, dass die Informationen Ihrer Organisation widergespiegelt werden. Sie können ein beliebiges Verzeichnis verwenden, sollten aber einen Speicherort auswählen, der gesichert und sicher ist. Einige Anbieter verschlüsseln ihren privaten Schlüssel mit einer starken Passphrase und speichern den verschlüsselten Schlüssel in der Quellcodeverwaltung. Andere speichern ihre Release-Schlüssel an einem ganz anderen Ort, z. B. auf einem Computer ohne Internetverbindung.

Verwenden Sie Folgendes, um ein Release-Image zu generieren:

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

Das Skript sign_target_files_apks verwendet eine .zip-Datei mit Zieldateien als Eingabe und erstellt eine neue .zip-Datei mit Zieldateien, in der alle .apk-Dateien mit neuen Schlüsseln signiert wurden. Die neu signierten Images finden Sie unter IMAGES/ in signed-target_files.zip.

OTA-Pakete signieren

Eine signierte `.zip`-Datei mit Zieldateien kann mit der folgenden Methode in eine signierte `.zip`-Datei für OTA-Updates konvertiert werden:
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

Signaturen und Sideloading

Beim Sideloading wird der normale Mechanismus zur Überprüfung der Paketsignatur von Recovery nicht umgangen. Vor der Installation eines Pakets überprüft Recovery, ob es mit einem der privaten Schlüssel signiert ist, die den öffentlichen Schlüsseln entsprechen, die in der Recovery-Partition gespeichert sind. Das ist genauso wie bei einem Paket, das per Over-the-Air-Verbindung bereitgestellt wird.

Updatepakete, die vom Hauptsystem empfangen werden, werden in der Regel zweimal überprüft: einmal vom Hauptsystem mit der Methode RecoverySystem.verifyPackage() in der Android API und dann noch einmal von Recovery. Die RecoverySystem API vergleicht die Signatur mit öffentlichen Schlüsseln die im Hauptsystem in der Datei /system/etc/security/otacerts.zip (Standard) gespeichert sind. Recovery vergleicht die Signatur mit öffentlichen Schlüsseln, die auf der RAM-Disk der Recovery-Partition in der Datei /res/keys gespeichert sind.

Standardmäßig wird das OTA-Zertifikat in der .zip-Datei mit Zieldateien, die vom Build erstellt wurde, so festgelegt, dass es mit dem Testschlüssel übereinstimmt. Bei einem veröffentlichten Image muss ein anderes Zertifikat verwendet werden, damit Geräte die Authentizität des Updatepakets überprüfen können. Wenn Sie das Flag -o an sign_target_files_apks übergeben, wie im vorherigen Abschnitt gezeigt, wird das Zertifikat des Testschlüssels durch das Zertifikat des Release-Schlüssels aus Ihrem Zertifikatsverzeichnis ersetzt.

Normalerweise speichern das Systemimage und das Wiederherstellungsimage denselben Satz öffentlicher OTA-Schlüssel. Wenn Sie nur dem Satz von Schlüsseln für Recovery einen Schlüssel hinzufügen, können Sie Pakete signieren, die nur per Sideloading installiert werden können. Voraussetzung ist, dass der Mechanismus zum Herunterladen von Updates des Hauptsystems die Überprüfung anhand von „otacerts.zip“ korrekt durchführt. Sie können zusätzliche Schlüssel angeben, die nur in Recovery enthalten sein sollen, indem Sie die Variable PRODUCT_EXTRA_RECOVERY_KEYS in Ihrer Produktdefinition festlegen:

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

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

Dadurch wird der öffentliche Schlüssel vendor/yoyodyne/security/tardis/sideload.x509.pem in die Schlüsseldatei für Recovery aufgenommen, sodass Pakete installiert werden können, die damit signiert sind. Der zusätzliche Schlüssel ist jedoch nicht in „otacerts.zip“ enthalten. Systeme, die heruntergeladene Pakete korrekt überprüfen, rufen Recovery daher nicht für Pakete auf, die mit diesem Schlüssel signiert sind.

Zertifikate und private Schlüssel

Jeder Schlüssel besteht aus zwei Dateien: dem Zertifikat mit der Erweiterung „.x509.pem“ und dem privaten Schlüssel mit der Erweiterung „.pk8“. Der private Schlüssel muss geheim gehalten werden und ist erforderlich, um ein Paket zu signieren. Der Schlüssel selbst kann durch ein Passwort geschützt sein. Das Zertifikat enthält dagegen nur die öffentliche Hälfte des Schlüssels und kann daher weit verbreitet werden. Es wird verwendet, um zu überprüfen, ob ein Paket mit dem entsprechenden privaten Schlüssel signiert wurde.

Der Standard-Android-Build verwendet fünf Schlüssel, die sich alle in build/target/product/security befinden:

testkey
Allgemeiner Standardschlüssel für Pakete, für die kein anderer Schlüssel angegeben ist.
platform
Testschlüssel für Pakete, die Teil der Kernplattform sind.
shared
Testschlüssel für Elemente, die im Prozess „Zuhause/Kontakte“ freigegeben werden.
media
Testschlüssel für Pakete, die Teil des Systems für Medien/Downloads sind.
networkstack
Testschlüssel für Pakete, die Teil des Netzwerksystems sind. Der Schlüssel „networkstack“ wird verwendet, um Binärdateien zu signieren, die als modulare Systemkomponenten konzipiert sind. Wenn Ihre Modulupdates separat erstellt und als Prebuilts in Ihr Geräte-Image integriert werden, müssen Sie möglicherweise keinen „networkstack“-Schlüssel im Android-Quellbaum generieren.

Für einzelne Pakete wird einer dieser Schlüssel angegeben, indem LOCAL_CERTIFICATE in der Datei „Android.mk“ festgelegt wird. Wenn diese Variable nicht festgelegt ist, wird „testkey“ verwendet. Sie können auch einen ganz anderen Schlüssel nach Pfad angeben, z.B.:

device/yoyodyne/apps/SpecialApp/Android.mk
 [...]

LOCAL_CERTIFICATE := device/yoyodyne/security/special

Jetzt verwendet der Build den Schlüssel device/yoyodyne/security/special.{x509.pem,pk8} , um „SpecialApp.apk“ zu signieren. Der Build kann nur private Schlüssel verwenden, die nicht durch ein Passwort geschützt sind.

Erweiterte Signaturoptionen

App-Signaturschlüssel ersetzen

Das Signaturskript sign_target_files_apks wird auf die Zieldateien angewendet, die für einen Build generiert wurden. Alle Informationen zu Zertifikaten und privaten Schlüsseln, die zur Build-Zeit verwendet werden, sind in den Zieldateien enthalten. Wenn Sie das Signaturskript zum Signieren für die Veröffentlichung ausführen, können Signaturschlüssel anhand des Schlüsselnamens oder des APK-Namens ersetzt werden.

Verwenden Sie die Flags --key_mapping und --default_key_mappings, um den Schlüsselaustausch anhand von Schlüsselnamen anzugeben:

  • Mit dem --key_mapping src_key=dest_key Flag wird der Austausch für jeweils einen Schlüssel angegeben.
  • Das --default_key_mappings dir Flag gibt ein Verzeichnis mit fünf Schlüsseln an, um alle Schlüssel in build/target/product/security zu ersetzen. Es entspricht der fünfmaligen Verwendung von --key_mapping zum Angeben der Zuordnungen.
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

Verwenden Sie das --extra_apks apk_name1,apk_name2,...=key Flag um die Ersetzungen des Signaturschlüssels anhand von APK-Namen anzugeben. Wenn key leer gelassen wird, behandelt das Skript die angegebenen APKs als bereits signiert.

Für das hypothetische Tardis-Produkt benötigen Sie sechs passwortgeschützte Schlüssel: fünf, um die fünf in build/target/product/security zu ersetzen, und einen, um den zusätzlichen Schlüssel device/yoyodyne/security/special zu ersetzen, der im obigen Beispiel für „SpecialApp“ erforderlich ist. Wenn sich die Schlüssel in den folgenden Dateien befinden:

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

Dann würden Sie alle Apps so signieren:

./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

Das führt zu Folgendem:

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.

Nachdem der Nutzer zur Eingabe von Passwörtern für alle passwortgeschützten Schlüssel aufgefordert wurde, signiert das Skript alle APK-Dateien in der Eingabe-.zip-Datei mit Zieldateien mit den Release-Schlüsseln neu. Bevor Sie den Befehl ausführen, können Sie auch die Umgebungsvariable ANDROID_PW_FILE auf einen temporären Dateinamen festlegen. Das Skript ruft dann Ihren Editor auf, damit Sie Passwörter für alle Schlüssel eingeben können. Das ist möglicherweise eine bequemere Möglichkeit, Passwörter einzugeben.

APEX-Signaturschlüssel ersetzen

In Android 10 wurde das APEX-Dateiformat für die Installation von Systemmodulen auf niedrigerer Ebene eingeführt. Wie unter APEX-Signierungerläutert, wird jede APEX-Datei mit zwei Schlüsseln signiert: einem für das Mini-Dateisystem-Image in einer APEX-Datei und dem anderen für die gesamte APEX-Datei.

Beim Signieren für die Veröffentlichung werden die beiden Signaturschlüssel für eine APEX-Datei durch Release-Schlüssel ersetzt. Der Schlüssel für die Dateisystemnutzlast wird mit dem Flag --extra_apex_payload angegeben und der Signaturschlüssel für die gesamte APEX-Datei mit dem Flag --extra_apks.

Nehmen wir an, Sie haben für das Tardis-Produkt die folgende Schlüsselkonfiguration für die APEX-Dateien com.android.conscrypt.apex, com.android.media.apex und com.android.runtime.release.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"

Und Sie haben die folgenden Dateien, die die Release-Schlüssel enthalten:

vendor/yoyodyne/security/runtime_apex_container.x509.pem
vendor/yoyodyne/security/runtime_apex_container.pk8
vendor/yoyodyne/security/runtime_apex_payload.pem

Mit dem folgenden Befehl werden die Signaturschlüssel für com.android.runtime.release.apex und com.android.tzdata.apex während der Signierung für die Veröffentlichung überschrieben. Insbesondere wird com.android.runtime.release.apex mit den angegebenen Release-Schlüsseln signiert (runtime_apex_container für die APEX-Datei und runtime_apex_payload für die Nutzlast des Dateisystem-Images). com.android.tzdata.apex wird als bereits signiert behandelt. Alle anderen APEX-Dateien werden gemäß der Standardkonfiguration verarbeitet, die in den Zieldateien aufgeführt ist.

./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

Wenn Sie den obigen Befehl ausführen, werden die folgenden Logs angezeigt:

        [...]
    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)
        [...]

Sonstige Optionen

Das Signaturskript sign_target_files_apks überschreibt die Build-Beschreibung und den Fingerabdruck in den Build-Eigenschaftsdateien, um anzugeben, dass es sich um einen signierten Build handelt. Mit dem Flag --tag_changes wird festgelegt, welche Änderungen am Fingerabdruck vorgenommen werden. Führen Sie das Skript mit -h aus, um die Dokumentation zu allen Flags aufzurufen.

Schlüssel manuell generieren

Android verwendet 2048-Bit-RSA-Schlüssel mit dem öffentlichen Exponenten 3. Sie können Zertifikats-/Privatschlüsselpaare mit dem Tool „openssl“ von openssl.orggenerieren:

# 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

Mit dem oben angegebenen Befehl „openssl pkcs8“ wird eine „.pk8“-Datei ohne Passwort erstellt, die für das Build-System geeignet ist. Wenn Sie eine „.pk8“-Datei mit einem Passwort schützen möchten (was Sie für alle tatsächlichen Release-Schlüssel tun sollten), ersetzen Sie das Argument -nocrypt durch -passout stdin. Dann verschlüsselt „openssl“ den privaten Schlüssel mit einem Passwort, das aus der Standardeingabe gelesen wird. Es wird keine Eingabeaufforderung angezeigt. Wenn die Standardeingabe also das Terminal ist, scheint das Programm zu hängen, obwohl es nur darauf wartet, dass Sie ein Passwort eingeben. Für das Argument „-passout“ können auch andere Werte verwendet werden, um das Passwort von anderen Speicherorten zu lesen. Weitere Informationen finden Sie in der openssl-Dokumentation.

Die temporäre Datei „temp.pem“ enthält den privaten Schlüssel ohne Passwortschutz. Gehen Sie daher beim Generieren von Release-Schlüsseln sorgfältig damit um. Insbesondere das Dienstprogramm GNUshred ist bei Netzwerk- oder Journaling-Dateisystemen möglicherweise nicht effektiv. Sie können ein Arbeitsverzeichnis auf einer RAM-Disk (z. B. einer tmpfs-Partition) verwenden, wenn Sie Schlüssel generieren, um zu verhindern, dass die Zwischenschritte versehentlich offengelegt werden.

Image-Dateien erstellen

Wenn Sie signed-target_files.zip haben, müssen Sie das Image erstellen, damit Sie es auf ein Gerät übertragen können. Führen Sie den folgenden Befehl im Stammverzeichnis des Android-Baums aus, um das signierte Image aus den Zieldateien zu erstellen:

img_from_target_files signed-target_files.zip signed-img.zip
Die resultierende Datei signed-img.zip enthält alle .img-Dateien. So laden Sie ein Image auf ein Gerät: Verwenden Sie dazu Fastboot wie folgt:
fastboot update signed-img.zip