APK 서명 체계 v2

APK 서명 체계 v2는 APK의 보호된 부분에 발생한 모든 변경사항을 감지하여 인증 속도를 높이고 무결성 보장을 강화하는 전체 파일 서명 체계입니다.

APK 서명 체계 v2를 사용하여 서명하면 APK 서명 블록이 ZIP 중앙 디렉터리 섹션 바로 앞의 APK 파일에 삽입됩니다. APK 서명 블록 내에서는 v2 서명 및 서명자 신원 정보가 APK 서명 체계 v2 블록에 저장됩니다.

서명 전과 후의 APK

그림 1. 서명 전과 후의 APK

APK 서명 체계 v2는 Android 7.0(Nougat)에서 도입되었습니다. APK를 Android 6.0(Marshmallow) 및 기존 기기에 설치할 수 있도록 v2 체계로 서명되기 전에 JAR 서명을 사용하여 APK를 서명해야 합니다.

APK 서명 블록

v1 APK 형식과의 역호환성 유지를 위해 v2 및 최신 APK 서명은 APK 서명 블록 내에 저장됩니다. APK 서명 블록은 APK 서명 체계 v2를 지원하기 위해 도입된 새로운 컨테이너입니다. APK 파일에서는 APK 서명 블록이 파일 끝에 있는 ZIP 중앙 디렉터리 바로 앞에 위치합니다.

블록에는 APK에서 블록을 찾기가 수월한 방식으로 래핑된 ID 값 쌍이 포함되어 있습니다. APK의 v2 서명은 ID가 0x7109871a인 ID 값 쌍으로 저장됩니다.

형식

APK 서명 블록의 형식은 다음과 같습니다(모든 숫자 필드는 little-endian임).

  • 바이트 단위의 size of block(이 필드 제외)(uint64)
  • 길이가 접두사로 지정된 uint64 ID 값 쌍의 시퀀스
    • ID(uint32)
    • value(가변 길이: 쌍의 길이 - 4바이트)
  • 바이트 단위의 size of block—맨 첫 번째 필드와 동일(uint64)
  • magic 'APK Sig Block 42'(16바이트)

APK는 파일 끝에 위치한 중앙 디렉터리 기록의 ZIP 끝을 찾은 다음 기록에서 중앙 디렉터리의 시작 오프셋을 읽어 먼저 ZIP 중앙 디렉터리 시작 지점을 찾아서 파싱됩니다. magic 값은 중앙 디렉터리 앞에 APK 서명 블록이 있을 가능성이 높다는 내용을 확립하기 위한 빠른 방법을 제공합니다. 그러면 size of block 값은 파일에서 블록의 시작 지점을 효율적으로 가리킵니다.

ID를 알 수 없는 ID 값 쌍은 블록을 해석할 때 무시해야 합니다.

APK 서명 체계 v2 블록

APK는 한 개 이상의 서명자/ID에 의해 서명되며, 각 서명자/ID는 서명 키로 표현됩니다. 이 정보는 APK 서명 체계 v2 블록으로 저장됩니다. 각 서명자에 대해 다음 정보가 저장됩니다.

  • (서명 알고리즘, 다이제스트, 서명) tuple: 서명 인증을 APK 콘텐츠의 무결성 확인으로부터 분리하기 위해 다이제스트가 저장됩니다.
  • 서명자의 신원을 나타내는 X.509 인증서 체인
  • 키-값 쌍 형식의 추가 속성

각 서명자의 경우 제공된 목록의 지원되는 서명을 사용하여 APK가 인증됩니다. 서명 알고리즘을 알 수 없는 서명은 무시됩니다. 여러 개의 지원되는 서명을 접했을 때 어떤 서명을 사용할지는 각 구현에 의해 결정됩니다. 그러면 향후에 더욱 강력한 서명 수단을 역호환 가능한 방식으로 도입할 수 있습니다. 제안된 접근 방식은 가장 강력한 서명을 인증하기 위함입니다.

형식

APK 서명 체계 v2 블록은 ID가 0x7109871a인 APK 서명 블록 내에 저장됩니다.

APK 서명 체계 v2 블록의 형식은 다음과 같습니다(모든 숫자 값은 little-endian이며 길이가 접두사로 지정된 모든 필드에는 uint32가 길이로 사용됨).

  • 길이가 접두사로 지정된 signer의 길이가 접두사로 지정된 시퀀스:
    • 길이가 접두사로 지정된 signed data:
      • 길이가 접두사로 지정된 digests의 길이가 접두사로 지정된 시퀀스:
      • X.509 certificates의 길이가 접두사로 지정된 시퀀스:
        • 길이가 접두사로 지정된 X.509 certificate(ASN.1 DER 형식)
      • 길이가 접두사로 지정된 additional attributes의 길이가 접두사로 지정된 시퀀스:
        • ID(uint32)
        • value(가변 길이: 추가 속성 길이 - 4바이트)
    • 길이가 접두사로 지정된 signatures의 길이가 접두사로 지정된 시퀀스:
      • signature algorithm ID(uint32)
      • signed data를 통한 길이가 접두사로 지정된 signature
    • 길이가 접두사로 지정된 public key(SubjectPublicKeyInfo, ASN.1 DER 형식)

