Register now for Android Dev Summit 2019!

セキュリティに関するヒント

Android には、アプリのセキュリティ問題の頻度と影響を大幅に削減するセキュリティ機能が組み込まれています。通常は、デフォルトのシステム パーミッションとファイル パーミッションを使用してアプリをビルドすることで、セキュリティに関する難しい判断を下す必要がないように、システムが設計されています。

セキュアなアプリの開発を支援する主なセキュリティ機能は次のとおりです。

  • Android アプリ サンドボックス: アプリのデータやコードの実行を他のアプリから分離。
  • アプリ フレームワーク: 暗号化、パーミッション、セキュア IPC など、一般的なセキュリティ機能が堅牢に実装されています。
  • 各種保護テクノロジー: ASLR、NX、ProPolice、safe_iop、OpenBSD dlmalloc、OpenBSD calloc、Linux mmap_min_addr などのテクノロジーより、一般的なメモリ管理エラーに関連するリスクを軽減します。
  • 暗号化ファイル システム: デバイスの紛失時や盗難時においても、データを保護できます。
  • ユーザーが付与するパーミッション: システム機能やユーザーデータへのアクセスを制限します。
  • アプリ定義パーミッション: アプリ単位でアプリデータを制御します。

このドキュメントで説明している Android のセキュリティのベスト プラクティスについて十分に理解しておくことが重要です。ベスト プラクティスをコーディング手法として常に遵守することにより、ユーザーに悪影響を及ぼすセキュリティ問題を不用意に引き起こす可能性を減らすことができます。

データを保存する

Android アプリのセキュリティにおいて最も重要な注意点は、デバイス上に保存したデータを他のアプリにもアクセスさせるかどうか、という点です。デバイス上にデータを保存する方法としては、主に次の 3 つがあります。

  • 内部ストレージ
  • 外部ストレージ
  • コンテンツ プロバイダ
各アプローチのセキュリティ問題について、以下で説明します。

内部ストレージを使用する

デフォルトでは、アプリが内部ストレージ上に作成したファイルにアクセスできるのは、当該のアプリだけに限られます。Android は、プラットフォーム レベルでこの保護機能を実装しており、ほとんどのアプリはこの機能で十分です。

通常は、IPC ファイルに対して、MODE_WORLD_WRITEABLE モードや MODE_WORLD_READABLE モードを使用するべきではありません。この 2 つのモードは、個々のアプリに対してデータアクセスを制限する機能がなく、データ形式を管理することもできません。他のアプリプロセスとデータを共有する必要がある場合は、コンテンツ プロバイダの使用を検討してください。コンテンツ プロバイダでは、他のアプリに読み取りまたは書き込みのパーミッションを与えたり、状況に応じて動的にパーミッションを付与したりすることができます。

機密データの保護をさらに強化するには、アプリから直接アクセスすることのできないキーを使用してローカル ファイルを暗号化します。たとえば、KeyStore 内にキーを配置し、デバイス上に保存していないユーザー パスワードを使用することで、キーを保護することができます。この方法では、ユーザーのパスワード入力を監視できるルート権限の不正使用からデータを保護することはできませんが、ファイル システムの暗号化を行っていないデバイスの紛失時にデータを保護できます。

外部ストレージを使用する

外部ストレージ(SD カードなど)上に作成されたファイルは、グローバルに読み取りと書き込みが可能です。外部ストレージは、ユーザーが取り外したり、アプリが編集したりする可能性があるため、外部ストレージを使用して機密情報を保存しないでください。

信頼できないソースから取得したデータの場合と同じように、外部ストレージのデータを処理する際は、入力検証を実行する必要があります。動的読み込みの前に外部ストレージ上に実行ファイルやクラスファイルを保存しないようにしてください。外部ストレージから実行ファイルを取得するアプリの場合は、動的読み込みの前に、ファイルに署名をして、暗号により検証する必要があります。

コンテンツ プロバイダを使用する

コンテンツ プロバイダは、構造化されたストレージ メカニズムを提供します。コンテンツ プロバイダの利用を自分のアプリだけに制限したり、エクスポートして、他のアプリからのアクセスを許可したりすることができます。他のアプリから ContentProvider にアクセスできないようにするには、アプリ マニフェスト内でコンテンツ プロバイダを android:exported=false とマーキングします。そうしない場合は、android:exported 属性を true に設定して、保存データに他のアプリがアクセスできるようにします。

