출시용 빌드 서명

Android OS 이미지는 아래와 같은 두 위치에 암호화 서명을 사용합니다.

  1. 이미지 내의 각 .apk 파일을 서명해야 합니다. Android의 패키지 관리자는 다음과 같이 두 가지 방식으로 .apk 서명을 사용합니다.
    • 교체된 애플리케이션은 기존 애플리케이션과 동일한 키로 서명되어야만 기존 애플리케이션의 데이터에 액세스할 수 있습니다. 이는 .apk를 덮어쓰기하여 사용자 앱을 업데이트하거나 /data에 설치된 최신 버전으로 시스템 앱을 재정의하는 경우에도 마찬가지입니다.
    • 2개 이상의 애플리케이션이 데이터 공유 등을 위해 사용자 ID를 공유하고 싶어 하는 경우, 동일한 키로 애플리케이션을 서명해야 합니다.
  2. OTA 업데이트 패키지는 시스템에서 예상하는 키 중 하나로 서명되어야 하며, 그렇지 않을 경우 프로세스에서 이를 거부합니다.

키 해제

Android 트리에서는 build/target/product/security 아래에 테스트 키가 포함됩니다. make를 사용하여 Android OS 이미지를 빌드하면 테스트 키를 사용 중인 모든 .apk 파일이 서명됩니다. 테스트 키는 공개적으로 알려져 있으므로 누구나 같은 키로 본인의 .apk 파일을 서명할 수 있으며, 이를 통해 OS 이미지에 빌드된 시스템 앱을 교체하거나 도용할 수도 있습니다. 따라서 공개적으로 출시되었거나 배포된 Android OS 이미지를 본인만 액세스할 수 있는 특수한 출시 키 모음으로 서명하는 것이 중요합니다.

고유한 출시 키 모음을 생성하려면 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을 입력으로 취하고, 모든 .apk 파일이 새 키로 서명된 새로운 타겟 파일 .zip을 생성합니다. 새로 서명된 이미지는 signed-target_files.zipIMAGES/에서 찾을 수 있습니다.

OTA 패키지 서명

서명된 타겟 파일 zip은 다음 절차를 사용하여 서명된 OTA 업데이트 zip으로 변환할 수 있습니다.
ota_from_target_files \
-k  (--package_key) 
signed-target_files.zip \
signed-ota_update.zip

서명 및 사이드로드

사이드로드로 복구의 정상적인 패키지 서명 인증 메커니즘이 우회되지는 않습니다. 복구는 OTA를 통해 제공된 패키지와 마찬가지로 패키지를 설치하기 전에 복구 파티션에 보관된 공개 키와 일치하는 비공개 키 중 하나로 패키지가 서명되었는지 인증합니다.

기본 시스템에서 수신된 업데이트 패키지는 보통 2회에 걸쳐 인증됩니다. 한 번은 기본 시스템에서 Android API의 RecoverySystem.verifyPackage() 메서드를 사용하여 서명되고, 이어서 복구에 의해 서명됩니다. 기본적으로 RecoverySystem API는 기본 시스템의 /system/etc/security/otacerts.zip 파일에 저장된 공개 키를 기준으로 서명을 확인합니다. 복구는 /res/keys 파일의 복구 파티션 RAM 디스크에 저장된 공개 키를 기준으로 서명을 확인합니다.

기본적으로 빌드에 의해 생성된 타겟 파일 .zip은 테스트 키와 일치하도록 OTA 인증서를 설정합니다. 출시된 이미지에는 기기가 업데이트 패키지의 신뢰성을 확인할 수 있도록 다른 인증서를 사용해야 합니다. 이전 섹션에서 본 것처럼 -o 플래그를 sign_target_files_apks에 전달하면 테스트 키 인증서가 인증서 디렉터리의 출시 키 인증서로 교체됩니다.

일반적으로 시스템 이미지와 복구 이미지에는 같은 OTA 공개 키 모음이 저장됩니다. 키를 키의 복구 모음에만 추가하면 사이드로드를 통해서만 설치 가능한 패키지를 서명할 수 있습니다(기본 시스템의 업데이트 다운로드 메커니즘이 otacerts.zip에 대한 인증을 올바르게 수행 중이라는 가정 하에). PRODUCT_EXTRA_RECOVERY_KEYS 변수를 제품 정의에 설정하여 복구에만 포함할 추가 키를 지정할 수 있습니다.

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

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