서명 알고리즘 ID

  • 0x0101—RSASSA-PSS 및 SHA2-256 다이제스트, SHA2-256 MGF1, 32바이트 솔트, 트레일러: 0xbc
  • 0x0102—RSASSA-PSS 및 SHA2-512 다이제스트, SHA2-512 MGF1, 64바이트 솔트 , 트레일러: 0xbc
  • 0x0103—RSASSA-PKCS1-v1_5 및 SHA2-256 다이제스트, 결정적 서명을 요구하는 빌드 시스템에 사용
  • 0x0104—RSASSA-PKCS1-v1_5 및 SHA2-512 다이제스트, 결정적 서명을 요구하는 빌드 시스템에 사용
  • 0x0201—ECDSA 및 SHA2-256 다이제스트
  • 0x0202—ECDSA 및 SHA2-512 다이제스트
  • 0x0301—DSA 및 SHA2-256 다이제스트

위의 모든 서명 알고리즘은 Android 플랫폼에 의해 지원됩니다. 서명 도구는 알고리즘의 하위 집합을 지원할 수 있습니다.

지원되는 키 크기 및 EC 곡선:

  • RSA: 1024, 2048, 4096, 8192, 16384
  • EC: NIST P-256, P-384, P-521
  • DSA: 1024, 2048, 3072

무결성 보호 콘텐츠

APK 콘텐츠 보호를 위해 APK는 4개의 섹션으로 구성됩니다.

  1. ZIP 항목 콘텐츠(오프셋 0부터 APK 서명 블록 시작 지점까지)
  2. APK 서명 블록
  3. ZIP 중앙 디렉터리
  4. 중앙 디렉터리의 ZIP 끝

서명 후의 APK 섹션

그림 2. 서명 후의 APK 섹션

APK 서명 체계 v2는 섹션 1, 3, 4 및 섹션 2 안에 포함된 APK 서명 체계 v2 블록의 signed data 블록을 보호합니다.

섹션 1, 3, 4의 무결성은 signed data 블록에 저장된 콘텐츠의 다이제스트 1개 이상에 의해 보호되며 블록은 1개 이상의 서명에 의해 보호됩니다.

섹션 1, 3, 4에 관한 다이제스트는 다음과 같이 2단계의 머클 트리와 유사한 방식으로 계산됩니다. 각 섹션은 연속적인 1MB(220바이트) 청크로 분할됩니다. 각 섹션의 마지막 청크는 더 짧을 수도 있습니다. 각 청크의 다이제스트는 바이트 0xa5의 연결, 바이트 단위의 청크 길이(little-endian unit32) 및 청크 콘텐츠에 관해 계산됩니다. 최상위 다이제스트는 바이트 0x5a의 연결, 청크 수(little-endian unit32) 및 APK에 청크가 표시되는 순으로 청크 다이제스트의 연결에 관해 계산됩니다. 다이제스트는 동시에 로드하여 계산을 가속화할 수 있도록 청크화된 방식으로 계산됩니다.

APK 다이제스트

그림 3. APK 다이제스트

섹션 4의 보호(ZIP 중앙 디렉터리의 끝)는 ZIP 중앙 디렉터리의 오프셋을 포함하는 섹션에 의해 복잡해집니다. 예를 들어 새 서명이 추가되어 APK 서명 블록의 크기가 변하면 오프셋이 변경됩니다. 따라서 ZIP 중앙 디렉터리의 끝에 대해 다이제스트를 컴퓨팅할 때에는 ZIP 중앙 디렉터리의 오프셋을 포함하는 필드가 APK 서명 블록의 오프셋을 포함하는 것으로 간주해야 합니다.

롤백 보호