エクスポートして他のアプリで使用する ContentProvider を作成する際は、単一のパーミッションで読み取りと書き込みの両方を許可するのか、読み取りと書き込みをそれぞれ個別のパーミッションで許可するのかを、マニフェスト内で指定します。パーミッションを付与する際は、タスクの完了に直接必要となるパーミッションだけに制限するようにしてください。通常は、パーミッションを取り消して、既存のユーザーに影響を及ぼすよりも、後でパーミッションを追加して、新しい機能をエクスポーズする方が簡単です。

自分のアプリ間だけでデータを共有するためにコンテンツ プロバイダを使用する場合は、android:protectionLevel 属性を signature 保護に設定して使用することをおすすめします。「signature」パーミッションはユーザーの承認が不要なため、ユーザー エクスペリエンスが向上し、コンテンツ プロバイダのデータにアクセスするアプリが同じキーで署名されていれば、そのデータに対するアクセスを細かく制御することができます。

また、コンテンツ プロバイダは、android:grantUriPermissions 属性を宣言して、コンポーネントをアクティブ化する Intent オブジェクト内で FLAG_GRANT_READ_URI_PERMISSION フラグと FLAG_GRANT_WRITE_URI_PERMISSION フラグを使用することで、詳細なアクセスを指定することができます。パーミッションの範囲は、 <grant-uri-permission> によってさらに制限することができます。

コンテンツ プロバイダにアクセスする際は、信頼されていないソースからの SQL インジェクションを防止するために、パラメータ化したクエリメソッド(query()update()delete() など)を使用してください。ただし、ユーザーデータを連結して作成した selection 引数をパラメータ化メソッドに送信する場合は、パラメータ化メソッドを使用しても不十分です。

書き込みパーミッションのセキュリティについて正しく理解するようにしてください。書き込みパーミッションでは、クリエイティブな WHERE 句を使用して結果を解析することによって一部のデータを確認できるようにする SQL ステートメントが許可されています。たとえば、攻撃者は、「特定の電話番号がすでに存在している場合に限り、通話ログの行を編集する」という方法で、通話ログ内にその電話番号が存在するかどうかを探ることがあります。コンテンツ プロバイダのデータが予測しやすい構造となっている場合、書き込みパーミッションは、結果的に読み取りパーミッションと書き込みパーミッションの両方を提供しているのと変わらないことがあります。

パーミッションを使用する

Android はアプリをサンドボックス化して分離するため、各アプリはリソースとデータを明示的に共有する必要があります。ベーシック サンドボックスでは提供されない追加機能(例: カメラなどのデバイス機能へのアクセス)が必要な場合は、パーミッションを宣言します。

パーミッションをリクエストする

アプリがリクエストするパーミッションの数は、最小限に抑える必要があります。機密にかかわるパーミッションへのアクセスを制限することで、そのようなパーミッションが不正使用されるリスクを減らし、アプリの普及を促進し、攻撃に対するアプリの脆弱性を抑えることができます。一般的に、アプリの機能に不要なパーミッションは、リクエストしないでください。アプリの実行に必須の機能がある場合は、マニフェスト ファイル内の <uses-feature> 要素を使用して宣言します。

可能であれば、いかなるパーミッションも必要としないような形でアプリを設計してください。たとえば、固有の ID を作成するためにデバイス情報へのアクセスをリクエストするのではなく、アプリの GUID を作成します(ユーザーデータを扱うを参照)。また、パーミッションが必要な外部ストレージを使用するのではなく、内部ストレージにデータを保存します。

パーミッションをリクエストするほかにも、アプリで <permission> 要素を使用して、ContentProvider などの IPC を保護することができます。IPC は高いセキュリティを必要とし、他のアプリでの使用を意図しています。一般に、パーミッションはユーザーの混乱を招きやすいため、可能な限りユーザー付与のパーミッション以外のアクセス制御の使用をおすすめします。たとえば、同一のデベロッパーが提供するアプリ間の IPC 通信のパーミッションには、「signature」保護レベルを使用することおすすめします。

パーミッションで保護されたデータを漏洩させないでください。データにアクセスするパーミッションを付与されたアプリだけが利用できる IPC を経由して、アプリがデータをエクスポーズすると、データの漏洩が発生します。アプリの IPC インターフェースのクライアントは、同じデータアクセス パーミッションを持っていない場合があります。この問題の発生頻度や潜在的な影響については、USENIX が発行した研究論文「Permission Re-Delegation: Attacks and Defenses」をご覧ください。

パーミッションを作成する

