APK Signingについて

はじめに(背景)

こんにちは。LINEでAIR GOの開発を担当しているKIM SEUNGHOONです。
最近、Googleは、Android 9(Pie)で、APK Signature Scheme v3を紹介しました。
これに伴い、AIR GOも、APK Signature Schemeを検知できるようになりましたので、このブログでは、APK Signing(APK Signature Scheme)が何かとAIR GOがどのように検知するかについて紹介したいと思います。
(AIR GOについては前回の記事を読んでいただければと思います。)

APK Signingについて

まず、この記事を読む前に、APK Signingについて簡単に説明したいとおもいますが
APK Signingとは、AndroidのAPKファイルに署名することを意味し、APK ファイルの完全性を保証する(1)、署名者 ( 開発者) を認証する(2)といった役割があります。そのため、Google PlayストアにAPKファイルを登録する際や、手元のAndroid端末にAPKファイルをインストールする際には必ず必要になります。
また、Google Playストアに登録したAPKファイルを更新する際には、前回と同じ証明書で署名する(3)必要もあります。
以下で説明するV2 Schemeは、V1 Schemeが(1)を部分的にしか保証がないため、登場したこと、
V3 Schemeは、この制約(3)をサポートするため、登場したことを念頭に入れていただければと思います。

APK Signingの種類 (Signing Schemes)

Androidでは、以下の3つのSigning Schemeが存在します。

V1 Scheme: JAR signing
https://source.android.com/security/apksigning/index.html#v1

V2 Scheme : APK Signature Scheme V2, Android 7から紹介されました。
https://source.android.com/security/apksigning/v2

V3 Scheme : APK Signature Scheme V3, Android 9から紹介されました。
https://source.android.com/security/apksigning/v3

最近のAndroid Studio(2018.12.27時点:~SDK 28.0.3)では、V1、V2 SchemeのみSigningできるようになっています。

[図1]

V3 SchemeでSigningするためには、以下のGitからソースをダウンロードしてapksignerコマンドをビルドする必要があります。

(apksignerコマンドの使い方については後記します。)https://android.googlesource.com/platform/tools/apksig/+/master

APK Signing BlockのScheme説明

V1 Schemeは、基本的なSigningであり、JAR Signingになります。JAR Signingの特徴としては、META-INF/MANIFEST.MFにリストされているエントリと、すべてのエントリが同じ署名であるかチェックします。

またMETA-INF/<signer>.SF(署名ファイル)、META-INF/<signer>.(RSA|DSA|EC) ファイルを用いてSigningを検証します。署名が無効な場合は、APKファイルのロードに失敗します。

V1 Schemeは、ファイルエントリに対するintegrityはチェックできますが、APKフォーマットに対するintegrityはチェックができません。(2017年には、 Janus vulnerability (CVE-2017-13156)として問題が公開されました。)

また、各ファイルエントリの署名をチェックするために、毎回メモリにロードするため、リソースの消費とAPKファイルのロードに時間が掛かる問題があります。

V2 Schemeは、V1の問題を解決するために、Android 7.0で紹介されました。

V1 Schemeがファイルエントリに対するintegrityをチェックしたことと異なり、V2 Schemeは、APKフォーマットに対するintegrityをチェックします。そのため、APKファイルの中にApk Signing Blockを生成します。

以下のように、V2 Schemeを用いてAPKファイルに署名を行うと、署名する前(Before signing)の3つの領域(Contents of Zip Entries、Central Directory、End Of Central Directory)に加えてAPK Signing Blockが追加されます。

[図2]

画像参照元:https://source.android.com/security/apksigning/v2

APK Signing Blockは、Central Directoryの前に位置し、既存のAPKファイルのフォーマットと異なるため、APKファイルのフォーマットをバイナリ形式で表示するEditorなどのAPK(ZIP)テンプレートで読み込んでも

正しくロードができません。今回は、010 EditorのZipテンプレートを修正して、以下の画像のようにAPK Signing Blockを確認しました。

[図3]

APK Signing Blockは、以下のような構成(Structure)になっています。

Structure memberMember TypeDescription
uiApkSignBlockSizeuintThe size of the APK Signing Block
uiApkSignBlockExtendSizeuintThe size of the space reserved to indicate the size of an APK Signing Block in case the size value does not fit in the uiApkSignBlockSizemember.
scheme[]structure arrayA list of scheme blocks (structures) an app is signed with
paddingscheme(Option)structureThe padding scheme to be used for aligning data entries before the Central Directory.
uiEndApkSignBlockSizeuintThe size of the APK Signing Block (The value must be equal to the uiApkSignBlockSize member)
uiEndApkSignBlockExtendSizeuintThe size of the space reserved to indicate the size of an APK Signing Block in case the size value is too big for the uiApkSignBlockSize member to represent. (The value must be equal to the uiApkSignBlockExtendSize member.)
signSignature[16]char arrayThe signature indicating the end of the signature. The value is fixed to "APK Sig Block 42"
Structure Member Member Type Description
uiSchemeBlockSize uint The size of the scheme block
uiSchemeblockExtendSize uint The size of the space reserved to indicate the size of the scheme block in case the size does not fit in the uiSchemeBlockSize member.
blockId uint The ID of the scheme used. For v2, this value is fixed to 0x7109871a.
uiSignBlockSize uint The size of a sign block including the signed data, signature and public key.
uiSignSize uint The size of the SignedData array
SignedData[uiSignSize] char array The signed data
uiSignatureSize uint The size of the signature
signatureData[uiSignatureSize] char The signature
uiPublickeySize uint The size of the public key
publickeyData[uiPublickeySize] char The public key