공격자는 v2로 서명된 APK 인증을 지원하는 Android 플랫폼에서 v2로 서명된 APK를 v1으로 서명된 APK로 인증하려고 시도할 수 있습니다. 이러한 공격을 줄이기 위해 v1은 물론 v2로도 서명된 APK에는 X-Android-APK-Signed 속성이 META-INF/*.SF 파일의 기본 섹션에 포함되어야 합니다. 속성 값은 쉼표로 구분된 APK 서명 체계 ID의 집합입니다. 이 체계의 ID는 2입니다. v1 서명을 인증할 때 APK 인증기는 인증기가 이 집합에서 선호하는 APK 서명 체계(예: v2 체계)의 서명이 없는 APK를 거부해야 합니다. 이 보호 조치는 콘텐츠 META-INF/*.SF 파일이 v1 서명으로 보호된다는 사실에 의존합니다. JAR로 서명한 APK 인증에 관한 섹션을 참조하세요.

공격자는 APK 서명 체계 v2 블록에서 더 강한 서명을 제거하려고 시도할 수 있습니다. 이러한 공격을 줄이기 위해 APK가 서명될 때 사용된 서명 알고리즘 ID 목록은 각 서명에 의해 보호되는 signed data 블록에 저장됩니다.

인증

Android 7.0 이상에서는 APK 서명 체계 v2+ 또는 JAR 서명(v1 체계)에 따라 APK를 인증할 수 있습니다. 기존 플랫폼은 v2 서명을 무시하고 v1 서명만 인증합니다.

APK 서명 인증 프로세스

그림 4. APK 서명 인증 프로세스(새 단계는 빨간색으로 표시됨)

APK 서명 체계 v2 인증

  1. APK 서명 블록을 찾아 다음을 인증합니다.
    1. APK 서명 블록의 두 가지 크기 필드에는 동일한 값이 포함됩니다.
    2. ZIP 중앙 디렉터리 바로 뒤에 ZIP 중앙 디렉터리 끝이 위치해야 합니다.
    3. 중앙 디렉터리의 ZIP 끝에 붙는 추가 데이터가 없습니다.
  2. APK 서명 블록 내에서 첫 번째 APK 서명 체계 v2 블록을 찾습니다. v2 블록이 있는 경우 3단계로 넘어갑니다. 아니면 v1 체계를 사용하여 APK 인증으로 돌아갑니다.
  3. APK 서명 체계 v2 블록의 각 signer의 경우 다음을 실행합니다.
    1. signatures에서 지원되는 가장 강력한 signature algorithm ID을 선택합니다. 강도 순서 지정은 각 구현/플랫폼 버전에 따라 결정됩니다.
    2. public key를 사용하여 signatures에서 signed data에 상응하는 signature를 확인합니다. 이제 signed data를 안전하게 파싱할 수 있습니다.
    3. digestssignatures의 순서가 지정된 서명 알고리즘 ID 목록이 동일한지 확인합니다. 이는 서명 제거/추가를 방지하기 위함입니다.
    4. 동일한 다이제스트 알고리즘을 서명 알고리즘에서 사용되는 다이제스트 알고리즘으로 사용하여 APK 콘텐츠 다이제스트를 계산합니다.
    5. 계산된 다이제스트가 digests의 상응하는 digest와 동일한지 확인합니다.
    6. certificates의 첫 번째 certificate의 SubjectPublicKeyInfo가 public key와 동일한지 확인합니다.
  4. 1개 이상의 signer가 발견되었고 발견된 각 signer에 3단계가 성공했으면 인증이 성공한 것입니다.

참고: APK는 3단계나 4단계에서 실패가 발생할 경우 v1 체계를 사용하여 인증하면 안 됩니다.

JAR로 서명한 APK 인증(v1 체계)

JAR로 서명한 APK는 표준 서명 JAR입니다. 여기에는 META-INF/MANIFEST.MF에 나열된 항목이 정확하게 포함되어야 하며 모든 항목은 동일한 서명자 집합으로 서명되어야 합니다. 무결성은 다음과 같이 확인됩니다.

  1. 각 서명자는 META-INF/<signer>.SF 및 META-INF/<signer>.(RSA|DSA|EC) JAR 항목으로 표시됩니다.
  2. <signer>.(RSA|DSA|EC)는 서명이 <signer>.SF 파일을 통해 확인된 SignedData 구조가 있는 PKCS #7 CMS ContentInfo입니다.
  3. <signer>.SF 파일에는 META-INF/MANIFEST.MF의 전체 파일 다이제스트와 각 META-INF/MANIFEST.MF 섹션의 다이제스트가 포함됩니다. MANIFEST.MF의 전체 파일 다이제스트가 인증됩니다. 인증되지 않으면 각 MANIFEST.MF 섹션의 다이제스트가 대신 인증됩니다.
  4. META-INF/MANIFEST.MF에는 각 무결성 보호 JAR 항목에 대해 상응하는 이름이 지정된 섹션이 포함됩니다. 섹션에는 항목의 압축 해제된 콘텐츠의 다이제스트가 포함됩니다. 이 모든 다이제스트가 인증됩니다.
  5. APK에 MANIFEST.MF에서 나열되지 않고 JAR 서명의 일부가 아닌 JAR 항목이 포함되어 있다면 APK 인증은 실패합니다.

따라서 보호 체인은 <signer>.(RSA|DSA|EC) -> <signer>.SF -> MANIFEST.MF -> 각 무결성 보호 JAR 항목의 콘텐츠입니다.