一般的に、セキュリティ要件を満たしつつ、定義するパーミッションの数を可能な限り抑える必要があります。システム定義パーミッションでさまざまな状況に対応できるため、ほとんどのアプリで、新たなパーミッションを作成することはありません。必要に応じて、既存のパーミッションを使用して、アクセス チェックを実行してください。

新しいパーミッションを作成する必要がある場合は、「signature」保護レベルでタスクを完了できるかどうかを検討してください。「signature」パーミッションは、ユーザーの承認を必要とせず、パーミッション チェックを実行するアプリと同じデベロッパーによって署名されたアプリのアクセスだけを許可します。それでも新しいパーミッションが必要な場合は、アプリ マニフェスト内で <permission> 要素を使用して宣言します。新しいパーミッションを使用するアプリは、それぞれのマニフェスト ファイル内に <uses-permission> 要素を追加することで、新しいパーミッションを参照することができます。また、addPermission() メソッドを使用すると、パーミッションを動的に追加することができます。

「dangerous」保護レベルのパーミッションを作成する場合は、さまざまな点を考慮する必要があります。

  • パーミッションには、ユーザーがセキュリティに関する判断を下す必要があることを簡潔に説明する文字列を含める必要があります。
  • パーミッション文字列は、さまざまな言語にローカライズする必要があります。
  • パーミッションがわかりにくい場合や、危険だと判断した場合、ユーザーは、アプリのインストールを拒否することができます。
  • パーミッションのクリエータがインストールされていない場合、アプリは、パーミッションをリクエストできます。

このように、デベロッパーは技術面以外で大きな難題に直面し、ユーザーも混乱するおそれがあります。そのため、「dangerous」パーミッション レベルは使用しないことをおすすめします。

ネットワークを使用する

ユーザーの個人的なデータが送信される場合もあるため、ネットワーク トランザクションには、本質的にセキュリティ上のリスクがあります。近年では、特にモバイル デバイスがネットワーク トランザクションを実行する場合など、モバイル デバイスのプライバシー問題に対するユーザーの意識は高まっています。そのため、ユーザーデータを常にセキュアに保持するため、すべてのベスト プラクティスをアプリに実装することが必要です。

IP ネットワークを使用する

ネットワークに関しては、Android 上でも、他の Linux 環境でも、それほど大きな違いはありません。重要な注意事項としては、機密データには必ず HttpsURLConnection などの適切なプロトコルを使用して、セキュアなウェブ トラフィックを実現する必要があります。モバイル デバイスは、公衆 Wi-Fi アクセス ポイントなど、セキュアではないネットワークに頻繁に接続するため、サーバーが HTTPS をサポートしている場合は常に、HTTP ではなく、HTTPS を使用してください。

SSLSocket クラスを使用すると、暗号化された認証済みのソケットレベル通信を簡単に実装することができます。Android デバイスが Wi-Fi 経由で非セキュア ワイヤレス ネットワークにアクセスする頻度を考慮すると、ネットワーク通信を行うすべてのアプリでセキュア ネットワークを使用することを強くおすすめします。

アプリによっては、機密性の高い IPC の処理に localhost ネットワーク ポートを使用していることがあります。このようなインターフェースは、デバイス上の他のアプリでもアクセスできるため、このアプローチは使用しないでください。代わりに、Service など、認証が可能な Android IPC メカニズムを使用してください。INADDR_ANY にバインドする方法は、ループバックの使用よりも不適切です。アプリがどこからでもリクエストを受け取ってしまうおそれがあります。

HTTP などの非セキュア プロトコルからダウンロードされたデータを信頼しないようにしてください。WebView への入力や、HTTP に対して発行されたインテントへのレスポンスも検証する必要があります。

電話機能ネットワークを使用する

SMS プロトコルは、主にユーザー間の通信を目的に設計されており、アプリのデータ転送には適していません。SMS には制約があるため、ウェブサーバーからユーザー デバイス上のアプリにデータを送信する場合は、Google Cloud Messaging(GCM)や IP ネットワークを使用してください。

SMS は暗号化されず、ネットワークとデバイスのどちらにおいても強力な認証が行われません。特に、SMS レシーバは、悪意のあるユーザーがアプリに SMS を送信する可能性があることを想定する必要があります。認証されていない SMS データを使用して、機密性の高いコマンドを実行しないでください。また、SMS は、ネットワーク上でなりすましや傍受の被害を受ける可能性があります。Android デバイス上では、SMS メッセージはブロードキャスト インテントとして送信されるため、READ_SMS パーミッションを持つ他のアプリによって読み取りやキャプチャが行われる可能性があります。