少し詳しく図で書きますとこのようになります。

[図4]
V2 Schemeの場合、blockIdは、0x7109871aの固定値になります。

Structure Member Member Type Description
uiSchemeBlockSize uint The size of the scheme block
uiSchemeblockExtendSize uint The size of the space reserved to indicate the size of the scheme block in case the size does not fit in the uiSchemeBlockSize member.
blockId uint The ID of the scheme used. For v2, this value is fixed to 0x7109871a.
uiSignBlockSize uint The size of a sign block including the signed data, signature and public key.
uiSignSize uint The size of the SignedData array
SignedData[uiSignSize] char array The signed data
uiSignatureSize uint The size of the signature
signatureData[uiSignatureSize] char The signature
uiPublickeySize uint The size of the public key
publickeyData[uiPublickeySize] char The public key

少し詳しく図で書きますとこのようになります。

[図5]
V3 Schemeの場合、blockIdは、0xf05368c0の固定値になります。APK Signature Scheme v2についてV2 Schemeでは、APKフォーマットに対してどのようにintegrityチェックを行うか説明すると以下の画像のように最終的にはDigestが生成され、上記の【V3 Schemeの構成】のDigestに入ることになります。このDigestがAPKファイルがインストールされる際にintegrityチェックに用いられることになります。

[図6]
画像参照元:https://source.android.com/security/apksigning/v2一つ目のChunksとは、APK Signing Blockを除いた3つの領域(Contents of Zip Entries、Central Directory、End Of Central Directory)をそれぞれ1MBずつ分割して生成したものです。最後のChunkは、1MB以下になる場合があります。二つ目のDigests of chunksとは、一つ目のそれぞれのChunkを、指定のDigest Algorithmによって計算したHash値をグルーピングしたものです。三つ目のDigestとは、二つ目のDigests of chunksをHashしたものになります。例えば、一つ目のChunksが100個で、Digest AlgorithmがSha256の場合、2つ目のDigests of chunksは、以下のように構成(はじめに0x5aが位置し、chunkの数である0x64が続いて、1つ目のそれぞれchunkの32bit hash値が順番に続きます。)され、これを元に三つ目のDigestの32bit Hashが生成されることになります。

[図7]V2 SchemeでSigningしたAPKファイルは、以下[図8]のような順番で署名が確認されインストールされることになりますが、


[図8]画像参照元:https://source.android.com/security/apksigning/v2

上記の[図4]をもう一度見ていただきたいですが、整理しますと、以下のように、APKフォーマットに対するintegrityチェックが行われAPKファイルがインストールされることになります。

① APK Signing Blockが存在するか確認します(正しくV2 Scheme blockが存在するか)
② public keyでSignedDataに対するsignatureのが正しいか確認します。
③ Signaturesのsignature algorithm IDsとSignedDataのDigestsからのsignature algorithm IDが同じか確認します。
④ APK Signing Blockを除いた3つの領域(Contents of Zip Entries、Central Directory、End Of Central Directory)に対して上記で説明したようにDigestを計算し、Digestと比較します。
⑤ certificatesのSubjectPublicKeyInfoがpublic keyと同じか確認します。
⑥ APKファイルをインストールします。APK Signature Scheme v3についてV3 SchemeのSigningは、V2 Schemeと同じく、APK Signing Blockが存在します。V3 SchemeでSigningされているAPKファイルは、以下のように、v3 Schemeがtrueになっていることが分かります。V3 Schemeを010 Editorを利用して、APK Signing Blockを確認すると、以下のようになります。上記で説明しましたが、V3 Schemeの場合、blockIdは、0xf05368c0の固定値になっていることが分かります。

 

[図9]

また、V2 Schemeと、V3 Schemeの構成(Structure)について比較したいと思います。

