Android・iOSアプリを配信する時の署名について整理する

「どうすればいいのか」という操作手順は探すと結構見つかるんですけど、

「それはなんのためにやるのか」というところがピンときてないと、

何かトラブった時に自分で対処できなかったり、

そもそも何をやってるかわからずに作業するのが気持ち悪かったりするので

それをなんとかする記事です。

間違いがあったらコメントで教えていただけると大変ありがたいです。


なお、「デジタル署名」「公開鍵」「秘密鍵」「証明書」「認証局」などの言葉については既知とします。


#そもそも何がしたいのか

両OSで共通してやりたいことは

**配布するアプリにデジタル署名を施す**

ということです。


デジタル署名が施してあれば、アプリを受け取る人は、手元にある「公開鍵(検証鍵)」を使って

 - アプリがその公開鍵と対応する秘密鍵を持つ人によって作られたこと
 - その秘密鍵で署名された後でアプリが改竄されていないこと


を確認できます。

なお、ユーザーはその公開鍵をいつ入手するかというと、

最初にそのアプリをインストールした時に一緒についてくる証明書に入ってる

ということになります。

##署名で防げること(Android)

Androidの場合、署名の主な目的は「アプリを不正にアップデートされることを防ぐ」ことです。

こちらから引用します。(閲覧・引用日　2019年11月12日)
https://support.google.com/googleplay/android-developer/answer/7384423?hl=ja

>Android アプリは秘密鍵で署名されます。アプリのアップデートが信頼できるものであることを保証するために、秘密鍵はそれぞれ公開鍵証明書に関連付けられています。デバイスやサービスはその公開鍵証明書を使って、アプリが信頼できる提供元によるものであることを確認できます。デバイスがアプリの更新を許可するのは、すでにそのデバイスにインストールされているアプリとアップデートで署名が一致する場合のみです。

既にインストールしたアプリと同じ製作者によるアップデートしか受け付けない、ということです。

###この署名で防げないこと

Androidアプリには製作者の署名と、公開鍵証明書(公開鍵自体もそこに入っている)が含まれていますが

その「証明」を行ったのはアプリ制作者自身です。証明書に入っている公開鍵に対応する秘密鍵を使って、その公開鍵に署名したものです。