入力検証を実行する

アプリを実行するプラットフォームの種類を問わず、不十分な入力検証は、アプリに影響を及ぼすセキュリティ問題として広まっています。Android は、アプリが入力検証の問題にさらされるリスクを低減する対策をプラットフォーム レベルで備えています。この機能を、可能な限り利用するようにしてください。また、タイプセーフな言語を選択することで、入力検証の問題が発生する可能性を抑えることもできます。

ネイティブ コードを使用している場合、ファイルから読み取ったデータや、ネットワーク経由で受信したデータ、IPC から受信したデータにより、セキュリティ問題が発生する可能性があります。よくある問題としては、バッファ オーバーフロー解放後使用(Use After Free)off-by-one エラーなどがあります。Android は、このようなエラーの脆弱性を抑えるため、ASLRDEP など、さまざまなテクノロジーに対応していますが、こうしたテクノロジーは根本的な問題を解決するものではありません。このような脆弱性は、ポインタの処理やバッファの管理を注意深く行うことで回避できます。

また、JavaScript や SQL といった文字列ベースの動的な言語においても、エスケープ文字やスクリプト インジェクションが原因で入力検証の問題が発生する可能性があります。

SQL データベースやコンテンツ プロバイダに送信するクエリ内でデータを使用する場合に、SQL インジェクションが発生することがあります。最善の対応策は、上記のコンテンツ プロバイダ セクションで説明したように、パラメータ化クエリを使用することです。また、パーミッションを読み取り専用や書き込み専用に制限することでも、SQL インジェクション問題の発生を抑えることができます。

上記のセキュリティ機能を利用できない場合は、堅牢な構造のデータ形式を使用し、データが目的の形式に沿っているか確認してください。文字のブラックリスト化や置き換えは有効な場合もありますが、実際には、このような方法によってエラーが生じることもあるため、可能であれば避けてください。

ユーザーデータを扱う

一般に、ユーザーデータのセキュリティを保護する最適な方法は、機密データやユーザーの個人データにアクセスする API の使用を最小限に抑えることです。ユーザーデータへのアクセスが可能なときに、情報の保存や送信を避けることができる場合は、データの保存や送信を行わないでください。また、データをハッシュや非可逆形式で使用してアプリのロジックを実装できないか検討してください。たとえば、アプリにおいて、メールアドレスのハッシュを主キーとして使用することで、メールアドレスの送信や保存を回避することができます。これにより、不注意によるデータ流出の可能性が減るほか、攻撃者によるアプリ悪用の可能性も減ります。

アプリがパスワードやユーザー名などの個人情報にアクセスする場合、一部の国や地域では、ユーザーの個人データの使用や保存についてプライバシー ポリシーで説明する法的な義務が発生することがあります。ユーザーデータへのアクセスを最小限に抑えるためのセキュリティ ベスト プラクティスを実装することで、コンプライアンスを簡素化できます。

また、アプリから不注意で個人情報が外部(広告用のサードパーティ コンポーネントや、アプリが使用しているサードパーティ サービスなど)に流出するおそれがないか確認する必要があります。コンポーネントやサービスが個人情報を要求する理由が不明な場合は、個人情報を提供しないでください。一般に、アプリによる個人情報へのアクセスを削減すると、この分野の問題が生じる可能性が減ります。

機密データへのアクセスが必要な場合、情報をサーバーに送信する必要があるのか、クライアント上で処理を実行できるのか確認してください。ユーザーデータの送信を避けるため、機密データを使用するコードをクライアント上で実行することをおすすめします。また、制限の緩い IPC や、グローバルに書き込み可能なファイル、ネットワーク ソケットなどを通じて、ユーザーデータがデバイス上の他のアプリに不注意で流出することのないようにしてください。制限の緩い IPC は、パーミッションで保護されたデータの漏洩の特別なケースです(パーミッションをリクエストするを参照)。

GUID が必要な場合は、一意の長い番号を生成して保存します。個人情報に関連付けられている可能性がある電話番号や IMEI などの電話 ID は、使用しないでください。詳細については、Android デベロッパー ブログをご覧ください。