여기에는 복구 키 파일의 공개 키 vendor/yoyodyne/security/tardis/sideload.x509.pem이 포함되므로 해당 키로 서명된 패키지를 설치할 수 있습니다. 하지만 추가 키는 otacerts.zip에 포함되지 않습니다. 따라서 다운로드된 패키지를 올바르게 인증하는 시스템에서 이 키로 서명된 패키지에 대한 복구를 호출하지 않습니다.

인증서 및 비공개 키

각 키는 확장자가 .x509.pem인 인증서와 확장자가 .pk8인 비공개 키, 이렇게 두 개의 파일로 제공됩니다. 비공개 키는 비밀로 유지해야 하며, 패키지를 서명하는 데 사용됩니다. 키 자체가 비밀번호로 보호될 수도 있습니다. 반면에 인증서는 공개 키만 포함하기 때문에 광범위하게 배포할 수 있습니다. 이는 상응하는 비공개 키로 서명된 패키지를 인증하는 데 사용됩니다.

표준 Android 빌드에는 모두 build/target/product/security에 있는 키 5개를 사용합니다.

testkey
달리 키를 지정하지 않는 패키지의 일반적인 기본 키입니다.
platform
핵심 플랫폼의 일부인 패키지의 테스트 키입니다.
shared
홈/연락처 프로세스에서 공유되는 항목에 대한 테스트 키입니다.
media
미디어/다운로드 시스템의 일부인 패키지의 테스트 키입니다.
networkstack
네트워킹 시스템의 일부인 패키지의 테스트 키입니다. networkstack 키는 모듈식 시스템 구성요소로 설계된 바이너리에 서명하는 데 사용됩니다. 모듈 업데이트가 별도로 빌드되어 기기 이미지에 사전 빌드된 것으로 통합되는 경우 Android 소스 트리에서 networkstack 키를 생성하지 않아도 될 수 있습니다.

개별 패키지는 Android.mk 파일에서 LOCAL_CERTIFICATE를 설정하여 이러한 키 중 하나를 지정합니다. (이 변수가 설정되지 않은 경우 테스트 키가 사용됩니다.) 또한 경로 이름으로 완전히 다른 키를 지정할 수도 있습니다. 예를 들면 다음과 같습니다.

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 플래그는 한 번에 키 1개의 교체를 지정합니다.
  • --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 제품의 경우 비밀번호로 보호된 키 6개가 필요합니다. 5개는 build/target/product/security의 키 5개를 교체하는 데 사용되고 다른 하나는 위의 예에 나온 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 파일의 서명 키 2개가 릴리스 키로 교체됩니다. 파일 시스템 페이로드 키는 --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.apexcom.android.tzdata.apex의 서명 키를 재정의합니다. 특히 com.android.runtime.release.apex는 지정된 릴리스 키 (APEX 파일의 경우 runtime_apex_container, 파일 이미지 페이로드의 경우 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-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-nocrypt-passout stdin 인수를 -nocrypt-passout stdin-passout stdin으로 교체합니다. 그러면 openssl이 표준 입력에서 읽은 비밀번호로 비공개 키를 암호화합니다. 프롬프트는 출력되지 않습니다. 따라서 stdin이 터미널인 경우 프로그램이 대기 중인 것으로 보일 수 있지만 실제로는 단순히 비밀번호 입력을 기다리고 있는 것입니다. the-passout 인수로 다른 값을 사용하면 다른 위치에서 비밀번호를 읽을 수 있습니다. 자세한 내용은 openssl 문서를 참조하세요.

temp.pem 중간 파일에는 어떠한 비밀번호 보호도 적용되지 않은 비공개 키가 포함됩니다. 따라서 릴리스 키를 생성할 때는 이를 신중히 폐기해야 합니다. 특히 GNUshred 유틸리티는 네트워크나 저널 파일 시스템에서 효과가 없을 수도 있습니다. 키를 생성할 때는 tmpfs 파티션과 같은 RAM 디스크에 위치한 작동 중인 디렉터리를 사용하여 중간 파일이 실수로 노출되는 상황을 예방할 수 있습니다.

이미지 파일 생성

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