おでーぶでおでーぶ

いろいろ書く。いろいろ。

v1 scheme 互換の v2 scheme 署名 apk を手動で作成する

source.android.com

モチベーション

今更なんでそんなことを手動で・・・と思うかもしれませんが、AndroidStudio 3.5 かつ minSdkVersion 24 以上の場合、署名 scheme が自動で判定されるようになりました。

v2 schemeAndroid N、つまり API 24 から動作します。AS 3.5 では Android N 以上をターゲットとするアプリに v1 scheme 互換を維持する必要はないとして、自動的に v1 scheme を行わないようになりました。それが例え v1SigningEnabled true && v2SigningEnabled false であったとしても、です。*1もっと言うと GUI 経由でも無視されます。*2

v2 scheme での署名を施す場合、apk 内部を変更したあとは再度署名を行う必要があります。*3 殆どの Android 開発者には関係のない話だと思いますが、諸般の事情により v1 scheme による署名でないといけない悲しい人類が存在します。そうです、僕です。

余談ですが、この仕様に対する疑念は IssueTracker にすでに上がっており、「仕様である」という一時対応から一転して再アサインがなされています。 https://issuetracker.google.com/issues/136691451

2019/09/27 追記 ↑は 3.5.1 で fix されたようです。

v1 scheme 互換を維持した v2 scheme 署名を行う方法(手動)

  • unsigned apk を作成する
  • jarsigner による署名を行う
  • zipalign による最適化
  • apksigner による v1 互換を維持した v2 署名

TL;DR

SigningConfig に明示的に null を渡したのち、以下を埋めつつ実行する。

export PATH="<android sdk home>/build-tools/<build tools version>:$PATH" # 28.0.3 で動作確認済
which jarsigner zipalign apksigner # すべて解決されることを確認してください。
keystore_path="<keystore のパス>"
keystore_alias="<keystore の alias>"
unsigned_apk_path="<署名無し apk のパス>"
output_apk_path="<最終出力先の apk のパス>"
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore "$keystore_path" "$unsigned_apk_path" "$keystore_alias"
zipalign -f -v 4 "$unsigned_apk_path" "$output_apk_path"
apksigner sign --ks "$keystore_path" --v1-signing-enabled=true --v2-signing-enabled=true "$output_apk_path"
apksigner verify --min-sdk-version=23 --verbose "$output_apk_path" # v1/v2 ともに verified であることを確認してください。

unsigned apk を作成する

BuildType#setSigningConfig(null) を呼びましょう。

jarsigner による v1 scheme 署名

v1 scheme は要するに jarsigning です。jarsigner を使って SHA1withRSA で署名します。SHA256withRSA で署名する場合は API 18 以下で動作しません。*4

$ jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore "<a file path of keystore>" "<a file path of unsigned apk := output path>" "<key store alias>"

なお上記コマンドの場合、署名 apk は上書き保存です。

zipalign による最適化

本記事のように jarsigning を行う場合、zipalign は jarsigner より あとに 行う必要があります。

zipalign -f -v 4 "<a file path of v1-signed apk>" "<a file path of v1-signed and zipaligned apk>"

zipalign をなぜ行う必要があるかについてはAndroidにおいてなぜzipalignをやる必要があるのかに譲ります。

v2 scheme の署名を必要としない場合、この時点で v1 scheme による署名 apk が生成されています。

apksigner による v1 互換を維持した v2 署名

apksigner を使って、v1 scheme 互換を維持したまま、v2 scheme による署名を施します。

apksigner sign --ks "<a file path of keystore>" --v1-signing-enabled=true --v2-signing-enabled=true "<a file path of v1-signed and aligned apk := output path>"

なお上記コマンドの場合、署名 apk は上書き保存です。

これで v1 scheme 互換の v2 scheme 署名 apk が完成です。最後に以下のコマンドを実行して確認しておきましょう。

apksigner verify --min-sdk-version=23 --verbose "<a file path of apk>"

おまけ : なぜ apksigner verify --min-sdk-version=23 なのか

ワークアラウンドです。まず以下のコマンドで apksigner のバージョンが確認できますが、このバージョンは一切信用できないことを知りましょう。

apksigner version

apksigner はコマンドラインツールとしての interface であり、内部では apksig と呼ばれるプロジェクトが署名挙動を担っています。したがって、apksigner のバージョンとは interface のバージョンであり、挙動のバージョニングを指しません。具体的な例としてあげるならば、BuildTools version 27.0.3 と 28.0.3 に含まれる apksigner はともにバージョン 0.8 ですが、apksig は変更されています。*5

この問題は apksigner を使う一部の人間にとって非常にツラいものです。そうです、僕が辛かったんです。

例えば apksigner は AndroidManifest に記述された minSdkVersion をデフォルト利用し、署名実行時に v1 scheme を利用するかどうかなどを判定します。*6必要のないことはしなくて良いという挙動については make sense という感じですね。さて、verify コマンドを見てみましょう。

apksigner verify --verbose "<a file path of apk>"

verify コマンドではその apk が v1 scheme として verified であるかどうか、v2 scheme として verified であるかどうかを確認できます。これは minSdkVersion に依らず、apk の状態を検証するものです。いや、でした

現在*7、このコマンドも AndroidManifest に記述された minSdkVersion を見るようになってしまったようです。結果、v1 scheme 互換の v2 scheme 署名 apk (minSdkVersion >= 24) は v1 scheme unverified 扱いになります。正確に署名状態が知りたい方は --min-sdk-version=23 を渡すようにしましょう。

おまけのおまけ

署名に興味のある方は src/apksigner/java/com/android/apksigner/help_sign.txt - platform/tools/apksig - Git at Google 辺りから追っておきましょう。v3 scheme (Android Pから) による key rotate support なども知っておくと面白いですね。

*1:この場合は app-debug.apk などの通常名称のまま unsigned apk になります。

*2:同じオプションをいじってるだけなので、それはそうという感じですが。

*3:つまり v2 scheme で署名済み apk を修正した場合は署名の妥当性が崩れる仕様であり、v2 scheme がセキュアと呼ばれる仕様の1つです。

*4:minSdkVersion 19以上にしたいですね。

*5:なお現在は 0.9 に上がっていることがリポジトリの最新リビジョンで確認できます。

*6:ただしオプションで明示的に渡すことで、上書きできます。

*7:BuildTools version 28.0.3