オンデバイス ログに書き込むときは注意してください。Android では、ログは共有リソースとなっており、READ_LOGS パーミッションを持つアプリであればアクセスすることができます。スマートフォンのログデータは一時的なものであり、再起動すると削除されますが、不適切な形でユーザー情報をログに記録すると、不注意でユーザーデータが他のアプリに漏洩する可能性があります。実稼働アプリの場合、PII をログに記録しないだけでなく、ログの使用自体を制限するようにしてください。簡単に実装するには、デバッグフラグと、簡単に設定可能なログレベルのカスタム Log クラスを使用します。

WebView を使用する

WebView は、HTML や JavaScript を含むウェブ コンテンツを読み込むことがあるため、不適切に使用すると、クロスサイト スクリプティング(JavaScript インジェクション)など、一般的なウェブ セキュリティ問題を引き起こす可能性があります。Android は、WebView の機能を、アプリが必要とする最小限のレベルに制限することによって、このような問題の発生を抑制するさまざまなメカニズムを備えています。

アプリが WebView 内で JavaScript を直接使用しない場合は、setJavaScriptEnabled() を呼び出さないでください。一部のサンプルコードではこのメソッドが使用されていますが、コードを実稼働アプリで再利用する際は、このメソッドの呼び出しが不要であれば削除してください。デフォルトでは、WebView は JavaScript を実行しないようになっているため、クロスサイト スクリプティングが生じる可能性はありません。

addJavaScriptInterface() を使用すると、通常は Android アプリ用に予約されている処理を JavaScript が呼び出せるようになるため、使用する際は十分に注意してください。使用する場合は、すべての入力を信頼できるウェブページに対してのみ addJavaScriptInterface() をエクスポーズしてください。信頼されていない入力が許可された場合、信頼されていない JavaScript によってアプリ内の Android メソッドが呼び出されるおそれがあります。通常は、アプリの APK 内に含まれている JavaScript に限り、addJavaScriptInterface() をエクスポーズすることをおすすめします。

アプリが WebView を使用して機密データにアクセスする場合は、clearCache() メソッドを使用して、ローカルに保存されたファイルをすべて削除することをおすすめします。また、no-cache などのサーバーサイド ヘッダーを使用して、アプリが特定のコンテンツをキャッシュすることのないように指示します。

Android 4.4(API レベル 19)よりも前のプラットフォームを実行しているデバイスは、さまざまなセキュリティ問題がある webkit のバージョンを使用しています。対応策としては、このようなデバイス上でアプリが実行された場合に、信頼できるコンテンツだけを WebView オブジェクトが表示するようにします。また、アプリが SSL の潜在的な脆弱性にさらされないように、アップデート可能なセキュリティ Provider オブジェクトを使用する必要があります(SSL の脆弱性攻撃から保護するためにセキュリティ プロバイダをアップデートするを参照)。アプリがオープンウェブのコンテンツをレンダリングする必要がある場合、独自のレンダラを用意し、最新のセキュリティ パッチを適用してアプリを最新の状態に保つことをおすすめします。

認証情報を扱う

フィッシング攻撃の検出率を上げ、成功率を下げるには、ユーザー認証情報を求める頻度を最小限に抑えます。その代わり、認証トークンを使用し、認証トークンを更新します。

可能な限り、デバイス上にユーザー名やパスワードを保存しないでください。代わりに、ユーザーが入力したユーザー名とパスワードを使用して初期認証を行い、短期間だけ存続するサービス単位の認証トークンを使用してください。

複数のアプリがアクセスできるサービスの場合、AccountManager を使用してアクセスする必要があります。可能であれば、AccountManager クラスを使用して、クラウドベースのサービスを呼び出し、デバイス上にはパスワードを保存しないようにします。

AccountManager を使用して Account を取得した後、認証情報を渡す前に CREATOR を使用することで、正しくないアプリに認証情報を誤って渡すことがないようにします。

認証情報を使用するのがデベロッパーのアプリだけの場合は、checkSignature() を使用して、AccountManager にアクセスするアプリを確認することができます。また、認証情報を使用するのが 1 つのアプリだけの場合は、KeyStore を使用して保存することができます。

暗号化を使用する

Android は、データの分離や、ファイル システム全体の暗号化のサポート、セキュア通信チャネルの提供に加えて、暗号化を使用してデータを保護するためのさまざまなアルゴリズムを備えています。

