第 21 章 セキュリティ 著:中西良明 21-1 Android アプリの セキュリティの基本 KEYWORD LESSON 開発時点からしっかりとセキュリティを考えておくことで、 アプリのリリース後に発生するトラブルのリスクを減らすこと ができます。 本節では、まずセキュリティの基本的な考え方 permission android:exported 暗号 共通鍵暗号 公開鍵暗号 を学び、その後、自作のAndroidアプリからのデータ流出を apkファイル 防ぐ方法を学びます。 Context SharedPreferences ContentProvider この節を学ぶとできること ・セキュリティの基本理解 ・ Androidアプリ内部データを外部アプリから守る方法を知る ・アプリの解析方法と攻撃者の手口を知る 42 著:中西良明 21-1-1 アプリのセキュリティとは何か? セキュリティと聞いたとき、いったいどんなことを考えますか? ウィルスやトロイの木馬 などによって、データを盗まれることや破壊されることに対する防御を思い浮かべるの ではないでしょうか。 第 当然それらも重要なセキュリティの1つです。しかし、アプリを開発する際、セキュリ 21 ティに気をつけるというのは、 アプリの持つデータをマルウェアに盗まれたり破壊された セ キ ュ リ テ ィ 章 りしないようにすることだけではありません。 アプリ開発においてセキュリティを考えるとい うのはどういうことかを学ぶ前に、まずはこれまでにあったセキュリティ問題の事例につ いて振り返ります。 アプリのセキュリティ問題事例 Androidアプリにおいて、過去に様々なアプリがセキュリティ問題を起こしてきまし た。ここではその一部の概要を紹介します。 (1)浮気防止アプリで、彼氏 (彼女) のスマートフォンの利用履歴を収集し、自 分の端末に対して送信していた (2)SNSアプリ、チャットアプリなどで、ユーザーの電話帳情報を収集しサー バーに勝手に送信していた (3)アプリからSDカード (拡張ストレージ)上に暗号化せずにデータを保存し ていた (4)データをアプリ外から直接アクセスできないところに置いていたが、デー タ管理機能のアクセス制限に不備があり、そのデータ管理機能を経由す ることで誰でもアクセスできる状態になっていた (5)サーバーとの暗号通信接続手順の不備により、サーバーとの通信を傍受 することが可能になっていた (6)サーバーとの暗号通信接続時に、弱い暗号方式が選ばれていた (7)Twitterクライアントアプリで、他のアプリから勝手に画像アップロード機 能を呼び出せるようになっていた (8)ログから、ユーザーの個人情報 (アカウント、メールアドレスなど) を傍受 することが可能になっていた (1)と(2)は、アプリが意図的に持っていた機能が利用者から不評を買い、社会 問題化した事例です。(3)∼(8)は、開発者が意図しない問題がアプリ内にあった 事例です。 前者は仕様に、後者は実装にセキュリティ問題があったと言えます。仕様と実装 のセキュリティについての考え方を理解しておくことは重要です。 43 仕様におけるセキュリティ アプリの「仕様」 とは、そのアプリがどのような機能をユーザーに提供するかという ことです。たとえば、以下のような機能を持ったアプリを考えてみましょう。 ・ 浮気防止のため、彼氏 (彼女) のスマートフォン利用履歴を収集し、自分の 端末に対して送信する ・ スマートフォンを利用する子供がトラブルに巻き込まれないようにするた め、監視ソフトをインストールした端末を与える →子ども向けに機能限定されたスマートフォンで、通信のフィルタリング やコミュニケーション相手を制限する →会社から従業員に、通信接続先を制限したスマートフォンを与える ・ ユーザーの家族や友人に招待を送るため、電話帳情報を取得してサー バーに対して送る →ユーザーの許可をもらってから情報を収集する →ユーザーには知らせず電話帳情報を収集する これらのアプリ (機能) は問題がないでしょうか。利用履歴の収集や、電話帳情 報の取得は、過去の事例では「無断で」おこなうことに問題があるとみなされまし た。浮気防止アプリの事例では、ユーザーが相手の了承を得た上で、相手の端 末にインストールをすることを建前としていました。しかし、実際には相手の同意を得 ずにこっそりとインストールする場合が大半であることが想定されたため、ネット上での 議論は炎上しました。結果、本格的なサービス開始の前にアプリの配布を終了す ることになりました。無断での電話帳収集は、現在では非常に有名になったサービ スがユーザー獲得のためにおこなっていた施策の1つです。現在ではそのような動 作をしないよう修正されています。 では、ユーザーに対して十分に説明した上で同意を取り、電話帳情報を収集す ることは問題ないのでしょうか。これについてはグレーゾーンであると考えられています (セキュリティに厳しい方は問題であると考えています)。電話帳情報の収集につい て、その電話帳を持っている人の同意を得ているとしても、電話帳に含まれる情報 は、その電話帳の持ち主のものではなく、電話帳に載せられている人の情報である ためです。 なお、子供用のスマートフォンに監視ソフトをインストールすることや、会社から従業 員に、通信接続先を制限したスマートフォンを与えるといったことは、機能的には浮 気防止の場合とそれほど違いがありませんが、社会的には問題とされない (されにく い)例であると考えられます。 というのも、親子や会社と従業員の間には、管理する側 とされる側という関係が存在するためです。 開発に時間や費用をかけたにもかかわらず、アプリやサービスを終了せざるを得 44 なくなることは非常に大きな無駄となります。よって、アプリのアイディアを考える際に、 そのアイディアは法的に問題がないか、また社会に受け入れられるものかを考えてお くことは非常に重要です。 パーミッション宣言 第 21 章 Androidには、アプリがどんな権限を利用するかを宣言する仕組みがあります。 セ キ ュ リ テ ィ それがパーミッションです。これはアプリがどのような仕様を持っているかを、間接的 な形で宣言しているとも考えられます。 アプリ開発の演習の中で、 「AndroidManifest.xml」に以下のように記述した ことがあるはずです。 リスト1 <uses-permission android:name="android.permission.INTERNET" /> 「このアプリはインターネットとの通信をおこなう権限を使います」 という意味です。 パーミッションのリファレンス これを宣言せずにアプリの中でインターネットとの通信を実行しようとすると、 「Secur reference/android/Manifest.pe ityException」 という例外が発生して通信に失敗します。 http://developer.android.com/ rmission.html ほかにもいろいろなパーミッションがありますが、詳しくは公式リファレンスを参照して ください。 「AndroidManifest.xml」で宣言したパーミッションは、そのアプリをGoogle Playで公開したときにユーザーに提示されます (図1)。 図1:インストールする時のパーミッション表示 45 もしゲームのアプリが、ゲームで使う必要があるようには見えない権限(例:電話を かける権限) を宣言していたとしたら、ユーザーはそのアプリを信用してダウンロード してくれるでしょうか? おそらく信用されないでしょう。よって、アプリ開発者は、自分 のアプリが使っている機能が必要としている権限だけを宣言しなければなりません。 また、ユーザーが疑問に思わないように、各々の権限について、何のために使って いるのかを、アプリの説明文で明らかにしておく方が、ユーザーの信頼を得やすい でしょう。 実装におけるセキュリティ 実装におけるセキュリティの問題は、主にアプリ開発時の不注意や、設計の不 備によって引き起こされます。大きく以下のように分類されます。 ・ アクセス制限の不備 ・ 暗号化/暗号通信の不備 ・ その他 「IPA」 (独立行政法人情報処理推進機構) によると、Androidアプリで報告 されたセキュリティ問題のうち、約75%がアクセス制限の不備でした。アクセス制限 の不備は、さらにファイルのアクセス制限の不備と、コンポーネントのアクセス制限の 不備に大別することができます。 以下では、 まずファイルのアクセス制限について説明し、 その後でコンポーネントの アクセス制限について説明します。 21-1-2 Android のファイルアクセス制限機能(初歩) Androidアプリ開発では、フレームワークが用意している様々なアクセス制限の 仕組みを利用することができます。ここでは、その中でもファイルへのアクセス制限に ついて説明します。 アプリ開発をしていると、アプリの終了や端末の再起動をおこなっても、次回のア プリ利用時にはデータを覚えておいてほしいことがあります。たとえば、設定情報、 写真などのメディアデータ、編集途中のテキストメモなどが考えられます。 そのために、 データを保存しておくためのAPIが用意されていますが、間違った使い方をすると、 そのデータはユーザーや他のアプリから簡単に参照されてしまう状態になります。 写真のデータであれば、他のアプリやユーザーからいつでも見られる状態になっ ている方がよいですが、サービスのアカウント情報(例:LINEのログイン情報) や、日 記アプリに書き込んだテキストが、他のアプリから簡単に読めるようになっていてはい 46 けません。 SharedPreferences Androidアプリは、アプリから設定情報などのデータを簡単に保存しておく仕組 みとして「SharedPreferences」 という機能を提供しています。SharedPreferen 第 cesは、 「キー」 と 「バリュー」を紐付けて記憶しておく簡易データベース (キーバ 21 リューストア) であり、以下のように使います。 セ キ ュ リ テ ィ 章 リスト2 // context を取得する (例:Activity自身を入れる) Context context = (Context)this; // user_settings という名前のキーバリューストアを取得する SharedPreferences prefs = context.getSharedPreferences("user_settings", Context.MODE_PRIVATE); // 取得したキーバリューストアからユーザー名とパスワードを取得する String username = prefs.getString("username"); String password = prefs.getString("password"); SharedPreferencesは、ActivityやServiceの親クラスである 「Context」に 定義されたメソッド、 「getSharedPreference()」で取得することができます。親ク ラスで定義されているため、ActivityやServiceから利用しやすいものです。 Context#getSharedPreferences()の第1引数はキーバリューストアの名前 です。アプリ内に複数のキーバリューストアを持つことができます。第2引数で、この キーバリューストアへのアクセス制限を指定します (表1)。 アクセス制限名 意味 MODE_PRIVATE このアプリからのみ読み書き可能 MODE_WORLD_READABLE 他のアプリからも読み出せる MODE_WORLD_WRITEABLE 他のアプリからも書き込める 表1:第2引数に指定するアクセス制限 アプリを開発していると、自分が作成した複数のアプリ間で設定データを共用し たいと考え、他のアプリからも読み書きできるようにしたくなる場合があるかもしれませ ん。しかしそのときに、 「MODE_WORLD_READABLE」や「MODE_WOR LD_WRITEABLE」を指定して設定を作成すると、他人の作ったアプリからもそ の設定情報を勝手に読み書きできるようになってしまいます。そのため、機能としては 用意されていますが、SharedPreferencesは「MODE_PRIVATE」以外では 作成しないようにしましょう。 ちなみに、MODE_WORLD_READABLEおよびMODE_WORLD_WRI TEABLEはAPI Level 17から 「deprecated( 将来バージョンのどこかで完全 削除予定)」 となったので、いつ新しいAndroidバージョンで使用できなくなっても 47 おかしくありません。その点からも、MODE_WORLD_READABLE、MODE_ WORLD_WRITEABLEを使用することは勧められません。 では、複数のアプリ間でデータを安全に共有するにはどうしたらよいでしょうか? その仕組みの作り方については、後で説明します。 先ほど、保存済みのデータを読み出す例については説明しましたが、逆にデータ を保存しておく方法は以下の通りです。 リスト3 String username; String password; // username と password にユーザー名とパスワードを格納しておく (省略) ...... // prefs の変更を受け付けるEditorオブジェクトを取得する SharedPreferences.Editor editor = prefs.edit(); // 取得したEditorオブジェクトにキーとバリューの組み合わせを登録する editor.putString("username", username); editor.putString("password", password); // 変更を確定する (非同期版) editor.apply(); アプリ専用データ領域内のファイル 端末にアプリがインストールされたとき、 「/data」ディレクトリー以下に、そのアプリ 専用のフォルダーが作成されます。シングルユーザー利用の端末であれば、 「/ data/data/<アプリのパッケージ名>」 というフォルダー名となります。 アプリが保存しておきたいデータ (ファイル) は、 そのディレクトリー内に作成すること ができます。 しかし、 アプリのパッケージ名を取得して、 このフォルダー名を自分で作っ てアクセスするということをしてはいけません。Android 4.2からはタブレット端末で、 Android 5.0からは電話型端末でも、 マルチユーザーを利用できるようになり、必ず しも前述のフォルダー名が現在実行中のユーザー用のフォルダー名とはならないた めです。 マルチユーザーでも正常に動作するようにするため、アプリ専用フォルダーへのア クセスは、 「Context#openFileInput()」や「Context#openFileOutput()」な どのAPIを使用してください。 Context#openFileInput()は、アプリ専用フォルダー内にあるファイルを指定し て読み込み用に開くAPIで、Context#openFileOputput()は、アプリ専用フォル ダー内にあるファイルを書きこみ用に開くAPIです。以下は、使い方の例です。 48 リスト4 // アプリ専用のフォルダーにファイルを書きこみ用に開く OutputStream os = null; try { os = context.openFileOutput("sample.txt", Context.MODE_PRIVATE); // os にファイルを書き出す (省略) 第 21 ...... 章 } finally { セ キ ュ リ テ ィ // ファイルを閉じる if (os != null) { os.close(); } } リスト5 // アプリ専用フォルダー内のファイルを読み込み用に開く InputStream is = null; try { is = context.openFileInput("sample.txt"); // isからファイルを読み出す (省略) ...... } finally { if (is != null) { is.close(); } } 勘の良い方は気づいたかもしれませんが、SharedPreferencesで指定したCo ntext.MODE_PRIVATEは、このアプリ専用データ領域内でのファイル作成時 のアクセス制限と同じものです。実は、SharedPreferencesのキーバリューを保存 するファイルは、 アプリ専用データ領域内に作成されます。SharedPreferencesの 場合と同様、他のアプリからアクセスできなくするために、ファイル作成の場合でも同 じように、Context.MODE_PRIVATEのみを使用してください。 拡張ストレージ 拡張ストレージは、特定のアプリからだけでなく、他のアプリや、端末を接続した PCからもアクセスできる領域です。カメラアプリで撮影した写真、 ブラウザーでダウン ロードしたファイルなど、大きなファイルの置き場として主に利用されます。 セキュリティの観点から、以下のような注意が必要です。 49 ・ 他のアプリからのアクセスを止める方法がないため、センシティブな情報 (個人情報や著作物) を生データでは保管しない ・ 著作物は暗号化して保管する ・ 他のアプリによってファイルを削除される可能性があるため、ファイルがな くなった場合の回復処理を考慮しておく →例:購入した著作物を再ダウンロードできるようにする 上記のような制限について対策するのは大変なので、アプリ専用データ領域の みを使用するようにアプリを作っていればよいのではないかと考えるかもしれません。 端末によって異なりますが、 アプリ専用データ領域は小さく、拡張ストレージは大きい ことが一般的です。 よって、動画などの大きなデータを取り扱う場合には、制限事項 を理解した上で、拡張ストレージを利用することを考えてください。 check! Android 4.4のSDカードアクセス Android端末の中には、端末に内蔵した拡張ストレージとは別 にSDカードを挿入できるものがあります。その場合、SDカードは 2番目の拡張ストレージとして扱われます。Android 4.3までは、 すべての拡張ストレージのアクセス制限は同じでしたが、Andro id 4.4から1番目と2番目以降の拡張ストレージでは、一般のア プリ権限でおこなえることが変わっています。 Android 4.4では、一般のアプリがたとえ 「WRITE_EXTERN AL_STORAGE」 のパーミッションを宣言して、ユーザーに許可さ れていたとしても、2番目以降の拡張ストレージにファイルを書 き込むことができなくなりました。あなたが、カメラアプリや画 像ギャラリーアプリを開発する場合、写真をSDカードにエクス 50 ポートするような機能を提供することはできないと考えてくださ い。 システム権限を持つアプリは、SDカードへのファイルの書き込 みが可能です。SDカードへのファイルコピーや移動ができるファ イルマネージャ系のアプリは、メーカーがプリインストールで提 供したもののみが完全な機能を提供できます。 Andorid 5.0以降では、新しい権限とAPIが追加されて再び2 番目以降の拡張ストレージにアクセスできるようになりました。 しかし、Android 5.0以降の普及率はまだ低く、Android 4.4の 普及率がかなり高い状況はしばらく続くと思われますので、この 制限事項には気をつけるようにしましょう。 演習問題 2つのアプリを作成します。 1つ目のアプリでデータを作成し、 2つ目のアプリはその データにアクセスを試みます。1つ目のアプリでデータ作成時に指定したアクセス制 限が、 2つ目のアプリからのアクセスの際に正しく働くことを確認しましょう。 第 21 章 セ キ ュ リ テ ィ 1つ目のアプリのプロジェクト作成 これまでのアプリ開発演習と同様に、新規プロジェクトを作成します (図2)。 図2:プロジェクトの新規作成 Application Name(アプリ名) にはExportApp、Company Domainにはte chinstitute.jpを指定します。これによりPacakge nameは自動的にjp.techinsti tute.exportsample となります。二つ目のアプリから参照する際にこのパッケージ 名を使用しますので、他のパッケージ名に変更して作成する場合は適宜読み替え てください。 Activityの作成画面では「Blank Activity」を選択してください。 次にデータ書き込み用のボタンとデータ読み込み用のボタンを持つ単純なレイア ウトを作成します。プロジェクト新規作成時に自動的に作成されているactivity_ main.xmlを編集します 。以下のように書き換えてください。 51 activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="jp.techinstitute.exportsample.MainActivity" > <Button android:id="@+id/write_button" android:text="Write Data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> <Button android:id="@+id/read_button" android:text="Read Data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="20dp" android:layout_centerHorizontal="true" android:layout_below="@id/write_button" /> </RelativeLayout> 次にActivityのJavaコードを書いていきます。java/jp.techinstitute.export app 以下にあるMainActivity.javaを編集します。 52 MainActivity.java package jp.techinstitute.exportsample; import android.app.Activity; (以下import文省略) public class MainActivity extends Activity 第 21 implements View.OnClickListener { 章 セ キ ュ リ テ ィ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button writeButton = (Button) findViewById(R.id.write_button); Button readButton = (Button) findViewById(R.id.read_button); writeButton.setOnClickListener(this); readButton.setOnClickListener(this); } (以下、変更のないコード省略) @Override public void onClick(View v) { SharedPreferences prefs = getSharedPreferences("sample1", MODE_PRIVATE); switch(v.getId()) { case R.id.write_button: SharedPreferences.Editor editor = prefs.edit(); editor.putString("sample_string", "Data is stored!!"); editor.apply(); break; case R.id.read_button: String readData = prefs.getString("sample_string", null); if (readData == null) { readData = "Data isn't stored."; } Toast.makeText(this, readData, Toast.LENGTH_LONG).show(); break; } } } Android Studioの補完機能を利用しながら入力していくと、 インポートは適切に パッケージへ追加されるはずです。補完機能や修正機能を有効に活用して、コン パイルエラーが残らないように編集をしてください。 四角で囲った部分が今回特有のコードです。読み込みボタンを押したときには、 データが書き込まれていればそのデータを、書き込まれていなければ書き込まれてい ないことをトーストで表示します。 53 上記ではデータの書き込みは、Context.MODE_PRIVATEで行っています。 二つ目のアプリのプロジェクト作成 二つ目のアプリプロジェクトを一つ目のプロジェクトと同様に作成します。Applic ation NameをUseApp、Company Domainは一つ目のプロジェクトと同じくtec hinstitute.jpとします。自動的にPackage Nameは jp.techinstitute.usesam ple となります。 他の設定はExportAppの場合と同じです。新規作成時に同時にBlank Ac tivityを作成するようにしてください。 次にデータ読み込み用のボタンを持つ単純なレイアウトを作成します。activi ty_main.xml を編集します。 activity_main.xml <RelativeLayout xmlns:android="http://schemas.android.com/apk/ res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="jp.techinstitute.exportsample.MainActivity" > <Button android:id="@+id/read_button" android:text="Read Other App Data" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> </RelativeLayout> 次にActivityのJavaコードを書いていきます。MainActivity.javaを編集しま す。 54 MainActivity.java package jp.techinstitute.usesample; import android.app.Activity; (以下import文省略) public class MainActivity extends Activity 第 21 implements View.OnClickListener { 章 セ キ ュ リ テ ィ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button) findViewById(R.id.read_button); button.setOnClickListener(this); } (以下、変更のないコード省略) @Override public void onClick(View v) { Context context; try { context = createPackageContext("jp.techinstitute.exportsample", CONTEXT_RESTRICTED); SharedPreferences prefs = context.getSharedPreferences("sample1", MODE_PRIVATE); String readData = prefs.getString("sample_string", null); if (readData == null) { readData = "data isn't stored."; } Toast.makeText(this, readData, Toast.LENGTH_LONG).show(); } catch (NameNotFoundException e) { e.printStackTrace(); } } } ここでは、jp.techinstitute.exportsampleのコンテキストを取得して、それが持 つSharedPreferences を読み取ろうとしています。しかし、MODE_PRIVATE で作成されているため、 データを読み取れないメッセージがトーストで表示されます。 それでは、 わざと制限を弱くして、別のアプリから読み取れるようにしてみましょう。 すでにsample1の名称でMODE_PRIVATEのSharedPreferencesは作 成済みであるため、sample2という名称でMODE_WORLD_READABLEなも のを作ります。jp.techinstitute.exportsampleの変更点は以下の通りです。 55 jp.techinstitute.exportsample @Override public void onClick(View v) { SharedPreferences prefs = getSharedPreferences("sample2", MODE_WORLD_READABLE); (以下省略) deprecatedなため、取り消し線が表示されますが無視してください。 同様にjp.techinstitute.usesampleも変更します。 jp.techinstitute.usesample @Override public void onClick(View v) { (省略) SharedPreferences prefs = context.getSharedPreferences("sample2", MODE_PRIVATE); (以下省略) MODE_PRIVATEはそのままで、sample1をsample2に変更するだけです。 変更することで、このように非常に簡単に別のアプリからデータを読み出せること が確認できました。これは、他のアプリから情報を簡単に盗むことができるということで す。 別のアプリから読み取れる設定でパスワードなどの重要な情報を保存しておくな どは危険なので絶対にしないようにしましょう。 21-1-3 アプリの解析 セキュリティについて考える場合、敵(攻撃者) がどんなことを行うのかを知っておくこ とは非常に重要です。ここでは、 そのためのアプリ解析の手法について紹介します。 アプリは通常、Google Playストアを通じて配布されます。実は、配布されて端 末にインストールされたアプリは、非常に簡単に端末から取り出して解析することが 可能です。 Android Studioに任せた場合の標 準のインストール先は、ユーザーの ホームディレクトリ下のAppData\Lo cal\Android\sdk\ まず、 コマンドプロンプトでadbコマンド を以下のように呼び出すことで、 インストール されたアプリの一覧を取得できます。 リスト6 adb shell pm list packages –f 56 もし特定のアプリを絞り込んで見つけたい場合は以下のように findstr コマンド (LinuxやMacの場合はgrepコマンド) を入力します。 リスト7 adb shell pm list packages –f | findstr google 第 21 章 上記はパッケージ名にgoogleが含まれるものを抽出しています。 セ キ ュ リ テ ィ 図3:"google"を含むパッケージ名のアプリ一覧取得 一覧の各行は以下のような形式になっています。 リスト8 package:<apkファイルパス>=<パッケージ名> 再びadbコマンドを使用し、apkファイルパスを指定して、apkファイルを取り出すこ とができます。 リスト9 adb pull <apkファイルパス> このように任意のapkファイルを簡単に取り出すことができます。apkファイルはZIP 形式のファイルなので、拡張子をzipに変更すれば、簡単に中身のファイルを見るこ とができます。 たとえば、Google Mapsのapkファイルを取り出し、拡張子をzipに変更して展開 してみると、その中にはresフォルダがあります。このresフォルダにはアプリが使用する 画像ファイルが格納されていることを確認できます。 57 Android 5.0以降、アプリはARTで 実行されますが、過去との互換性か らapkファイルに含まれるコンパイル されたコードはDalvik VM向けの形 式となります。 地図アプリではアイコン画像など、コピーされてもそれほど困るリソースは入ってい ません。しかし、 ゲームや電子書籍のアプリで、画像リソースファイルをそのまま格納し ていると、簡単に画像データだけを抜き出されてしまいます。これは非常にまずいで す。 リソースだけでなく、プログラムも取得できるので、解析することができます。ネット上 にはそのためのツール類が非常に充実しています。 アプリの情報の概要は、Android SDKのビルドツールに付属しているaaptコマ ンドで取得することができます。aaptファイルは、SDKディレクトリの直下にあるbuildtoolsの中にあります。ビルドツールのバージョンが21.0.0の場合、以下のようにする ことでapkファイルの情報を取得できます。 リスト10 <SDKの置き場所>\build-tools\21.0.0\aapt l -a <解析したいapkファイル名> このように、標準ツールだけでも、Android Manifestにどんなことが記述されて いるかといった情報を簡単に見ることができます。 apkファイルを展開するとその中にはclasses.dexというファイルがあります。これは ソースコードからコンパイルされたDalvik VM向けのコード が格納されたファイル です。 このファイルは、dex2jarを使用することでJarというJavaの形式に逆変換する ことが可能です。dex2jarは以下などから入手できます。 →https://github.com/pxb1988/dex2jar Jar形式に変換されてしまえば、Java向けのツールでソースコードにまで逆変換 することが可能です。それにはJD(Java Decomiler) というツールを使用します。 以下のサイトから入手することができます。 →http://jd.benow.ca/ その中にあるJD-GUIツールでJarファイルを読み込むと、ファイルを解析してJava ソースコードに戻してくれます。 ここまで戻すことができれば、 ソースコードを読んでアプ リがどんな処理を行っているのかを解析していくことが可能です。 まとめ アプリのセキュリティについて、仕様と実装の両面で考慮をしないといけないことを 本節で学びました。また、具体的にどのようにアクセス制限が働くかを紹介しました。 インストールされたアプリは簡単にユーザーによって端末から取り出され、解析さ れる可能性があることを学びました。次回、それに対する対策方法について学びま す。 58 21-2 Android アプリの 高度なセキュリティ 第 LESSON この節では、Androidのセキュリティ機能について踏み込 んでいきます。まずコンポーネントへのアクセス制限機能に ついて学びます。その後、HTTPサーバーとの暗号通信につ いても取り上げます。 21 KEYWORD 章 セ キ ュ リ テ ィ android:exported permission grantUriPermission protectionLevel SSL/TLS ContentProvider URL HttpsURLConnection InputStream InputStreamReader BufferedReader この節を学ぶとできること ・ Androidアプリのコンポーネントの守り方 ・ HTTPサーバーとの暗号通信 ・アプリ解析への対策 59 21-2 -1 コンポーネントのアクセス制限 Androidのフレームワークが用意している様々なアクセス制限のうち、ファイルへ のアクセス制限については前節で説明しました。本節ではコンポーネントへのアクセ ス制限を説明します。Androidでは、 ファイルレベルのアクセス制限だけではなく、ア プリの構成要素(コンポーネント) に対してもアクセスを制限することができます。 Androidアプリを開発していると、複数のActivityやServiceなどを作成してい くことがよくあります。それらのコンポーネントの中には、外部アプリから自由に呼び出 せてはいけない機能を持ったものがあるかもしれません。たとえば、SNSクライアントア 攻撃があったことは確認されていませ んが、有名なTwitterクライアントアプ リが実際に持っていた脆弱性です。 プリの中のあるコンポーネントを、悪意のある他のアプリから呼び出すことができるよう になっていると、SNSクライアントアプリの権限でネットに公開するつもりのなかった端 末内の写真を全世界にばら撒かれるということが起こりえます。 Androidにはこれを防ぐ仕組みとして、AndroidManifest.xmlにてActivity やServiceなどに「android:exported」属性を宣言しておくことができます。以下 はその記述例です。 リスト1 <application ...> <activity android:name="com.example.android.activity1" android:exported="true"> ...... </activity> ...... <servce android:name="com.example.android.service1" andorid:exported="false"> ...... </service> ...... </application> android:exportedを「false」にしておくことで、そのActivity、Serviceなどを 他のアプリに対して非公開状態にすることができます。その場合も、自分のアプリ内 からは呼び出すことが可能です。セキュリティの観点からは、公開にする必要がない コンポーネントにはandorid:exportedをfalseに設定することを習慣づけましょう。 60 特定のアプリにだけデータを開放する (grantUriPermission) Androidでは、アプリ内およびアプリ間でのデータのやり取りに便利な仕組みと 第 して、 「ContentProvider」 というものを持っています。ContentProviderの詳細 21 は本節では省略しますが、ContentProviderを介することで、アプリは管理する セ キ ュ リ テ ィ 章 データを他のアプリからアクセスできるようにすることができます。 ただし、 アプリ内でContext.MODE_PRIVATEで保管し、他のアプリからのア クセスを防いでいたにもかかわらず、ContentProviderを通じて他のアプリすべて にアクセスを開放してしまうことは望ましくないでしょう。特定のアプリにだけ、自分が 作ったContentProviderにアクセスさせるために、 「grantUriPermission」を使 うことができます。 まず、AndoridManifest.xml でContentProviderの宣言にgrantUriPer missionを使用することを宣言します。 リスト2 <provider andorid:name=".SampleProvider" android:authorities=" com.example.android.sampleprovider" android:grantUriPermission="true"> </provider> そして、自分が作ったContentProviderの中で、どのアプリからのアクセスを許 可するかを宣言します。 リスト3 public class SampleProvider extends ContentProvider { ... @Override public void onCreate() { ...... // パッケージ名 com.example.android.reader のアプリにアクセスを許可する grantUriPermission("com.example.android.reader", Uri.parse("content://com.example.sampleprovider/", Intent.FLAG_GRANT_READ_URI_PERMISSION); ...... } ...... } 「grantUriPermission()」の第1引数で許可を与えるアプリのパッケージ名を 指定し、第2引数ではアクセスを許可するContentProviderのURI、第3引数で 許可を与えるアクセスの種類(ここでは読み込みアクセス) を指定します。上記の例 61 では、 「com.example.android.sampleprovider」のContentProviderは、 「com.example.android.reader」からの読み込みアクセスを受け入れ、他のア プリからのアクセスを拒絶するようになります。 grantUriPermission()メソッドにて与えた許可は、端末の再起動まで与えられ たままとなります。権限を取り上げたい場合は、 「revokeUriPermission()」メソッド を呼び出すことができます。 リスト4 revokeUriPermission(Uri.parse("content://com.example.sampleprovider/"), Intent.FLAG_GRANT_READ_URI_PERMISSION); revokeUriPermission()では、許可を取り上げるアプリのパッケージ名指定が できないため、指定したURIのContentProviderへのアクセスをすべて取り除くこと に注意してください。必要であれば、すべて取り除いた後に、許可するアプリだけを 再度登録しなおしてください。 同じ鍵で署名したアプリのアクセスを許可する grantUriPermissionでは、特定のアプリを明示的に指定して、ContentPro viderにアクセスを許可することができました。これは、事前に許可する対象のアプリ が明確な場合には有効です。 では、自分が開発したコンポーネントを、将来自分が作成するアプリから自由に利 用できるようにしつつ、他人のアプリからのアクセスを禁止するのはどうすればよいの でしょうか? また、ActivityやServiceに対しても、自作アプリからのアクセスのみを 許可したい場合はどうすればよいでしょうか? Androidにはそのための仕組みが用意されています。それがパーミッションです。 (1)コンポーネントを提供するアプリのAndroidManifest.xmlでパーミッ ションの作成をおこなう。また、同じ鍵で署名したアプリのみアクセスを 許可する旨を記載する (2)上記アプリと同じ鍵で署名した別のアプリのAndroidManifest.xml で、(1)で作成したパーミッションの利用を宣言する 実はパーミッションは、すでに述べたシステムが用意したものだけではなく、アプリ が独自に追加することも可能となっています。そしてそれを利用することで、自分が開 発したアプリにのみ権限を与えることが可能となります。 まず、(1)のアプリのAndroidManifest.xmlの例を見てみましょう。 62 リスト5 <permission andorid:name="com.example.android.CUSTOM_PERMISSION" android:description="Custom Permission" android:label="Custom Permission" android:protectionLevel="signature" /> 第 <uses-permission android:name="com.example.android.CUSTOM_PERMISSION" /> 21 章 <application ...> セ キ ュ リ テ ィ <activity android:permission="com.example.android.CUSTOM_PERMISSION" .... > ...... </activity> ...... </application> まず「<permission>」タグで新しいパーミッションを登録します。名前は自由に つけることが可能です。 「android:protectionLevel」に「signature」を設定して いますが、 これが同じ鍵で署名したアプリにだけ許可を与えるということを示します。 permissionに設定する項目の詳細は、以下の公式リファレンスを参照してくだ さい。 h t t p : / / d e v e l o p e r. a n d r o i d . c o m / g u i d e / t o p i c s / m a n i f e s t / permission-element.html 次に、自分が宣言した<permission>を自分で「<uses-permission>」で宣 言しておくことで、自分自身もそのパーミッションを利用できるようにしています。Activ ityの宣言で「android:permission」に宣言したパーミッション名を記述すること で、 このActivityは同じ鍵で署名したアプリからしか呼び出せなくなります。 呼び出す側のアプリは簡単で、インターネットアクセスのパーミッションを要求した 場合と同じように、以下の記述をAndroidManifest.xmlに記載すればよいので す。 リスト6 <uses-permission android:name="com.example.android.CUSTOM_PERMISSION" /> 上の記述を同じ鍵で署名していないアプリが宣言していても、許可は与えられま せん。 この仕組みは非常に簡単で、かつ十分なセキュリティを備えているように見えます が、 1つ大きな弱点があります。<permission>でのパーミッションの登録は、先にイ ンストールしたアプリのものが優先されることです。パーミッション名を、自分が管理す るドメイン名を用いたものにするなど、工夫することで意図しない衝突を避けることがで 63 きます。しかし、悪意のある開発者によるアプリが、事前に同じ名前のパーミッション を弱い制限(誰でも使える制限) で登録していた場合、後からインストールされた正 しいアプリによる制限は働きません。 このように、独自パーミッションによる制限は正しく働かない場合があるので、独自 パーミッションをあてにした設計は行わないことを推奨します。 check! ユーザーの識別には端末ID(UDID)を使ってはいけない アプリを開発していると、アプリを利用しいているユーザーを 識別したくなる場合があります (例:ゲームのスコア管理、SNSの 簡易ログイン) 。 その方法として、端末固有のID (Unique Device Identifier) を 利用するアプリがありますが、それにはセキュリティ上の問題が あります。たとえばSNSの簡易ログインにUDIDを使用した場合、 以下のような攻撃方法が考えられます (1) 悪意のあるアプリがUDIDを取得する (2) 取得したUDIDを用いてSNSのサーバーにログインする (3) ユーザーの情報をSNSのサーバーから取得する UDIDはハードウェアと紐づいていて変更することもできない ため、それを悪意のあるアプリが取得してしまえば、攻撃を防ぐ ことができなくなります。 アプリで簡易ログインのためになんらかのIDを使う場合は、 UDIDではなくUUID (Universally Unique Identifier) を利用して ください。Androidでは、以下の方法でUUIDを生成することが可 能です。 String uuid = UUID.randomUUID().toString(); そして、生成したUUIDはSharedPreferencesに保存しておくこ とで、次回以降の簡易ログインなどに使用してください。 ただ、真の意味ではこれはユニーク (唯一の) IDとはなっていま せん。ランダム生成されたIDなので、実際には、他のIDと重複して いないかをサーバーとの通信で確認し、重複がないことを確認す るまで生成し直すなどして重複を排除しなければなりません。サー バーでユニークIDを生成して、端末アプリに対して与えるという設 計とするのもよいでしょう。 21-2 -2 通信のセキュリティ ユーザーにとって魅力的なアプリを作ろうとすれば、アプリはサーバーとの通信を おこなうことが多くなります。たとえば、ソーシャルゲームはサーバーと連携して、ユー ザーに対戦や協力プレイの機能を提供します。ニュースなどのキュレーション (情報 収集とまとめ) をおこなうアプリも、サーバーと連携して、キュレーションした結果を取 得するなどの機能が必要となります。 しかし、 ログイン時の通信が傍受されると、悪意のあるユーザーやアプリによって、 ア カウントの乗っ取りなど、不正がおこなわれてしまう危険性があります。よってそのような 通信については、 アプリとサーバーとの間の通信を暗号化することが必要です。 ここでは、Androidアプリがサーバーと安全な通信路を確立するための方法に ついて説明します。 SSL/TLSによる暗号通信 厳密にはTCP/IP以外でも使用でき るものです。 「SSL」 (Secure Socket Layer) および「TLS」 (Transport Layer Secur ity) は、TCP/IP上で安全な通信路を構築するためのプロトコルです。 64 それらの通信の安全性についての説明、および公開鍵暗号方式の説明は、本 教科書の趣旨から外れますので詳細を割愛しますが、以下のような手順でクライア ントとサーバーの間の安全な通信路を確立します。 (1)クライアントがサーバーに接続を要求する (2)サーバーが応答し、クライアントにサーバーの証明書を送る (3)クライアントはサーバー証明書がCA(Certificate Authority:認証局) さらにクライアントが証明書を送付し、 サーバーがクライアントを検証する場 合もあります。 第 21 章 セ キ ュ リ テ ィ から発行された正しいものかどうかを検証する。正しい場合は、クライア ントで生成した乱数をサーバー証明書に含まれる公開鍵で暗号化して サーバーに返す (4)サーバーは、暗号化された乱数データをサーバーの秘密鍵で復号 (暗号 解読) する (5)サーバーとクライアントは、共有した乱数データから共通鍵暗号の鍵を 生成し、以降の通信をその共通鍵を用いて暗号化しながらおこなう アプリからサーバーへ通信する場合、ほとんどが「HTTP」を用いることとなりま す。SSL/TLSによる安全な通信路でおこなうHTTPを、特に「HTTPS」 と呼びま す。サーバーとの接続でHTTPSが利用される場合、URLの先頭は「https://」 から始まるものとなります。 Androidでは、HTTPSによるサーバーとの接続を簡単におこなうことができま す。実はHTTPの通信の場合とほとんど手順は同じで、 「URL」クラスと 「Https UrlConnection」を用います。以下に手順を示します。 リスト7 try { // URLクラスのインスタンスを作成 URL url = new URL("https://www.example.com/"); // サーバー接続用のオブジェクトを生成する HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); // リクエストメソッドなどのオプション設定 conn.setRequestMethod("GET"); conn.setInstanceFollowRedirects(false); conn.setRequestProperty("Accept-Language", "jp"); // サーバーと接続する conn.connect(); } catch (IOException e) { // エラー処理を記述する } 65 サーバーとの接続が失敗すると 「IOException」が発生しますが、接続に失敗 する理由は1つではありません。以下のようなものが考えられます。 ・ サーバーが見つからない ・ サーバーとのTCP/IPの接続に失敗した ・ サーバーの証明書が正しくない 特に3番目は、セキュリティにおいて重要です。このエラーが出た場合、アプリと サーバーとの間に割り込んで、通信を傍受しようとしている何者かがいるのかもしれ ません。その場合は単にサーバーへの接続が失敗したというエラーを出すのではな く、偽のサーバーに接続しようとしているかもしれないことを警告するエラーをユー ザーに表示し、注意を喚起することが望ましいです。 そのようなセキュリティ警告を意識したサーバーへの接続処理の書き方を、Goo gleのサーバーにHTTPSで接続する演習を通じて学びます。 アプリのプロジェクト作成 アプリプロジェクトを作成します。Application NameをTLSAppとしてください。 以下の例ではPackage Nameがjp.techinstitute.tlssample となるようにしてい ます。適宜読み替えてください。 次にWebViewを持つ単純なレイアウトを作成します。 リスト8 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" tools:context="jp.techinstitute.tlssample.MainActivity" > <WebView android:id="@+id/webview" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> 次に、ActivityのJavaコードを記述します。 サーバーからのデータ読み込みを行い、取得した結果をWebViewに表示する 処理となります。 66 リスト9 package jp.techinstitute.tlssample; (import文は省略します) public class MainActivity extends Activity { private static final String SITE_URL = 第 "https://www.google.co.jp/"; 21 private WebView mWebView; セ キ ュ リ テ ィ 章 @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mWebView = (WebView) findViewById(R.id.webview); AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() { private String mSecurityReason; @Override protected String doInBackground(Void... params) { try { URL url = new URL(SITE_URL); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("GET"); conn.setInstanceFollowRedirects(false); conn.setRequestProperty("Accept-Language", "ja-jp"); conn.setRequestProperty("Accept-Charset", "utf-8"); conn.setRequestProperty("User-Agent", "Mozilla/5.0"); conn.connect(); InputStream in = conn.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in, "UTF-8")); StringBuilder sb = new StringBuilder(); String line; while((line = br.readLine()) != null) { sb.append(line); sb.append("\n"); } return sb.toString(); } catch (SSLException e) { e.printStackTrace(); mSecurityReason = e.toString(); } catch (IOException e) { e.printStackTrace(); } return null; } 67 @Override protected void onPostExecute(String result) { if (result != null) { mWebView.loadDataWithBaseURL(SITE_URL, result, "text/html", "UTF-8", null); } else if (mSecurityReason != null) { Toast.makeText(MainActivity.this, mSecurityReason, Toast.LENGTH_LONG).show(); } }; }; task.execute(); } (以下、省略します) サーバーとの接続時にHttpsURLConnection#setProperty()で様々な指 定を行っていますが、これは文字コードUTF-8で結果を返してもらうためのもので す。ここでは詳細を割愛します。 Androidではメインスレッド(UIスレッド)で通信を行ってはいけないので、Async Taskを用いてバックグランドで通信し、結果をUIスレッド上で表示するようにしてい ます。成功すればページが表示されます。 SSL/TLSに失敗する(例:証明書検証に失敗する)などの場合は、失敗時の例 外(SSLException)を人間が読めるものにした情報をトーストで表示するようにして います。 上記のGoogleサーバーへの接続については、インターネット上に障害が発生す るなどの何らかのトラブルが無ければ、通常は成功するはずです。 SSL/TLSの失敗の確認 では、SSL/TLSの接続が失敗する場合も試してみましょう。ただし、通信の間に 割り込みエラーを発生させるのは難しいため、エラーを別の方法で発生させてみま す。 サーバーによっては、クライアント側にも証明書を要求する場合があるということに ついて、少しだけ触れましたが、そのような要求を行うところとして典型的なのは銀行 のサイトです。ほかにも役所のサイト(e-Taxなど)でクライアント側に証明書を要求す る場合があります。 具体的なURLについて、ここには記述しませんが、試しにGoogle検索などで銀 行のサイトを見つけ、その中から法人向けのネットバンキングサービスなどのログイン を探し、 クライアント証明書を求めるURLを見つけてみてください。 そして、先ほど指定したGoogleのサーバーのURLをそのURLに置き換えたアプ 68 リを実行してみましょう。具体的には以下の定数定義を変更するだけです。 リスト10 private static final String SITE_URL = "https://www.google.co.jp/"; 第 この部分を見つけたURLに変更し、 ビルドしてアプリをインストールし直して実行し 21 てみましょう。クライアント証明書を指定する処理が記述されていないので、サーバ セ キ ュ リ テ ィ 章 が接続を拒否し、例外(SSLException)が発生します。 通信に割り込んでデータを盗もうとする中間者攻撃では、この場合とはメッセージ 内容などが異なる例外が発生しますが、基本的には同じです。 実際のアプリでは、SSL接続での例外発生は適切にユーザーにエラー内容を 提示するようにしてください。 独自証明書を用いたサーバーとのHTTPS接続 アプリの開発をする際に、サーバーも同時に開発をするということがよくあります。 ソー シャルゲームで、端末アプリとサーバーの開発をおこなう場合などもその一例です。 本番運用となれば、ほとんどの場合はサーバーに正式な証明書を設定すること になりますが、開発中には様々な理由から正式な証明書を置くことができない場合 があります。 開発用のサーバーは本番用サーバー とURLが異なり、本番用の証明書が 利用できない、 まだ証明書を発行して いない、などなど。 その場合、ひとまずはHTTPSではなくHTTPで開発を進めていくというのも1つ の手ですが、本来できるだけ本番環境に近づけて、本番環境で起こる問題を減ら すことが大切です。 また、社内システムと接続するアプリの場合、 その会社独自のCAから発行された 証明書が設定されたサーバーを、本番でも利用していることがあります。ただし、そ のような独自証明書を持ったサーバーとのHTTPS接続では、Androidに標準で 備わっているCA証明書を用いて、HTTPSサーバーの正当性を証明することがで きません。 ネット上には、証明書検証でのエラーを無視する処理を書くことで、エラーをとりあ えず回避するという対策がよく出ています。一時的な対策にはなりますが、セキュリ ティ上はあまり良い方法であるとは言えません。特に、本番サーバーでも独自証明書 を利用する場合には、絶対におこなってはいけない対策です。 実は、独自証明書を用いたHTTPSサーバーとの接続時に、サーバーの正当 性を検証する方法は、Android公式サイトにある以下のドキュメントに詳しく説明さ れています。 その他にも、ホスト名検証に失敗す http://developer.android.com/training/articles/security-ssl. html る場合の対策など、よく起こるケース についての対策が示されています。 69 そちらに記載されているサンプルをベースに、使い方を日本語コメントで説明します。 リスト11 // まずCertificateFactoryのインスタンスを生成します CertificateFactory cf = CertificateFactory.getInstance("X.509"); // 独自証明書を発行したCA証明書を読み込みます // この例では、ワシントン大学ローカルのCA証明書です // From https://www.washington.edu/itconnect/security/ca/load-der.crt InputStream caInput = new BufferedInputStream(new FileInputStream("load-der.crt")); Certificate ca; try { ca = cf.generateCertificate(caInput); System.out.println("ca=" + ((X509Certificate) ca).getSubjectDN()); } finally { caInput.close(); } // 前述のCAを含んだKeyStoreを作成します String keyStoreType = KeyStore.getDefaultType(); KeyStore keyStore = KeyStore.getInstance(keyStoreType); keyStore.load(null, null); keyStore.setCertificateEntry("ca", ca); // 前述のKeyStoreを持ったTrustManagerFactoryを作成します String tmfAlgorithm = TrustManagerFactory.getDefaultAlgorithm(); TrustManagerFactory tmf = TrustManagerFactory.getInstance(tmfAlgorithm); tmf.init(keyStore); // SSLContextを前述のTrustManagerFactoryから取得した // TrustManagerを用いて作成します SSLContext context = SSLContext.getInstance("TLS"); context.init(null, tmf.getTrustManagers(), null); // HttpsUrlConnectionに、ここまでの手順で作成したSSLContextを設定します URL url = new URL("https://certs.cac.washington.edu/CAtest/"); HttpsURLConnection urlConnection = (HttpsURLConnection)url.openConnection(); urlConnection.setSSLSocketFactory(context.getSocketFactory()); // urlConnectionからの入力を、コンソールに出力する InputStream in = urlConnection.getInputStream(); copyInputStreamToOutputStream(in, System.out) https://jvn.jp/vu/JVN VU90369988/ 実はHTTPS接続においては、独自の証明書を持ったサーバーとの、接続時 の証明書検証の不備をセキュリティ問題として指摘される事例は多く、大手の会 社がリリースしたアプリでもそういった例がありました。 気づかれないだろうと思って、証明書検証を省略するなどの手抜きをした結果、 ネット上でのその手抜きを大きく話題にされてしまうということもありえます。ご注意くだ さい。 70 21-2 -3 アプリ解析への対抗策 前節にて、 リリースしたAndroidアプリは簡単に端末から抜き出され、 データの取 り出しやプログラムの解析が行われる可能性があることを示しました。 ここでは、 それへの対抗策について示します。 第 ただし、ソフトウェアによる方法では完全には防御できません。徹底的に時間と手 21 間をかけることで、対抗策をすべて無効化することも可能であることを知っておいてく セ キ ュ リ テ ィ 章 ださい。 しかし、 この施策が無駄であるとは考えないでください。 攻撃者は、アプリ解析で得られる情報の価値と、攻略にかかる手間を天秤にか けて、攻撃するかどうか判断します。これは泥棒が家や会社に侵入する際と同じよう なものです。通常の場合犯罪者は、手間がかかるものは避けて、楽なところを攻める ものです。 ProGuardによる難読化 Androidアプリ開発環境には、標準でプログラムの難読化を行うためのProGu ardというツールが含まれています。 これまでに作成したプロジェクトで、Module:appのbuild.gradleに以下のような 記述があることを確認してください。 リスト12 buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } 皆さんが新規作成したプロジェクトでは、自動的にそのツールを使用して、 リリース ビルドを難読化するようになっています。ここでは、ProGuardの設定ファイルの記述 法の詳細については省略します。 ProGuardでは、以下の処理を行って、 プログラムの解析難易度を高めます。 ・ 不要な処理の除去 リンクしたライブラリも含め、使わないコードをアプリ内から取り除く ・ 最適化 コードを最適化する (例:ループの展開など) ・ 名前の短縮化 パッケージ名、クラス名、変数名などを短縮して、人間にはわかりにくいものにしま す。 71 ・ ログ出力の削除 ログ出力を削除して、解析のヒントを与えないようにします ProGuardでは守れないもの 一方、 データ (リソース) の暗号化などについては、ProGuardでは行ってくれませ ん。アプリ内のデータを抜き取られることを防ぐためには、データの暗号化や復号の 処理は自前で作成する必要があります。 暗号化によるデータ保護の具体的な方法については、時間の都合上ここでは 割愛します。独自のデータ保護を作成するのであれば、暗号に関する書籍やウェ ブサイトなどで十分に学習した上で行うようにしてください。 実際の開発の現場では、ゼロからデータ保護を開発するのではなく、既存の ツールを利用することもあります。ただし、それらのツールは有償の製品であることが ほとんどです。 たとえば、無償で利用できるProGuardには有償の上位版のDexGuard( htt ps://www.saikoa.com/dexguard) という製品があります。このツールは、ProG uardの機能に加えて以下の保護機能をアプリに追加することができます。 ・ 文字列の暗号化 ・ クラスの暗号化 ・リフレクションを利用したAPI呼び出し処理の隠蔽 ・ アセットの暗号化 ・リソースXML難読化 ・ Natriveライブラリの暗号化 ・ Root化端末の検知 ・ デバッガ接続の検知 ・ エミュレータ上での実行の検知 など多数 DexGuardが提供している保護機能などは、自分で保護機能を開発する場合に も参考になります。これらの機能はすべて、 これまでにあった攻撃方法への対処だから です。 たとえば、TwitterやFacebookなどのSNSへのログイン機能を持つアプリの場 合、認証用のトークン文字列は暗号化して持っておく方が安全です。 ゲームアプリでは、アセットには暗号化したデータを置いておき、アプリで読み込ん だ暗号データを画像ファイルに復号し表示するというような処理が必要になるでしょう。 新たに開発するコストと、ツールを購入するコスト、守りたいデータやコードの価値 を天秤にかけて、 ツールの購入、独自保護機能の開発などをおこなってください。 72 まとめ さまざまなセキュリティ対策を行うことは、機能を増やしてアプリを魅力的にすること にはつながらないため、後回しにされがちです。場合によっては、あえてその作業を 減らして開発をスピードアップするといったことが行われることがあります。 第 しかし、セキュリティを軽視した開発をすると、そこを攻撃されたときに大きなダメー 21 ジを受けてしまい、開発の時点でかかったコストの何倍、何十倍、何百倍もの負担 セ キ ュ リ テ ィ 章 を負うことになるかもしれません。 現代のネットには、セキュリティ不備に対する攻撃があふれています。セキュリティ 対策は転ばぬ先の杖ではなく、ネット上にサービス、アプリを提供する際に必須の 防具です。その防具が無ければ殺されるのだというくらいの考えでいても、大げさで はない時代です。有名になればなるほど、攻撃もまた増えます。 新しいアプリやサービスで世界に名を轟かせる野心を持つのであれば、攻撃さ れてもびくともしないセキュリティという防具も持ちましょう。 73
© Copyright 2024 Paperzz