これを[自己署名証明書](https://ja.wikipedia.org/wiki/%E8%87%AA%E5%B7%B1%E7%BD%B2%E5%90%8D%E8%A8%BC%E6%98%8E%E6%9B%B8)と言います。

ということは、その公開鍵が正当な製作者のものであるという「第三者に依る保証」がありません。

攻撃者が偽アプリを作って、攻撃者自身が作った秘密鍵で署名し、自己署名証明書を同梱しても、受け取る人は正当な製作者によるものと区別がつきません。

なので、アップデート時以外、つまり、アプリを最初にデバイスにインストールする時(一度消して再びインストールする場合も含む)に、アプリそのものを署名や証明書ごとすり替えられる可能性が残ります。

ここは私もよくわかっていないところなのですが、

Play Storeとの通信がHTTPSで保護されている以上、そんな攻撃は考えられない、ということですかね？🤔

Play Store以外から入手する場合は自己責任でいいと思いますけども。

##署名で防げること(iOS)

iOSアプリの場合は、自己署名証明書ではなく、Appleが発行した証明書が付きます。

こちらのページから引用します。(閲覧・引用日　2019年11月12日)
https://developer.apple.com/jp/support/code-signing/

>Appにコード署名をすると、そのAppが特定の発行元から提供されており、最後に署名した時点から改変されていないことをユーザーに保証できるようになります。MacやiOS Appを、ストアサービスで利用、開発やテスト用にiOSデバイスにインストール、App Storeに登録するには、Appleが発行した証明書で署名する必要があります。

アップデート時に限らず、最初のインストール時から、正当な製作者によるアプリであることが保証される、ということですね。

開発者からすると、Appleに公開鍵証明書をもらうという一手間が増えます。

###「証明書で署名」？

…ところで。

私はこの引用に出てくる「証明書で署名」という表現が気になります。

これは「『証明書に含まれる公開鍵に対応する秘密鍵』で署名する」という意味に捉えればいいんですかね？

証明書に秘密鍵は含まれていませんから、「証明書で署名」と言われると「署名する時に証明書は関係ないじゃん」と思ってしまうんですが…🤔



# 共通の基本的な手順

ということで、デジタル署名を施してアプリをアップロードする大まかな手順は次のようになります。

1. 秘密鍵・公開鍵のペアを作成する
2. (Android)公開鍵の自己署名証明書を作る/(iOS)公開鍵をAppleに登録し、証明書をもらう
3. 秘密鍵でアプリに署名し、証明書を同梱する
4. アプリをストアにアップロード
5. 秘密鍵と証明書のセットをどこかに保管しておく

となります。4と5はどっちが先でもいいです。

これがピンと来ていると、実際の作業も見通しが良くなります。

でもこれはあくまで基本的な流れで、具体的な流れはもっと細かくいろいろあります。

# Androidの手順

ではもう少し具体的な流れを見てみましょう。

「何のソフトを開いて、どこをクリックして、何のコマンドを実行して…」というところまでは具体化しません。あくまで「この操作はこういうことをやっている」という解説です。

## 1. 2. 秘密鍵・公開鍵のペアを作り、自己署名証明書を作る

(アプリ開発が2つ目以降で、前回と同じ鍵を使う場合は、このステップは飛ばせます)

```
keytool --genkey (あと略)
```

このコマンドで、自己署名証明書の作成まで一度に行われます。


>-genkeypair
(中略)
鍵のペア(公開鍵および関連する非公開鍵)を生成します。公開鍵はX.509 v3自己署名証明書でラップされます。

(https://docs.oracle.com/javase/jp/8/docs/technotes/tools/windows/keytool.html より引用 2019年11月12日)

ちなみに多くの解説では`genkey`とされているところは、現在(2019年11月12日)では

```
--genkeypair
```

を使うべきとされているようです。機能は同じ。

秘密鍵も、公開鍵を含んだ自己署名証明書もまとめて一つの`keystore`に保存されます。拡張子は`.keystore`か`.jks`にすることが多いようです。

開発したアプリが2つ目以降の場合、前回と同じ鍵を使っても構いません。

## 3. アプリに署名し、証明書を同梱

いくつか方法があります。

 - `zipalign`→`apksigner`
 - `jarsigner`→`zipalign`
 - `key.properties`や`build.gradle`を適切に書いてビルド時に自動で処理

と言ったところでしょうか。

`zipalign`は署名とは関係ない操作ですが、署名方法によってタイミングが変わってくるので書き足しておきました。

## 4. アップロード

Play Store Console からアプリの作成を行い、ビルドしたアプリ本体をアップロードします。

リリースするためには説明文を書いたり対象年齢を設定したりいろいろやることがありますがこの記事では割愛します。

## 5. 秘密鍵と証明書のセットを保管

先程作った`keystore`ファイルをどこかに保管すればOK。

## 「Google Play アプリ署名」を使う場合

GooglePlayアプリ署名を使う場合は、アプリ本体への署名をGoogleが代わりに行います。その際の鍵ペアと自己署名証明書はGoogleが勝手に作ります。

また、Play Storeにアップロードするものが、apkファイルではなく`Android App Bundle`になります。

(`Android App Bundle`ではなくapkファイルをアップしつつ、「Google Play アプリ署名」を使用することもできるのかどうかはよくわかりません🤔)

ユーザーは、アプリの製作者が変わっていないことをGoogleが保証してくれるので、安心してアップデートできます。

あとは、製作者がアップデート版をアップロードする時に、Googleに対して「前と同じ製作者ですよ」ということを証明しないといけません。

なので結局、製作者も毎回署名をする必要があります。この時の署名に使う鍵は「アップロード鍵」と呼ばれます。

とはいえ署名手順はそこまで大きく変わりません。使った鍵がアップロード鍵と呼ばれるようになるだけです。

アップロード鍵も、複数のアプリで同じものを使用してもかまいません。

##既にapkファイルをアップロードしたアプリを、 「Google Play アプリ署名」を使うように変更する場合



この場合、今後のアップデートでアプリに署名を施すのはGoogleになるわけですが、

ユーザー視点で考えると、アプリに施された署名はアップデート時も変わってはいけないので、

製作者は、**これまで使っていたアプリ署名用の鍵をGoogleに渡し、使ってもらう**必要があります。

つまりこれって**秘密鍵を渡す**ことになります。

これは結構珍しい操作な気がしますがどうなんでしょう。

そして、今後自分が使うアップロード鍵を用意して、それもGoogleに伝えます。こちらは秘密鍵を渡してはいけません。公開鍵(を含んだ自己署名証明書)だけで十分ですね。

### 注意　アップロードキーを伝えるタイミングがわかりにくい

アップロード鍵を伝える操作のタイミングがわかりにくく、誤ってその工程を飛ばしてしまうと、先に渡した秘密鍵がそのままアップロードキーとして設定されてしまいます！

この場合、アップロード鍵と秘密鍵が同じ鍵になります。それでもアプリリリースはできますが、それが嫌な場合は気をつけてください。

確か、秘密鍵を提出したタイミングで、「アップロード鍵を設定する(推奨)」というリンクが出現したと思いますので、見落とさないようにしましょう。

私は見落として、Googleのサポートに連絡してアップロードキーをリセットしてもらいました。

# iOSの手順

続いてiOSです。こちらも「つまりどういうことをしているのか」の解説にとどめ、具体的な操作手順は解説しません。

実際の手順はこちらのサイトがわかりやすいです。
https://i-app-tec.com/ios/app-release.html

## 1. 秘密鍵・公開鍵のペアを作成する

今回は、いきなり「キーのペアを作る」という操作はしません。ではどうするのか。

この後のステップで、「Appleに証明書をお願いする」という部分があります。こちらで作ったキーペアに対して、署名してもらえるようにAppleにお願いするということです。

一般的に、認証局に証明書をお願いする場合は「証明書署名要求(certificate signing request, CSR)」と呼ばれるものを作成し、それを認証局に送ります。

[証明書署名要求(Wikipedia)](https://ja.wikipedia.org/wiki/%E8%A8%BC%E6%98%8E%E6%9B%B8%E7%BD%B2%E5%90%8D%E8%A6%81%E6%B1%82)

認証してもらえた場合、証明書が送り返されたり、ダウンロードできるようになる、という流れです。

iOSアプリを作る際は、お手元のMacに最初からインストールされているKeychainAccessというアプリケーションを使ってCSRを作成します。


Keychain Accessを使ってCSRを作成する際に、秘密鍵・公開鍵のペアも自動で作成されます。

事前に作っておく必要はないということです。

ステップが削減されるのは良いのですが、私ははじめ「鍵を作らずにCSRを作るってどういうことだ…」と混乱しました。


## 2. 公開鍵をAppleに登録し、証明書をもらう

Apple Developerにログインし、Certificatesの所から操作を行います。

その際に、先程作ったCSRを指定する行程があります。

これが完了すると、証明書がダウンロード可能になります。

ダウンロードしてKeychain Accessに追加しましょう。これをしないと、Xcodeからこの証明書にアクセスできません。


###有効期限は1年

作った証明書の有効期限は1年ですから、切れる前に作り直す必要があります。

その場合はまたCSRの作成から行います。

署名に使う鍵も変更されるわけですね。この点はAndroidの場合と違いますね。アップデート時に製作者が変わっていないことを、「署名が変わっていないこと」ではなく、Appleの認証によって保証するわけですね。

作り直したら、Provisioning Profile(後述)を編集して、紐付ける証明書を切り替えておきましょう。

###証明書はCSRを作ったMacに紐付いている

Macを買い替えたりした場合は紐付けをし直す必要があります。

やり方はここでは解説しないので調べてください🙏


## 3. 4. 秘密鍵でアプリに署名し、証明書を同梱し、アップロードする

### Apple Developerでの作業
まずApple Developerのサイト上でApp IDの登録をします。

その後、Provisioning Profileというものを作ります。これもApple Developerのサイト上です。

Provisioning Profileは、次の3種類の情報の組み合わせを表したものです。

 - Certificate(証明書のこと)
 - App ID
 - テスト用端末のID

この情報があることで、Appleは「どのアプリとどの証明書をくっつけて配信すればいいのか」を把握できます。

Certificate(証明書)の有効期限が1年で切れますので、その前に新しい証明書を作って紐付けておきましょう。

ちなみに、Provisioning ProfileとApp IDの紐付けは一対一対応ではないそうです。どういうケースで一対一対応ではなくなるのかコレガワカラナイ🤔


### Xcodeでの作業

アプリのプログラムそのものは、Xcodeを使ってアップロードします。

その際に、先程作ったProvisioning Profileを指定します。そこに証明書の情報も紐付いているので、先程作った証明書を使ってくれるようになります。

もろもろ調整してアップロードします。

余談ですが、この部分でかなりいろんなエラーに悩まされると思います。私は「アイコンに透明度が設定されている」というエラーが一番多かったです。その場合は透明度を抜いた画像を作り直して設定し直します。

### App Store Connectでの作業

最後にApp Store Connectのサイト上で、新しいアプリを登録し、スクリーンショットや説明文など必要な情報を登録します。先程作ったApp IDとの紐付けも行います。

Androidで言うところの、Play Store Console上での作業に相当します。

## 5. 秘密鍵と証明書のセットを保管

Keychain Accessをまた開いて、先程ダウンロードした証明書と、それに対応した秘密鍵をセットにした`p12`ファイルというものを作成できます。それを保管すればOK。

CategoryはMy Certificateを選んでおかないと、p12を選択できませんので注意。

Keychain AccessをまるごとiCloudで管理しておくという手もありますね。

# おわり

この記事は以上です。

署名まわりの話はややこしいですが、この記事の内容を理解しておけば、何が何だかわからずに作業するよりもずっと見通しが良くなると思います。

もし間違いがありましたら教えていただけると幸いです。また、疑問点として残した所もご存知の方がおられましたら教えていただけると幸いです。