一般的に、ソフトウェアがどの Java 暗号化アーキテクチャ(JCA)セキュリティ プロバイダを使用しているのかを把握しておく必要があります。ユースケースをサポートできる最も高いレベルの既存のフレームワーク実装を使用するようにしてください。可能であれば、Google が提供するプロバイダを、Google が指定した順序で使用します。既知の場所からファイルをセキュアに取得する必要がある場合は、シンプルな HTTPS URI が適切であり、暗号化の知識も必要ありません。セキュアなトンネルが必要な場合は、独自のプロトコルを作成するのではなく、HttpsURLConnection または SSLSocket を使用することをおすすめします。SSLSocket を使用する場合、ホスト名の検証は実行されません。SSLSocket を直接使用する場合の注意事項をご覧ください。

独自のプロトコルの実装が必要な場合は、独自の暗号化アルゴリズムを実装しないことをおすすめします。Cipher クラスで提供されている AES 実装や RSA 実装など、既存の暗号化アルゴリズムを使用してください。また、以下のベスト プラクティスを守るようにしてください。

  • 商用の場合は、256 ビット AES を使用します(利用できない場合は、128 ビット AES を使用します)。
  • 楕円曲線(EC)暗号化の場合は、224 ビットまたは 256 ビットの公開鍵サイズを使用します。
  • CBC、CTR、GCM ブロックモードを使用するタイミングを把握します。
  • CTR モードでは、IV / カウンタの再利用を避けます。必ず、暗号に基づいてランダムになるようにします。
  • 暗号化を使用する際は、次のいずれかの機能を備えた CBC モードまたは CTR モードを使用して整合性を実装します。
    • HMAC-SHA1
    • HMAC-SHA-256
    • HMAC-SHA-512
    • GCM モード

セキュア乱数生成ツール SecureRandom を使用して、KeyGenerator が生成した暗号化鍵を初期化します。セキュア乱数生成ツールが生成していない鍵を使用すると、アルゴリズムの強度が大幅に低下し、オフライン攻撃を受けるおそれがあります。

鍵を保存して繰り返し使用する必要がある場合は、KeyStore など、暗号化鍵の長期的な保存と取得を可能にするメカニズムを使用してください。

プロセス間通信(IPC)を使用する

アプリによっては、ネットワーク ソケットや共有ファイルなど、Linux の従来の手法を用いて IPC の実装を試みているものがあります。しかし、代わりに、IntentBinderMessengerServiceBroadcastReceiver など、IPC 向けの Android システム機能を使用するようにしてください。Android の IPC メカニズムを使用すると、IPC に接続するアプリの ID を確認し、各 IPC メカニズムにセキュリティ ポリシーを設定することができます。

IPC メカニズム間でさまざまなセキュリティ要素が共有されます。他のアプリによる使用を意図していない IPC メカニズムの場合、<service> 要素など、コンポーネントのマニフェスト要素内で android:exported 属性を false に設定します。同じ UID 内の複数のプロセスで構成されるアプリの場合や、開発の終盤で、実際には機能を IPC としてエクスポーズしないことを決定したが、コードを書き換えない場合は、このように設定すると便利です。

他のアプリが IPC にアクセスできるようにする場合、<permission> 要素を使用して、セキュリティ ポリシーを適用することができます。同一の鍵で署名した複数のアプリの間に IPC がある場合、android:protectionLevel 内で signature レベル パーミッションを使用することをおすすめします。

インテントを使用する

アクティビティやブロードキャスト レシーバの場合、Android 内の非同期 IPC には、インテント メカニズムを使用することをおすすめします。アプリの要件に応じて、個々のアプリ コンポーネントに対して sendBroadcast() や、sendOrderedBroadcast()、明示的インテントを使用できます。セキュリティ上の理由から、明示的インテントをおすすめします。

注: Service へのバインドにインテントを使用する場合は、明示的インテントを使用して、アプリがセキュアであることを確認してください。暗黙的インテントを使用してサービスを開始すると、どのサービスがインテントに応答するのかを把握できず、ユーザーにはどのサービスが開始するのかがわからないため、セキュリティ上の危険が伴います。Android 5.0(API レベル 21)以降では、暗黙的インテントを使用して bindService() を呼び出すと、システムから例外がスローされます。

順序付きのブロードキャストは、レシーバによって「消費」される場合があるため、すべてのアプリに配信されるとは限りません。送信するインテントを特定のレシーバに配信する必要がある場合は、名前に基づいてレシーバを宣言する明示的インテントを使用する必要があります。

インテントの送信元は、メソッドの呼び出しで非 null パーミッションを指定することで、レシーバがパーミッションを保持しているか検証することができます。そのパーミッションを保持しているアプリのみがインテントを受信します。ブロードキャスト インテント内のデータが機密データの可能性がある場合、悪意のあるアプリが適切なパーミッションなしに登録してメッセージを受信することのないように、パーミッションを適用することをおすすめします。また、このような状況では、ブロードキャストを実行するのではなく、レシーバを直接呼び出すことをおすすめします。