V2 Schemeの場合、以下の構成(Structure)になります。
 (https://android.googlesource.com/platform/tools/apksig/+/master/src/main/java/com/android/apksig/internal/apk/v2/V2SchemeSigner.java)

private static final class V2SignatureSchemeBlock {
 
 private static final class Signer {
  public byte[] signedData;
  public List < Pair < Integer, byte[] >> signatures;
  public byte[] publicKey;
 }
 
 
 private static final class SignedData {
  public List < Pair < Integer, byte[] >> digests;
  public List < byte[] > certificates;
  public byte[] additionalAttributes;
 }
 
}

V3 Schemeの場合、以下の構成(Structure)になります。(https://android.googlesource.com/platform/tools/apksig/+/master/src/main/java/com/android/apksig/internal/apk/v3/V3SchemeSigner.java)

private static final class V3SignatureSchemeBlock {
private static final class Signer {

public byte[] signedData;
public int minSdkVersion;
public int maxSdkVersion;
public List < Pair < Integer, byte[] >> signatures;
public byte[] publicKey;
}

private static final class SignedData {
public List < Pair < Integer, byte[] >> digests;
public List < byte[] > certificates;
public int minSdkVersion;
public int maxSdkVersion;
public byte[] additionalAttributes;
}

}

 

上記の構成(Structure)の違いは、minSdkVersion、maxSdkVersionの存在です。

V3 Schemeの場合、Signer classとSignedData classにminSdkVersion, maxSdkVersionが新しく存在することが分かりますが

これは、APKファイルのインストール対象となるプラットフォームのバージョン(SdkVersion)がminSdkVersion、maxSdkVersionに指定されてる範囲でなければ、このSigner構成(Structure)は無視するようにするためです。

proof-of-rotateについて

冒頭で説明しましたが、Google Playストアに登録したAPKファイルを更新する場合、新しいバージョンとなるアプリは、前回と同じ証明書で署名する(3)必要があります。

これは、署名鍵管理や紛失において問題になります。そのため、V3 Schemeでは、proof-of-rotateという新しい機能が追加されました。proof-of-rotate 構成(Structure)は、APK Signing Blockに存在します。

この機能は、複数の署名鍵を信頼することで、署名鍵の管理や紛失の問題を解決できるような仕組みです。

開発者は、過去のアプリに使った署名情報と更新対象である新しいアプリの署名情報を信頼し結びつける(SigningCertificateLineage[図14])ことができます。

以下のコマンドを例に、どのように署名情報を結びつけるか確認します。

コマンドの例1)$apksigner rotate –out /path/to/new/file –old-signer –ks release.jks –new-signer –ks release2.jks

このコマンドは、apksignerのrotateオプションで、それぞれのアプリの署名に用いた2つのkeystoreを信頼し署名情報を結びつけています。

–outオプションで指定したファイルがSigningCertificateLineageとなり、次のコマンドで署名する際に用いられます。

コマンドの例2)$apksigner sign –lineage /path/to/new/file –ks release.jks –nextsigner –ks release2.jks newapp.apk

このコマンドは、apksignerのsignオプションで、先ほど生成したファイル(SigningCertificateLineage)を用いてアプリに署名をします。

SigningCertificateLineageは、以下のような構成(Structure)になります。(https://android.googlesource.com/platform/tools/apksig/+/master/src/main/java/com/android/apksig/internal/apk/v3/V3SigningCertificateLineage.java

 

[図11]Nodeは、Signerの情報を格納しており、Parent(old-signer)とChild(new-signer)の結びつけ関係を確認することができます。最初のNodeは、Parent(old-signer)がないため、Fake Nodeとして生成されます。


[図12] signingCertificateLineage 0 : fake Node ( made By Parent Data )1 : Node DataV3 SchemeでSigningしたAPKファイルは、以下[図13]のような順番で署名が確認されインストールされることになりますが、


[図13]画像参照元:https://source.android.com/security/apksigning/v3上記の[図12]をもう一度見ていただきたいですが、整理しますと、以下のようにAPKフォーマットに対するintegrityチェックが行われAPKファイルがインストールされることになります。(V2 Schemeと比べると③と⑦が追加)
① APK Signing Blockが存在するか確認します(正しくV3 Scheme blockが存在するか)
② public keyでSignedDataに対するsignatureのが正しいか確認します。
③ SignedDataのminSDKversionとmaxSDKversionがAPKファイルのインストール対象となるプラットフォームのバージョンに指定されてる範囲であるか確認します。
④ Signaturesのsignature algorithm IDsとSignedDataのDigestsからのsignature algorithm IDが同じか確認します。
⑤ APK Signing Blockを除いた3つの領域(Contents of Zip Entries、Central Directory、End Of Central Directory)に対して上記で説明したようにDigestを計算し、Digestと比較します。
⑥ certificatesのSubjectPublicKeyInfoがpublic keyと同じか確認します。
⑦ SignedDataのAdditional attributesのproof-of-rotationの構成(Structure)が存在する場合、Certificateが正しいか比較します。(SigningCertificateLineageのNodeに含まれていないsigningCertがあると失敗します。)
⑧ APKファイルをインストールします。最後に最後に、AIR GOでどのようにAPK Signingの種類 (Signing Schemes)を検知しているか気になるところですが、AIR GOでは、以下の画像のように、APKファイルのSigningの種類 (Signing Schemes)が、V1 Schemeのみになっている場合、NORMAL レベルとして検知(表示)し、開発者が気づくようにしています。V1, V2 Schemeになっている場合は、検知しません。(AIR GOの検知レベルについては前回の記事をご参照ください。)


[図14]画像参照元:AIR GO(https://air.line.me/airgo/

Related Post