Android には、アプリのセキュリティ問題の発生頻度とその影響を格段に軽減するセキュリティ機能が組み込まれています。通常は、デフォルトのシステム権限とファイルへのアクセス許可を使用してアプリを構築することで、セキュリティに関する難しい判断を下す必要がないように、システムが設計されています。
セキュアなアプリの開発を支援する主なセキュリティ機能は次のとおりです。
- Android アプリケーション サンドボックス。アプリデータやコードの実行を他のアプリから分離。
- 暗号、アクセス権限、セキュアなプロセス間通信(IPC)などの一般的なセキュリティ機能が堅牢に実装されたアプリケーション フレームワーク。
- アドレス空間配置のランダム化(ASLR)、no-execute(NX)、ProPolice、safe_iop、OpenBSD の
dlmalloc
およびcalloc
、Linux のmmap_min_addr
などの技術により、一般的なメモリ管理エラーのリスクを軽減。 - ユーザーが権限を付与する仕組み。システム機能やユーザーデータへのアクセスを制限。
- アプリで定義する権限。アプリのデータをアプリごとに制御。
このページで説明する Android セキュリティのベスト プラクティスについて十分に理解しておくことが重要です。ベスト プラクティスをコーディング手法として常に採用することにより、ユーザーに悪影響を及ぼすセキュリティ問題を意図せず引き起こす可能性を避けられます。
認証
認証は、数多くの主要なセキュリティ オペレーションの前提条件です。ユーザーデータ、アプリの機能など、保護されたアセットへのアクセスを制御するには、Android アプリに認証を追加する必要があります。
アプリを認証情報マネージャーと統合することで、ユーザーの認証エクスペリエンスを改善できます。認証情報マネージャーは、パスキー、パスワード、フェデレーション ログイン ソリューション(「Google でログイン」など)といった、ほとんどの主要な認証方法の API サポートを統合した Android Jetpack ライブラリです。
アプリのセキュリティをさらに強化するには、指紋スキャン、顔認識などの生体認証方法を追加することをご検討ください。たとえば、金融、医療、ID 管理系のアプリでは、生体認証を追加することをおすすめします。
Android の自動入力フレームワークを使用することで、登録とログインのプロセスを簡易化し、入力ミスやユーザーの負担を軽減できます。自動入力はパスワード マネージャーと統合されるため、ユーザーは、保管と取得が簡単かつ安全な、複雑でランダム化されたパスワードを選択できます。
データ ストレージ
Android 上のアプリのセキュリティにつきものの課題と言えば、デバイスに保存したデータに他のアプリからのアクセスを許可するかどうか、という点です。デバイス上にデータを保存する方法としては、主に次の 3 つがあります。
- 内部ストレージ
- 外部ストレージ
- コンテンツ プロバイダ
各アプローチのセキュリティ問題について、以下で説明します。
内部ストレージ
デフォルトでは、内部ストレージ上に作成したファイルにアクセスできるのは、作成元のアプリに限られます。Android は、プラットフォーム レベルでこの保護機能を実装しており、ほとんどのアプリはこの機能で十分です。
非推奨の MODE_WORLD_WRITEABLE
モードと MODE_WORLD_READABLE
モードを IPC ファイルに使用しないようにします。これらのモードには、特定のアプリに対するデータアクセスを制限する機能はなく、データ形式の制御も行われません。他のアプリプロセスとデータを共有する必要がある場合は、コンテンツ プロバイダを使用することをおすすめします。コンテンツ プロバイダであれば、他のアプリに対して読み取り権限や書き込み権限を付与したり、状況に応じて動的に権限を付与したりすることができます。
外部ストレージ
外部ストレージ(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 はアプリをサンドボックス化して分離するため、各アプリはリソースとデータを明示的に共有する必要があります。ベーシック サンドボックスでは提供されない追加機能(例: カメラなどのデバイス機能へのアクセス)が必要な場合は、権限を宣言します。
権限のリクエスト
アプリがリクエストする権限の数を最小限に抑えます。機密情報にかかわる権限へのアクセスを制限することで、そのような権限が不正使用されるリスクを減らしてアプリの普及を促進し、攻撃に対するアプリの脆弱性を抑えることができます。一般的に、アプリの機能に不要な権限はリクエストしないでください。詳しくは、アプリで権限を宣言する必要があるかどうかを評価するをご覧ください。
可能であれば、権限を必要としないようにアプリを設計してください。たとえば、アプリ用の固有の ID を作成するためにデバイス情報へのアクセスをリクエストするのではなく、UUID を作成します(詳しくは、ユーザーデータに関するセクションをご覧ください)。また、権限が必要な外部ストレージではなく、内部ストレージにデータを保存します。
権限をリクエストしなくても、アプリ内で <permission>
要素を使用することで、ContentProvider
など、他のアプリにも利用され、高度なセキュリティを必要とする IPC を保護できます。一般に、ユーザーに権限の同意を得る方法はユーザーの混乱を招きやすいため、可能な限りそれ以外のアクセス制御方式を使用することをおすすめします。たとえば、同一のデベロッパーが提供するアプリ間の IPC の権限には、「signature」保護レベルを使用することを検討してください。
権限で保護されているデータが流出しないようにしてください。該当のデータにアクセスする権限を付与されているだけでアプリが IPC を利用できる場合に、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 メカニズムを使用してください。不特定の IP アドレス INADDR_ANY
にバインドすると、アプリは任意の IP アドレスからリクエストを受信できるため、ループバックを使用する場合よりもリスクが高まります。
HTTP などの安全ではないプロトコルからダウンロードされたデータを信頼しないようにしてください。たとえば、WebView
での入力や、HTTP に対して発行されたインテントへのすべてのレスポンスも検証する必要があります。
電話ネットワーク
ショート メッセージ サービス(SMS)プロトコルは、主にユーザー間の通信を目的に設計されており、アプリのデータ転送には適していません。SMS には制約があるため、ウェブサーバーからユーザー デバイス上のアプリにデータ メッセージを送信する場合は、Firebase Cloud Messaging(FCM)と IP ネットワークを使用することをおすすめします。
SMS は暗号化されず、ネットワークとデバイスのどちらにおいても強力な認証が行われません。特に、SMS レシーバは、悪意のあるユーザーがアプリに SMS を送信する可能性があることを想定しておく必要があります。認証されていない SMS データに基づいて、機密性の高いコマンドを実行しないでください。また、SMS はネットワークでなりすましや傍受の被害を受ける可能性があるので注意してください。Android デバイス上では、SMS メッセージはブロードキャスト インテントとして送信されるため、READ_SMS
権限を持つ他のアプリによって読み取られたり取得されたりする可能性があります。
入力検証
アプリを実行するプラットフォームの種類を問わず、入力検証が十分行われていないことが、アプリに影響を及ぼすセキュリティ上のよくある問題の一つとなっています。Android には、アプリが入力検証の問題にさらされるリスクを軽減するための、プラットフォーム レベルの対策が用意されているため、可能な限り利用することをおすすめします。また、入力検証で問題が発生する可能性を下げるために、タイプセーフな言語を使用することも検討してください。
ネイティブ コードを使用している場合、ファイルから読み取ったデータや、ネットワーク経由で受信したデータ、IPC から受信したデータにより、セキュリティ問題が発生する可能性があります。よくある問題としては、バッファ オーバーフロー、解放後のメモリ使用(Use After Free)、off-by-one エラーなどがあります。Android は、ASLR やデータ実行防止(DEP)など、このようなエラーの悪用を軽減するさまざまな技術を提供していますが、根本的な問題を解決するものではありません。このような脆弱性は、ポインタの処理やバッファの管理を注意深く行うことで回避できます。
また、JavaScript や SQL といった文字列ベースの動的な言語においても、エスケープ文字やスクリプト インジェクションが原因で入力検証の問題が生じます。
SQL データベースやコンテンツ プロバイダに送信されるクエリ内のデータを使用する場合に、SQL インジェクションが問題となる可能性があります。最善の対応策は、コンテンツ プロバイダに関するセクションで説明しているように、パラメータ化されたクエリを使用することです。また、権限を読み取り専用や書き込み専用に制限することでも、SQL インジェクション問題の発生を抑えられます。
このセクションで説明するセキュリティ機能を使用できない場合は、適切な構造化データ形式を使用し、データが所定の形式に従っているかどうかを検証してください。特定の文字をブロックしたり、文字の置き換えを行ったりすることが効果的な場合もありますが、実際にはエラーが発生しやすくなるため、可能な限り使用しないことをおすすめします。
ユーザーデータ
ユーザーデータのセキュリティに関するアプローチとしては、機密情報や個人情報にアクセスする API の使用を最小限にすることをおすすめします。ユーザーデータにアクセスする権限がある場合は、可能であれば保存や送信は避けてください。データをハッシュ形式または非可逆形式で使用してアプリロジックを実装できないかを検討してください。たとえば、アプリにおいて、メールアドレスのハッシュを主キーとして使用することで、メールアドレスの送信や保存を回避できます。これにより、意図せぬデータ漏洩の可能性が減るほか、攻撃者にアプリの脆弱性が利用される可能性も減ります。
プライベートなデータへのアクセスが必要な場合は常にユーザー認証を行い、パスキーや認証情報マネージャーなどの最新の認証方法を使用してください。アプリが個人情報にアクセスする必要がある場合、地域の適用法令によっては、そのデータの使用と保存について説明するプライバシー ポリシーの提供が義務付けられることがあります。ユーザーデータへのアクセスを最小限に抑えるためのセキュリティ ベスト プラクティスを実装することで、コンプライアンスを簡素化できます。
また、広告用のサードパーティ コンポーネントやアプリが使用するサードパーティ サービスなど、アプリから意図せず個人情報が第三者に漏洩する可能性がないかも検討してください。コンポーネントやサービスが個人情報を要求する理由が不明な場合は、個人情報を提供しないでください。一般に、アプリによる個人情報へのアクセスを削減すると、個人情報漏洩の問題が生じる可能性が少なくなります。
機密データへのアクセスが必要な場合、情報をサーバーに送信する必要があるのか、クライアント上で処理できないのかを確認してください。ユーザーデータの送信を避けるため、機密データを使用するコードはクライアント上で実行することをおすすめします。また、制限の緩い IPC や、グローバルに書き込み可能なファイル、ネットワーク ソケットなどを通じて、ユーザーデータがデバイス上の他のアプリに意図せず流出することのないようにしてください。制限の緩い IPC は、権限で保護されたデータが漏洩する特別なケースです(権限のリクエストを参照)。
グローバル一意識別子(GUID)が必要な場合は、一意の大きな番号を作成して保存します。個人情報に関連付けられている可能性がある電話番号や IMEI などの電話 ID は、使用しないでください。このトピックについて詳しくは、一意の識別子に関するベスト プラクティスのページをご覧ください。
デバイスログへ書き込む際には注意が必要です。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-store
などのサーバーサイド ヘッダーを使用して、アプリが特定のコンテンツをキャッシュすることのないように指示します。
Android 4.4(API レベル 19)よりも前のプラットフォームを搭載しているデバイスは、さまざまなセキュリティ問題があるバージョンの webkit
を使用しています。対応策としては、このようなデバイス上でアプリが実行された場合に、信頼できるコンテンツだけを WebView
オブジェクトが表示するようにします。また、アプリが SSL の潜在的な脆弱性にさらされないように、アップデート可能なセキュリティ Provider
オブジェクトを使用する必要があります(SSL の脆弱性につけこんだ攻撃から保護するためにセキュリティ プロバイダをアップデートするを参照)。アプリがオープンウェブのコンテンツをレンダリングする必要がある場合、独自のレンダラを用意し、最新のセキュリティ パッチを適用してアプリを最新の状態に保つことをおすすめします。
認証情報リクエスト
認証情報リクエストは攻撃の経路となります。次に、Android アプリで認証情報リクエストのセキュリティを強化するためのおすすめの方法を紹介します。
認証情報の漏洩を最小限に抑える
- 不要な認証情報リクエストを避ける。フィッシング攻撃を目に留まりやすく、成功しにくくするには、ユーザー認証情報を求める頻度を最小限に抑えます。その代わり、認証トークンを使用し、認証トークンを更新します。認証と承認に必要な最小限の認証情報のみをリクエストします。
- 認証情報を安全に保存する。認証情報マネージャーを使用することで、パスキーによるパスワード不要の認証を有効にしたり、「Google でログイン」などのスキームを使用したフェデレーション ログインを実装したりできます。従来のパスワード認証を使用する必要がある場合は、ユーザー ID とパスワードをデバイスに保存しないでください。代わりに、ユーザーが入力したユーザー名とパスワードを使用して初期認証を行ったら、その後は存続期間の短いサービス単位の認証トークンを使用します。
- 権限のスコープを制限する。より狭いスコープのみで済むタスクには、広範な権限をリクエストしないでください。
- アクセス トークンを制限する。存続期間の短いトークン操作と API 呼び出しを使用します。
- 認証の間隔を制限する。認証リクエストや承認リクエストの間隔が短く、連続している場合は、ブルート フォース アタックの兆候の可能性があります。認証を妥当な間隔に抑えつつ、使い勝手の良いユーザー フレンドリーなアプリを構築します。
安全な認証を使用する
- パスキーを実装する。パスワードより安全性が高く、ユーザー フレンドリーなアップグレードとしてパスキーを有効にします。
- 生体認証を追加する。指紋認証や顔認識などの生体認証を使用できるようにして、セキュリティを強化します。
- フェデレーション ID プロバイダを使用する。認証情報マネージャーは、「Google でログイン」などのフェデレーション認証プロバイダをサポートしています。
- 通信を暗号化する。HTTPS などの技術を使用して、アプリからネットワーク経由で送信するデータを確実に保護します。
安全なアカウント管理を実践する
- 複数のアプリからアクセス可能なサービスには、
AccountManager
を使用して接続します。AccountManager
クラスを使用して、クラウドベースのサービスを呼び出し、デバイス上にはパスワードを保存しないようにします。 AccountManager
を使用してAccount
を取得した後、認証情報を渡す前にCREATOR
を使用すれば、認証情報を誤って違うアプリに渡すことはありません。- 認証情報を使用するのが自分のアプリだけの場合は、
checkSignatures
を使用して、AccountManager
にアクセスするアプリを検証できます。また、認証情報を使用するアプリが 1 つだけの場合は、KeyStore
を使用して保存できます。
警戒の手を緩めない
- コードを最新の状態に保つ。最新の脆弱性から保護するため、ソースコードを必ず更新します。これには、サードパーティのライブラリや依存関係が含まれます。
- 不審なアクティビティをモニタリングする。承認が不正に使用されているパターンなど、不正使用の可能性がないか確認します。
- コードを監査する。コードベースに対して定期的にセキュリティ チェックを実施し、認証情報リクエストに潜在的な問題がないか確認します。
API キー管理
API キーは多くの Android アプリの重要なコンポーネントです。アプリが外部サービスにアクセスして、マッピング サービス、認証、天気情報サービスへの接続など、重要な機能を実行できるようにします。ただし、これらの機密性の高いキーが公開されると、データ侵害、不正アクセス、金銭的な損失などの深刻な結果を招く可能性があります。このようなシナリオを回避するために、デベロッパーは開発プロセス全体で API キーを安全に取り扱う戦略を実装する必要があります。
サービスを不正使用から保護するために、API キーは慎重に保護する必要があります。アプリと API キーを使用するサービスの間の接続を保護するために、API へのアクセスを保護する必要があります。アプリがコンパイルされ、アプリのソースコードに API キーが含まれている場合、攻撃者がアプリを逆コンパイルしてこれらのリソースを見つける可能性があります。
このセクションは Android デベロッパーの 2 つのグループを対象としています。1 つは継続的デリバリーのパイプラインでインフラストラクチャ チームと連携するグループ、もう 1 つは Google Play ストアでスタンドアロン アプリをデプロイするグループです。このセクションでは、アプリがサービスと安全に通信できるように、API キーを取り扱う方法のベスト プラクティスについて説明します。
生成とストレージ
デベロッパーは、多層防御アプローチを使用して、API キーストレージをデータ保護とユーザーのプライバシーの重要な要素として扱う必要があります。
キーの強固なストレージ
キー管理のセキュリティを最適化するには、Android Keystore を使用し、保管したキーを暗号化するために security-crypto Jetpack ライブラリや Tink Java などの強力なツールを利用します。
次の例では、Jetpack security-crypto ライブラリを使用して、暗号化された共有設定を作成します。
Kotlin
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val encryptedSharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// use the shared preferences and editor as you normally would
encryptedSharedPreferences.edit()
Java
MasterKey masterKey = new MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build();
SharedPreferences sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
);
// use the shared preferences and editor as you normally would
SharedPreferences.Editor editor = sharedPreferences.edit();
ソース管理からの除外
API キーはソースコード リポジトリにコミットしないでください。ソースコードに API キーを追加すると、公開リポジトリ、共有コードサンプル、誤って共有されたファイルにキーが公開されるリスクがあります。プロジェクトで API キーを操作するには、代わりに secrets-gradle-plugin などの Gradle プラグインを使用してください。
環境固有のキー
可能であれば、開発環境、テスト環境、本番環境それぞれで個別の API キーを使用します。環境固有のキーを使用して各環境を分離することで、本番環境データの漏洩リスクを軽減し、本番環境に影響を与えることなく侵害されたキーを無効にできます。
使用状況とアクセス制御
API とユーザーを保護するためには、API キーの安全な取り扱いが不可欠です。最適なセキュリティを確保してキーを準備する手順は次のとおりです。
- アプリごとに一意のキーを生成する: アプリごとに個別の API キーを使用し、不正使用されたアクセスを特定して隔離します。
- IP 制限を実装する: 可能であれば、API キーの使用を特定の IP アドレスまたは IP 範囲に制限します。
- モバイルアプリのキーの使用を制限する: API キーを特定のモバイルアプリにバンドルするか、アプリ証明書を使用して、API キーの使用を特定のモバイルアプリに制限します。
- 不審なアクティビティのログ記録とモニタリング: API 使用状況のロギングとモニタリングのメカニズムを実装して、不審なアクティビティを検出し、潜在的な不正行為を防ぎます。
注: サービスでは、特定のパッケージまたはプラットフォームにキーを制限する機能を提供する必要があります。たとえば、Google Maps API では、キーへのアクセスをパッケージ名と署名鍵によって制限します。
OAuth 2.0 は、リソースへのアクセスを認承するためのフレームワークを提供します。クライアントとサーバーのやり取りに関する標準を定義し、安全な認証を可能にします。OAuth 2.0 を使用すると、API キーの使用を特定のクライアントに制限し、各 API キーが本来の目的に必要な最小レベルのアクセスのみを持つようにアクセス スコープを定義できます。
キーのローテーションと有効期限
API の未知の脆弱性による不正アクセスのリスクを軽減するには、API キーを定期的にローテーションすることが重要です。ISO 27001 標準は、キーのローテーション頻度に関するコンプライアンス フレームワークを定義しています。ほとんどの場合、キーのローテーション期間は 90 日~6 か月で十分です。強固なキー管理システムを実装すると、プロセスを合理化し、キーのローテーションと有効期限に関するニーズを効率的に満たすことができます。
一般的なベスト プラクティス
- SSL/HTTPS を使用する: 常に HTTPS 通信を使用して API リクエストを暗号化します。
- 証明書のピン留め: セキュリティを強化するために、証明書のピン留めを実装して、どの証明書が有効とみなされるか確認することを検討してください。
- ユーザー入力の検証とサニタイズ: ユーザー入力の検証とサニタイズを行い、API キーが漏洩するおそれのあるインジェクション攻撃を防ぎます。
- セキュリティのベスト プラクティスに従う: 安全なコーディング手法、コードレビュー、脆弱性スキャンなど、一般的なセキュリティのベスト プラクティスを開発プロセスで実装します。
- 最新情報を入手する: 最新のセキュリティ脅威と API キー管理のベスト プラクティスに関する最新情報を入手します。
- SDK を最新にする: SDK とライブラリが最新バージョンに更新されていることを確認してください。
暗号
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 の実装を試みているものもありますが、代わりに、Intent
、Binder
または Messenger
と Service
、BroadcastReceiver
などの、Android システムの IPC 機能を使用することをおすすめします。Android の IPC メカニズムを使用すると、IPC メカニズムごとにセキュリティ ポリシーを設定し、IPC に接続するアプリの ID を検証できます。
セキュリティ要素の多くは IPC メカニズム間で共有されます。他のアプリによる使用を想定していない IPC メカニズムの場合、<service>
要素など、コンポーネントのマニフェスト要素内の android:exported
属性を false
に設定します。同じ UID 内の複数のプロセスで構成されるアプリの場合や、開発の終盤で、実際には機能を IPC として開示しないことにしたが、コードを書き換えたくない場合は、このように設定すると便利です。
他のアプリが IPC にアクセスできるようにする場合、<permission>
要素を使用して、セキュリティ ポリシーを適用できます。IPC が自分のアプリ間で使用され、同じ鍵で署名されている場合は、android:protectionLevel
で signature-level
権限を使用します。
インテント
アクティビティやブロードキャスト レシーバの場合、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**
を使用してバックグラウンド サービスを実行する必要があります。
サービスは、権限を設定することで、サービスに対して行われた個々の IPC 呼び出しを保護できます。そのためには、呼び出しを実装する前に checkCallingPermission()
を呼び出します。通常はマニフェスト内で権限を宣言した方が見落としにくいため、その方法をおすすめします。
注: クライアントとサーバーの権限を混同しないでください。呼び出されたアプリが適切な権限を持っていることと、呼び出し元のアプリに同じ権限が付与されていることを確認します。
Binder インターフェースと Messenger インターフェース
Android で RPC スタイルの IPC を実行する場合は、Binder
や Messenger
のメカニズムを使用することをおすすめします。これらは適切に定義されたインターフェースを備えており、必要に応じて、エンドポイントの相互認証が可能です。
アプリ インターフェースを設計する際は、インターフェース固有の権限チェックを必要としない設計にすることをおすすめします。Binder
オブジェクトも Messenger
オブジェクトもアプリ マニフェスト内で宣言するものではないため、宣言型の権限を直接適用することはできません。通常は、Service
や Activity
を実装しているアプリのマニフェスト内で宣言されている権限を継承します。認証やアクセス制御を必要とするインターフェースを作成する場合、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 セキュリティの概要をご覧ください。ネイティブ コードを使用する場合でも、アプリの権限について十分に理解しておく必要があります。