注: インテント フィルタはセキュリティ機能ではありません。明示的インテントでコンポーネントを呼び出すことはできますが、インテント フィルタに適合するデータがコンポーネントに含まれていないことがあります。インテント レシーバ内で入力検証を実行して、呼び出されるレシーバ、サービス、アクティビティに対して、入力が適切にフォーマットされているか確認する必要があります。

サービスを使用する

Service は通常、他のアプリが使用する機能を提供する目的で用いられます。各サービスクラスは、対応する <service> 宣言をマニフェスト ファイル内に持つ必要があります。

デフォルトでは、サービスはエクスポートされず、他のアプリから呼び出すことはできません。ただし、サービスの宣言にインテント フィルタを追加すると、そのサービスはデフォルトでエクスポートされます。想定どおりに機能させるには、android:exported 属性を明示的に宣言することをおすすめします。また、android:permission 属性を使用して、サービスを保護することもできます。サービスを保護した場合、他のアプリは、対応する <uses-permission> 要素をマニフェスト内で宣言して、サービスの開始や停止、サービスへのバインドを行う必要があります。

注: Android 5.0(API レベル 21)以降をターゲットとしているアプリの場合、JobScheduler を使用して、バックグラウンド サービスを実行する必要があります。JobScheduler の詳細については、API-reference documentation をご覧ください。

サービスは、IPC 呼び出しの実装を実行する前に checkCallingPermission() を呼び出すことで、個々の IPC 呼び出しをパーミッションで保護することができます。通常は、マニフェスト内で宣言を行うタイプのパーミッションの方がミスが少なくなるため、宣言型のパーミッションを使用してください。

注: クライアントとサーバーのパーミッションを混同しないでください。呼び出されたアプリが適切なパーミッションを持っているか確認し、呼び出し元のアプリに同じパーミッションが付与されているか確認します。

バインダ インターフェースやメッセンジャー インターフェースを使用する

Android で RPC スタイルの IPC を実行する場合は、BinderMessenger のメカニズムを使用することをおすすめします。適切に定義されたインターフェースを備えており、必要に応じて、エンドポイントの相互認証が可能になります。

アプリ インターフェースを設計する際は、インターフェース固有のパーミッション チェックを必要としないような形で設計してください。Binder オブジェクトおよび Messenger オブジェクトは、アプリ マニフェスト内では宣言されないため、宣言型のパーミッションを直接適用することはできません。通常は、ServiceActivity が実装されているアプリ マニフェスト内で宣言されているパーミッションを継承します。認証やアクセス制御を必要とするインターフェースを作成する場合、Binder インターフェースや Messenger インターフェース内で、明示的に各種コントロールをコードとして追加する必要があります。

アクセス制御を必要とするインターフェースを提供する場合、checkCallingPermission() を使用して、呼び出し元が必要なパーミッションを持っているか検証します。アプリの ID は他のインターフェースに渡されるため、呼び出し元に代わってサービスにアクセスする前にパーミッションを検証することが特に重要になります。Service が提供するインターフェースを呼び出す場合、当該のサービスにアクセスするパーミッションがないと、bindService() の呼び出しに失敗する可能性があります。独自のアプリがローカルで提供するインターフェースを呼び出す場合、内部セキュリティ チェックを満たすために、アプリのパーミッションに対して呼び出し元のパーミッションをマスキングする clearCallingIdentity() を使用すると便利です。restoreCallingIdentity() メソッドを使用することで、後で呼び出し元のパーミッションを復元できます。

サービスを使用して IPC を実行する方法については、バインドされたサービスをご覧ください。

ブロードキャスト レシーバを使用する

BroadcastReceiver は、Intent が開始した非同期リクエストを処理します。

デフォルトでは、レシーバはエクスポートされ、他のアプリから呼び出すことができます。他のアプリが BroadcastReceiver を使用する場合は、アプリ マニフェスト内で <receiver> 要素を使用することで、レシーバにセキュリティ パーミッションを適用することができます。これにより、適切なパーミッションを持たないアプリは、インテントを BroadcastReceiver に送信できなくなります。

コードを動的に読み込む

アプリ APK の外部からはコードを読み込まないことを強くおすすめします。外部からコードを読み込むと、コード インジェクションやコードの改ざんによってアプリが不正使用される可能性が大幅に高まります。また、バージョン管理やアプリのテストが複雑になります。アプリの動作を検証できなくなることがあるため、一部の環境では禁止されている場合があります。

アプリが動的にコードを読み込む場合、動的に読み込んだコードに関して特に注意する必要があるのは、アプリ APK と同じセキュリティ パーミッションでコードが実行されるという点です。ユーザーは、デベロッパーの身元に基づいてアプリをインストールするかどうかを判断し、「アプリ内で実行されるコードは、動的に読み込まれるコードを含め、すべてそのデベロッパーが提供している」と考えます。

動的に読み込むコードは、検証可能なソースから提供されている必要があり、その点がセキュリティ上の大きな問題となります。モジュールが直接 APK 内に格納されている場合、他のアプリはこのモジュールを変更できません。コードがネイティブ ライブラリであっても、DexClassLoader を使用して読み込まれたクラスであっても、この点については同じです。暗号化されていないプロトコルを通じてネットワークからダウンロードしたコードや、グローバルに書き込み可能な場所(外部ストレージなど)からダウンロードしたコードなど、セキュアでない場所から取得したコードを読み込もうとするアプリが数多く存在します。セキュアでない場所では、ネットワーク上の第三者が送信中のコンテンツを改変したり、ユーザー デバイス上の別のアプリがデバイスのコンテンツを編集したりする可能性があります。

仮想マシンのセキュリティ

Dalvik は、Android のランタイム仮想マシン(VM)です。Dalvik は Android 専用として開発されていますが、他の仮想マシンにおけるコードの安全性に関する注意事項は、その多くが Android にも当てはまります。一般的に、仮想マシンに関するセキュリティ問題について、デベロッパー自身が心配する必要はありません。アプリはセキュアなサンドボックス環境内で実行されるため、システム内の他のプロセスがアプリのコードや個人データにアクセスすることはできません。

仮想マシンのセキュリティ問題について詳細に知りたい場合は、この問題に関する既存のリソースやドキュメントを十分に理解しておくことをおすすめします。一般的なリソースとしては、以下をご覧ください。

このドキュメントでは、Android に固有の領域や、他の VM 環境とは異なる領域について重点的に説明しています。他の環境で VM 関連のプログラミングの経験があるデベロッパーの場合、Android アプリを作成する際に、以下の 2 つの大きな相違点について確認してください。

  • 一部の仮想マシン(JVM や .NET ランタイムなど)は、セキュリティ境界として機能し、基盤オペレーティング システムの機能からコードを分離します。Android の場合、Dalvik VM はセキュリティ境界として機能しません。OS レベルでアプリ サンドボックスが実装されているため、セキュリティの制約なしに、同じアプリのネイティブ コードを Dalvik と相互運用することができます。
  • モバイル デバイスはストレージに制約があるため、デベロッパーは通常、モジュール式アプリをビルドし、動的なクラス読み込みを使用します。その際、アプリのロジックを取得するソースと、そのソースをローカルで保存する場所の両方について考慮する必要があります。セキュアではないネットワーク ソースや外部ストレージなど、検証されていないソースに対して動的なクラス読み込みは使用しないでください。このような場所のコードは改変されていて悪意のある動作が組み込まれている可能性があります。

ネイティブ コードのセキュリティ

一般的に、Android NDK でネイティブ コードを使用するのではなく、アプリの開発には Android SDK を使用してください。ネイティブ コードでビルドされたアプリは、複雑で、移植性が低く、バッファのオーバーフローなど、一般的なメモリ破損エラーが含まれる可能性が高くなります。

Android は Linux カーネルを使用して構築されているため、Linux 開発のセキュリティ ベスト プラクティスに精通していると、ネイティブ コードを使用する際に特に役立ちます。Linux のセキュリティ プラクティスについては、「Secure Programming HOWTO - Creating Secure Software」をご覧ください。

Android 環境と大部分の Linux 環境との重要な違いは、アプリ サンドボックスです。Android では、ネイティブ コードで記述されたアプリを含め、すべてのアプリがアプリ サンドボックス内で実行されます。Linux に精通しているデベロッパーであれば、「すべてのアプリに、極めて限定的なパーミッションを持つ一意の UID が付与される」と理解することで、最も基本的なレベルでこの仕組みを理解できます。詳細については、Android のセキュリティの概要をご覧ください。ネイティブ コードを使用する場合でも、アプリのパーミッションについて十分に理解しておく必要があります。