スマートフォンが生活に欠かせないインフラとなった今、モバイルアプリのセキュリティは企業にとって最重要課題です。スマホのネイティブアプリは、本体が利用者の端末に配布されるため、攻撃者が手元でコードを解析できるという大きなリスクがあります。
本記事では、モバイルOSのセキュリティ構造から、AndroidとiOSの違い、Jailbreak(脱獄)やRoot化のリスク、さらに実践的な攻撃手法と防御策まで、現場で必要となるセキュリティ知識を体系的に解説します。
スマホアプリ特有のセキュリティリスクとは
ネイティブのモバイルアプリケーションには、Webアプリとは前提が異なるセキュリティ課題があります。
アプリがユーザー端末で動作するリスク
Webアプリケーションでは、サーバー側のプログラムは通常利用者に配布されません(ただし、ブラウザで動くJavaScriptなどのクライアント側コードは配布され、解析できます)。一方、スマホのネイティブアプリは、プログラム本体(バイナリコード)がユーザーの端末にダウンロードされ、そこで実行されます。
この違いは設計の前提を大きく変えます。攻撃者はアプリストアから正規にアプリをダウンロードし、以下のような解析を自由に行えます。
- プログラムコードの逆コンパイル
- APIキーや暗号化キーの抽出
- 通信内容の傍受と改ざん
- メモリ上のデータの読み取り
- 認証ロジックの迂回
つまり、スマホアプリは「攻撃者の手元にある」という前提で設計しなければなりません。
多様なデバイス環境への対応
PCと異なり、スマートフォンは機種、OSバージョン、メーカーのカスタマイズが多岐にわたります。特にAndroidは端末の種類が膨大で、セキュリティパッチの適用状況も一律ではありません。
この「断片化(フラグメンテーション)」により、開発者は多様な環境でのセキュリティを考慮する必要があります。
+おまけ:毎日1通の無料メール講座つき。登録した日が「1日目」、図解と論理で16週間かけて基礎も固まります。
モバイルOSのセキュリティ基盤|サンドボックスの仕組み
現代のスマートフォンOSは、堅牢なセキュリティモデルを持っています。その中核となるのが「サンドボックス」という概念です。
サンドボックスとは何か
サンドボックス(Sandbox)とは、各アプリケーションを独立した「砂場」のような隔離空間で動作させるセキュリティ機構です。
従来のPC向けOSでは、あるアプリが作成したファイルに、別のアプリが比較的自由にアクセスできました。しかしモバイルOSでは、アプリごとに専用のデータ保存領域が提供され、互いに隔離されます。Androidでは各アプリに固有のLinuxユーザーID(UID)を割り当てることで、iOSではアプリごとのコンテナとOSのサンドボックスポリシーによって、この隔離を実現しています。
サンドボックスの保護機能
- アプリAが保存したデータは、アプリAの領域にのみ格納される
- アプリBからアプリAのデータへの直接アクセスは遮断される
- 悪意のあるアプリをインストールしても、他アプリのデータは保護される
- 銀行アプリの認証情報や連絡先が簡単に盗まれることを防ぐ

パーミッション(権限)による制御
サンドボックスは強力な防壁ですが、完全な隔離ではアプリが機能しません。カメラ、GPS、連絡先などの機能を使うには、壁を越えるアクセスが必要です。
ここで重要になるのが「パーミッション(権限)」モデルです。
Androidでの権限管理
アプリはAndroidManifest.xmlに必要な権限を記述します。ユーザーは以下のタイミングで権限の付与を判断します。
- インストール時(Android 5.1以前。要求された権限をまとめて承認する方式)
- 実行時(Android 6.0以降。位置情報・カメラ・連絡先などの「危険な権限(dangerous permission)」は、アプリが必要とするタイミングでダイアログにより許可を求める。インターネット接続などの「通常の権限(normal permission)」はインストール時に自動で付与される)
- 設定画面から随時変更可能
iOSでの権限管理
Info.plistに利用目的を記載し、機能の初回利用時にダイアログで許可を求めます。近年のiOSでは、以下のような細かい制御が可能です。
- アプリ使用中のみ許可
- 一回限り許可
- 正確な位置情報の制限
- トラッキング許可の明示化
最小権限の原則
開発者は必要最小限の権限のみを要求すべきです。不要な権限要求は、ユーザーからの信頼を損ね、セキュリティリスクも高めます。
AndroidとiOSのセキュリティ特性を比較
両OSともサンドボックスという基本思想は共通していますが、実装やエコシステムには明確な違いがあります。
Android|オープン性がもたらす自由と課題
プラットフォームの特徴
- Linuxカーネルベースのオープンソース
- 多様な端末メーカーが参入可能
- Google Playストア以外からのインストールにも対応
セキュリティ上の課題
Androidのオープンなエコシステムは、以下のリスクを生みます。
- 断片化問題: 端末ごとにセキュリティパッチの適用状況が異なる。古い端末では脆弱性が放置される可能性が高い。
- サイドローディング: 「不明なアプリのインストール」を許可すれば、Web経由で配布されたAPKファイルを直接インストールできる。審査を経ていないマルウェアのリスクが高まる。なお、Googleは認定Android端末(Android 7以降)で、アプリの開発者が本人確認を済ませた登録済みの開発者かを確認し、未登録の開発者のアプリのインストールを制限する仕組み(Android developer verification)を段階導入している(上級者向けに、未登録の開発者のアプリを入れる手順も用意される)。2026年9月30日から、ブラジル・インドネシア・シンガポール・タイで参加ストアからのインストールを対象に保護が始まり、2027年に全世界へ広げる予定とされている。
- メーカー・キャリアによるカスタマイズ: メーカーやキャリアがOSをカスタマイズしているため、標準的なセキュリティ機能が変更されている場合がある。また、ユーザーが非公式のOSイメージ(カスタムROM)を導入した端末では、公式のセキュリティ機能や更新が前提にできない。
iOS|堅牢な審査による安全性
プラットフォームの特徴
- Appleがハードウェアとソフトウェアを統合管理
- App Store以外からのインストールは原則不可(ただしEUや日本などでは法規制により代替アプリマーケットプレイスが認められた。日本ではスマホソフトウェア競争促進法の全面施行に合わせ、iOS 26.2以降で利用可能)
- App Storeで配布するアプリに厳格な審査(App Review)を実施(代替マーケットプレイス経由のアプリは、Appleの「公証(Notarization)」と呼ばれる基本的な審査を経る)
セキュリティ上の強み
iOSの「ウォールド・ガーデン(壁に囲まれた庭)」アプローチは、以下のメリットをもたらします。
- マルウェアの混入リスクが低い
- プライバシーポリシー違反アプリの排除
- 統一的なセキュリティパッチの配信
- ハードウェアレベルのセキュリティ機能(Secure Enclave)
注意点
App Storeの審査を通過したアプリでも、後から悪意ある動作を追加する例があります。また、OS自体の脆弱性が発見された場合、パッチが配信・適用されるまでは、該当するOSバージョンを使う利用者が危険に晒されます。
Jailbreak(脱獄)とRoot化|セキュリティ境界の崩壊
「Jailbreak」と「Root化」は、OSの制限を解除し管理者権限を取得する行為です。これらはモバイルセキュリティの重大な脅威の一つです。
サンドボックスの制限が迂回される
通常の端末
- サンドボックスによりアプリ間のデータアクセスが制限される
- システム領域への書き込みが禁止される
- 権限のないアクセスは、OSのアクセス制御で拒否される(OSに脆弱性がない限り)
Jailbreak・Root化された端末
- root権限の取得に加え、Root化に伴うSELinuxポリシーの変更や保護機構の迂回により、サンドボックスの制限を回避されうる
- 他アプリのデータ領域やシステム領域にアクセスされるおそれがある
- システムファイルの改ざんが可能になる場合がある
AndroidのSELinuxは、root権限で動くプロセスにも強制アクセス制御を適用するため、root権限を得ただけで何でもできるとは限りません。ただし、Root化ツールはSELinuxポリシーの変更や保護機構の迂回を伴うことがあり、その結果、他アプリのデータにアクセスされるおそれがあります。サンドボックスの仕組み自体が自動で消えるわけではありませんが、アプリ間の隔離を保証できなくなる、というのが正確な理解です。
具体的な攻撃シナリオ
Root化された端末では、以下のような攻撃が起こりえます。
決済アプリへの攻撃
- アプリがサンドボックス内に保存した暗号化鍵を窃取
- メモリダンプから認証トークンを抽出
- 決済処理を改ざんして不正送金
ゲームアプリへの攻撃
- ゲームデータの保存ファイルに直接アクセス
- アイテムやポイントの数値を書き換え
- サーバー通信を傍受して課金を回避

Root化・Jailbreak検知の実装
金融アプリやセキュリティレベルの高いアプリでは、起動時に端末の状態をチェックします。
検知方法の例
- 特定ファイルの存在確認(Androidの
suバイナリ、iOSのJailbreak用パッケージマネージャー(古くはCydia、現在はSileoなど)の痕跡など) - システムディレクトリへの書き込み権限チェック
- 特定パッケージのインストール状況確認
- ブートローダーのロック解除やVerified Bootの状態(アプリが直接調べるのではなく、Key AttestationやPlay Integrity APIの判定結果から間接的に分かる)
なお、iOSではシステム領域を書き換えない「rootless」型のJailbreakも広く使われており、ファイルの痕跡だけでは判定しにくくなっています。
検知時の対応
・警告メッセージの表示
・アプリの機能制限(重要機能のみ無効化)
・完全な起動拒否
ただし、端末内の検知は、Root化隠蔽ツールやFridaなどによるフック、検知処理を取り除いた再パッケージで無効化されうるため、単独では決め手になりません。重要な処理(ログイン、送金など)では、Play Integrity API(Android)やApp Attest(iOS。正規のApple端末上で動く正規のアプリかを確認するもので、Jailbreakの有無までは判定できない)を使い、端末とアプリの正当性をサーバー側で検証し、その結果に応じて処理を制限します。判定をサーバー側で行うことで、端末内のチェックだけに頼るより改ざんに強くなります(これらも万能ではないため、サーバー側の不正検知などと組み合わせます)。
リバースエンジニアリング|アプリ解析の脅威と対策
モバイルアプリは攻撃者が自由にダウンロードできるため、コードの解析が大きな脅威の一つとなります。
アプリ解析の二つのアプローチ
静的解析|コードを読んで理解する
アプリを実行せず、プログラムファイルを解析する手法です。
主なツール:
- apktool(APKファイルの展開)
- jadx(バイトコードの逆コンパイル)
- Hopper、IDA Pro(バイナリ解析)
静的解析で暴かれる情報:
- APIキーやサーバーのURLなどの定数
- 暗号化アルゴリズムの実装
- 認証ロジックの仕組み
- 隠し機能やデバッグコード
- データベースのスキーマ構造
動的解析|動かしながら観察する
アプリを実際に動作させ、その挙動を監視・改ざんする手法です。
主なツール:
- Frida(動的インストゥルメンテーション)
- Xposed系フレームワーク(現在はLSPosedなど。Androidのメソッドフック)
- Charles Proxy(通信の傍受)
- Android Studio Debugger(デバッグ)
動的解析で可能になること:
- メモリ上の変数値の書き換え
- 関数の戻り値の改ざん(フッキング)
- 暗号化前のデータの取得
- サーバー通信の傍受と改ざん
- 課金処理や認証処理のバイパス
コード難読化による防御
リバースエンジニアリングを完全に防ぐことは不可能ですが、解析の難易度を上げることは可能です。
難読化の仕組み
Androidでは、Android Gradle Plugin(AGP)標準のコード縮小・難読化ツールであるR8(ProGuardのルール記法と互換)を使います。R8は自動では動かず、リリースビルドで有効化(isMinifyEnabled = trueなど)したときに、クラス名・メソッド名などを短い名前に置き換えます(リネーム)。iOSでは、名前の置換にサードパーティ製のツールを使います(保守が終了したツールもあるため、保守状況を確認して選びます)。制御フローの複雑化や文字列の暗号化といった高度な変換は、DexGuardなどの商用難読化ツールが提供します。
変換の例:
- クラス名:
UserAuthenticationManager→a - メソッド名:
validatePassword()→b() - 変数名:
secretApiKey→c - 制御フローの複雑化(意味のない分岐を追加)
- 文字列の暗号化
難読化後のコード
逆コンパイルしても、以下のように意味不明なコードになります。
public class a {
private String c = "bG9naW4=";
public boolean b(String d) {
if (e(d)) {
return f();
}
return false;
}
}
名前から処理の意味を推測しにくくなるため、解析に要する時間は増やせます。ただし、文字列や通信内容、動的解析からロジックを追われる可能性は残ります。例えば上の"bG9naW4="はBase64でエンコードしただけの文字列で、デコードすればloginにすぐ戻せます。難読化は、APIキーなどの秘密情報をアプリに埋め込まないことを前提にした、解析の手間を増やす対策と考えます。

耐タンパ性の実装
アプリが改ざんされていないかを検証する機能も重要です。
実装例
- アプリファイルのハッシュ値計算と検証
- デジタル署名の確認
- 実行時の整合性チェック
- デバッガ接続の検知
- エミュレータ環境の検知
改ざんが検知された場合は、アプリの動作を停止させます。ただし、検知処理そのものもフックや再パッケージで改ざんされうるため、重要な判定は前述のPlay Integrity APIやApp Attestによるサーバー側の検証と組み合わせます。
Intent悪用によるアプリ間連携の脆弱性
Androidの「Intent」は便利なアプリ間連携機能ですが、適切に実装しないと深刻な脆弱性となります。
Intentとは何か
Intentは、アプリ間でデータをやり取りしたり、機能を呼び出したりするためのメッセージング機構です。
Intentの種類
明示的Intent:
- 連携先をクラス名で直接指定
Intent(this, DetailActivity::class.java)- 主に同一アプリ内での画面遷移に使用(パッケージ名とクラス名を指定すれば、他アプリの特定コンポーネントも呼び出せる)
暗黙的Intent:
- 処理内容のみを指定(「Webページを開く」など)
- OSが適切なアプリを選択
Intent(Intent.ACTION_VIEW, Uri.parse("https://..."))- 他アプリとの連携に使用
exported属性による脆弱性
AndroidManifest.xmlでの設定ミスが、深刻なセキュリティホールを生みます。
危険な設定例
<activity
android:name=".SecretActivity"
android:exported="true">
</activity>
この設定では、任意の外部アプリからSecretActivityを呼び出せてしまいます。
攻撃シナリオ
悪意のあるアプリから、以下のような攻撃が可能になります。
- 不正なデータを送り込んでクラッシュさせる
- 内部データを取得する
- 権限が必要な処理を勝手に実行させる
- SQLインジェクションなどの攻撃
正しい実装
<!-- 外部から呼び出す必要がない場合 -->
<activity
android:name=".SecretActivity"
android:exported="false">
</activity>
<!-- 自社アプリ間など、特定のアプリとだけ連携する場合 -->
<!-- 同じ署名鍵で署名されたアプリにだけ付与される独自パーミッションを定義 -->
<permission
android:name="com.example.app.permission.PARTNER_ACCESS"
android:protectionLevel="signature" />
<activity
android:name=".PartnerActivity"
android:exported="true"
android:permission="com.example.app.permission.PARTNER_ACCESS">
<!-- 呼び出し元はこのパーミッションを保持している必要がある -->
</activity>
外部公開するコンポーネントを保護する場合、カメラやSMSなどの「危険な権限」はユーザーが許可すれば任意のアプリが取得できるため、保護には向きません。特定のアプリとだけ連携するなら、protectionLevel="signature"の独自パーミッションのように、呼び出し元を限定できる仕組みを使います。なお、Android 12(API 31)以降を対象とするアプリでは、intent-filterを持つコンポーネントにandroid:exportedの明示的な指定が必須になっています。
Intentデータの検証
外部から受け取ったデータは、必ず検証してから使用します。
検証項目
- データ型の確認
- 値の範囲チェック
- NULL値のハンドリング
- 想定外の文字や形式の拒否
- ファイルパスの正規化と、許可したディレクトリ内に収まるかの確認
検証を怠ると、ディレクトリトラバーサルやコマンドインジェクションなどの攻撃を受ける可能性があります。なお、エスケープは入力検証とは別の対策です。受け取ったデータをSQL・HTML・OSコマンドなどに渡すときに、出力先に応じて利用時に行います(SQLならプレースホルダを使う、OSコマンドはシェルを経由せず引数を分けて渡す、など)。
WebViewのセキュリティリスク
アプリ内でWebコンテンツを表示するWebViewは、適切に設定しないと重大な脆弱性となります。
WebViewの基本的なリスク
WebViewはアプリ内にブラウザ機能を埋め込むコンポーネントです。表示するWebページに悪意あるJavaScriptが含まれていると、アプリのデータが盗まれる危険があります。
リスクの例
- Cookie情報の窃取
- ローカルストレージへのアクセス
- アプリ内のネイティブ機能の不正呼び出し
- フィッシング詐欺への誘導
JavaScript Interface の危険性
addJavascriptInterfaceメソッドは、WebページのJavaScriptからアプリのJava/Kotlinメソッドを呼び出せるようにする機能です。
古いバージョンの脆弱性
Android 4.1以前(API 16以下)では、この機能を有効にすると、JavaScriptからJavaのリフレクションを悪用して任意のコマンドを実行できる脆弱性が存在しました(CVE-2012-6636)。Android 4.2(API 17)以降を対象(targetSdkVersion 17以上)とするアプリでは、@JavascriptInterfaceアノテーションを付けたメソッドだけが公開されるよう改められています。
現在の安全な使い方
class JavaScriptInterface {
@JavascriptInterface
fun getData(): String {
// このメソッドのみがJavaScriptから呼び出せる
return "Safe Data"
}
}
webView.addJavascriptInterface(JavaScriptInterface(), "Android")
@JavascriptInterfaceアノテーションを付けたメソッドのみが公開されます。それでも、信頼できないコンテンツでこの機能を有効にすることは避けるべきです。
WebViewの安全な設定
推奨設定
webView.settings.apply {
// JavaScriptは必要な場合のみ有効化
javaScriptEnabled = true
// ファイルアクセスを制限
allowFileAccess = false
allowContentAccess = false
// Mixed Content(HTTP/HTTPS混在)を許可しない
mixedContentMode = WebSettings.MIXED_CONTENT_NEVER_ALLOW
}
// WebView内で表示してよいURLか(自社ドメインの許可リスト)
fun isAllowedUrl(uri: Uri): Boolean =
uri.scheme == "https" && uri.host == "www.example.com"
webView.webViewClient = object : WebViewClient() {
// 画面内のリンクやリダイレクトで遷移するURLを検証する
// (このメソッドはPOST要求やアプリ自身のloadUrlでは呼ばれない)
override fun shouldOverrideUrlLoading(
view: WebView,
request: WebResourceRequest
): Boolean {
val uri = request.url
if (isAllowedUrl(uri)) {
return false // 許可したURLだけWebView内で表示
}
// 外部アプリ(ブラウザなど)で開くのは、メインフレームのhttpsリンクだけ
if (request.isForMainFrame && uri.scheme == "https") {
try {
view.context.startActivity(Intent(Intent.ACTION_VIEW, uri))
} catch (e: ActivityNotFoundException) {
// 開けるアプリがない場合は何もしない
}
}
return true // 許可リスト外はWebViewで読み込まない(他のスキームやサブフレームは拒否)
}
// SSL証明書エラーを無視しない
override fun onReceivedSslError(
view: WebView,
handler: SslErrorHandler,
error: SslError
) {
// handler.proceed() は呼ばない
handler.cancel()
}
}
// アプリ自身が読み込むURLも、loadUrlの前に同じ関数で検証する
if (isAllowedUrl(Uri.parse(url))) {
webView.loadUrl(url)
}
表示するURLを許可リストで制限する
WebViewに任意のURLを表示させると、攻撃者のページでJavaScript Interfaceを呼び出されたり、本物そっくりのログイン画面を表示されたりするおそれがあります。表示するURLは自社ドメインの許可リストで制限し、それ以外の外部URLは外部アプリ(ブラウザなど)で開くか、読み込みを拒否します。
- 画面内のリンクやリダイレクトによる遷移は
shouldOverrideUrlLoadingで検証する。上の例が検証するのは、loadUrlの前のチェックと、このコールバックの対象になる遷移に限られる Intent.ACTION_VIEWはブラウザ専用ではない。intent:などHTTP(S)以外のスキームまで無条件に渡すと別のアプリが起動したり、対応するアプリがなく例外になったりするため、外部で開くのはメインフレーム(request.isForMainFrame)のhttpsリンクに限り、それ以外は拒否してActivityNotFoundExceptionも処理するshouldOverrideUrlLoadingはPOST要求では呼ばれないため、フォームの送信先は表示するページ側でも自社ドメインに限定し、そもそも信頼できないコンテンツをWebViewに読み込まない設計にするshouldOverrideUrlLoadingはアプリ自身がloadUrlで読み込むURLには呼ばれないため、Intentなど外部から受け取ったURLはloadUrlの前に検証する- 文字列の前方一致で判定する場合は、
https://www.example.com/のようにホスト名の直後のスラッシュまで含める。https://www.example.comまでで判定すると、https://www.example.com.example.net/やhttps://www.example.com@example.net/のような別ホストのURLも通ってしまう - URLを解析してスキーム(https)とホスト名を完全一致で比較する方法も有効
HTTPS通信の徹底
WebViewでも通常のネットワーク通信と同様に、HTTPS通信を強制します。
- HTTP通信は原則禁止
- 証明書エラーは無視しない
- 開発時の例外設定を本番環境に残さない
ネットワーク通信のセキュリティ対策
モバイルアプリは公共Wi-Fiなど、信頼できないネットワークで使用される機会が多く、通信の保護が不可欠です。
中間者攻撃(MITM)のリスク
攻撃の仕組み
攻撃者が通信経路の中間に入り込み、以下を行います。
- 通信内容の盗聴
- データの改ざん
- 偽のサーバーへの誘導
- 認証情報の窃取
狙われやすい環境
- 公共Wi-Fi(カフェ、空港、ホテル)
- 暗号化されていないWi-Fi
- 悪意のあるアクセスポイント(Evil Twin攻撃)
HTTPSの必須化
iOS(App Transport Security)
iOS 9で導入されたApp Transport Security(ATS)により、URLSessionなどの標準APIでは、デフォルトでHTTP(平文)通信がブロックされます(Info.plistで例外を設定できます)。
<!-- Info.plist で例外を設定する場合 -->
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<!-- 特定ドメインのみ許可する場合 -->
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
Android(Network Security Configuration)
Android 7.0で導入されたNetwork Security Configurationにより、通信設定をXMLで宣言できます。Android 9(API 28)以降を対象(targetSdkVersion 28以上)とするアプリでは、デフォルトでHTTP(平文)通信が禁止されています。
設定ファイルはres/xml/に置くだけでは適用されません。AndroidManifest.xmlの<application>要素でandroid:networkSecurityConfig属性から参照する必要があります(この節とピンニングの実装例に共通する前提です)。
<!-- AndroidManifest.xml -->
<manifest ... >
<application
android:networkSecurityConfig="@xml/network_security_config"
... >
...
</application>
</manifest>
<?xml version="1.0" encoding="utf-8"?>
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<base-config cleartextTrafficPermitted="false" />
</network-security-config>
証明書ピンニングによる高度な防御
証明書ピンニング(Certificate Pinning)は、サーバー証明書の正当性をより厳格に検証する技術です。
仕組み
通常のHTTPS通信では、端末にインストールされた認証局(CA)の証明書を信頼します。しかし、攻撃者が不正なCAを使って偽の証明書を発行できれば、中間者攻撃が成立してしまいます。
証明書ピンニングでは、接続先サーバーの証明書(または公開鍵)のハッシュ値を、アプリ内にあらかじめ埋め込んでおきます。
ただし、Androidの公式ドキュメントは、認証局(CA)の変更などサーバー側の設定変更によって、アプリを更新するまで接続できなくなるおそれがあるとして、証明書ピンニングを原則として推奨していません。採用する場合は、公開鍵のピン、複数のバックアップピン、ピンの更新手順をセットで設計します。
実装例(Android)
<!-- res/xml/network_security_config.xml -->
<network-security-config>
<domain-config>
<domain includeSubdomains="true">api.example.com</domain>
<!-- expiration:この日付を過ぎるとピン留めは行われなくなる -->
<pin-set expiration="2027-12-31">
<!-- 下の2つの値は置き換えが必要なプレースホルダー。実際には、各公開鍵情報(SubjectPublicKeyInfo)のSHA-256ハッシュをBase64にした値を書く -->
<pin digest="SHA-256">base64EncodedPublicKeyHash==</pin>
<!-- バックアップ用の鍵のハッシュも登録 -->
<pin digest="SHA-256">backupPublicKeyHash==</pin>
</pin-set>
</domain-config>
</network-security-config>
運用上の注意点
証明書ピンニングを実装する場合、以下に注意が必要です。
- 変更後の証明書チェーンに、登録済みのピンと一致する公開鍵がなくなる場合は、事前にアプリの更新が必要(公開鍵をピン留めしている場合、同じ鍵のまま証明書を更新するならアップデートは不要)
- バックアップ用のピンを複数登録しておく(少なくとも1つは自社で完全に管理できる鍵にする)
- ピンに有効期限(
expiration)を設定する。期限を過ぎるとピン留めは行われず通常の証明書検証だけになるため、更新されない古いアプリが接続不能になるのは防げるが、期限後はピンニングの保護も外れる - 緊急時の証明書・鍵の変更手順を用意する
- 証明書の有効期限を監視する
- Root化・Jailbreakされた端末では、Fridaなどで検証処理をフックされ、ピンニングを回避されうる(通信の解析を完全に防げるわけではない)
機密データの安全な保存方法
アプリが扱うデータの重要度に応じて、適切な保存場所と暗号化を選択する必要があります。
避けるべき保存場所
外部ストレージ(共有ストレージ・SDカード)
外部ストレージは他のアプリからも読み書き可能なパブリックな領域を含みます。Android 10で対象範囲別ストレージ(Scoped Storage)が導入され、Android 11(API 30)以降を対象(targetSdkVersion 30以上)とするアプリでは必須となって、他アプリからのアクセスが制限されました。それでも、共有領域や古いOSの端末では依然として他アプリに読まれるおそれがあります。個人情報や認証トークンを保存してはいけません。
SharedPreferencesとUserDefaults
AndroidのSharedPreferencesやiOSのUserDefaultsは、設定値を保存するための便利な仕組みですが、デフォルトでは平文で保存されます。Root化された端末では読み取られるおそれがあります。
アプリのログ出力
開発時のデバッグログに機密情報を出力し、本番環境にそのまま残してしまうケースがあります。Android 4.1以降、一般のアプリは他アプリのログを読めなくなりましたが、USBデバッグ(ADB)接続やRoot化された端末、プリインストールアプリなどからは読み取られる可能性があるため、機密情報を出力してはいけません。
OSが提供するセキュアストレージ
iOS|Keychain Services
Keychainは、パスワードや暗号鍵などの機密情報を安全に保存するためのiOS標準機能です。
特徴:
- OSによる自動暗号化
- アプリのコンテナとは別の、OSが管理する領域に保存(他アプリとの共有は、アクセスグループで明示した場合のみ)
- 生体認証との統合
- デバイス間の同期(オプション)
使用例:
- ログイン認証トークン
- クレジットカード情報
- 暗号化キー
- 証明書
Android|Keystore System
Android Keystoreは、暗号鍵をアプリから取り出せない形で保管し、対応端末ではハードウェアレベルで保護する仕組みです。
特徴:
- TEE(Trusted Execution Environment)やセキュアエレメント(StrongBox)での鍵管理(端末が対応している場合)
- 鍵素材はアプリのプロセスに渡らず、ハードウェアで保護された鍵はセキュアハードウェアの外に出ないため、鍵の抽出が極めて困難(アプリは鍵を「使う」ことはできても「取り出す」ことはできない)
- 生体認証との連携
- 用途別の鍵生成
EncryptedSharedPreferences
AndroidではJetpack Securityライブラリ(security-crypto)が提供するEncryptedSharedPreferencesを使用することで、SharedPreferencesのデータを自動的に暗号化できます。ただし、このライブラリは1.1.0-alpha07(2025年4月9日)で全APIが非推奨(deprecated)とされ、既存のプラットフォームAPIとAndroid Keystoreを直接使う方針が示されました。移行先は一つに決められているわけではなく、例えばAndroid Keystoreで管理する鍵とGoogle Tinkなどの暗号ライブラリを組み合わせ、暗号化したデータをJetpack DataStoreに保存する方法があります。以下は既存アプリで見かける実装例です。
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val sharedPreferences = EncryptedSharedPreferences.create(
context,
"secret_shared_prefs",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
// 通常のSharedPreferencesと同じように使用
sharedPreferences.edit().putString("api_token", "secret_value").apply()
データ暗号化のベストプラクティス
機密データを保存する際の推奨手順:
- Android KeystoreまたはiOS Keychainで暗号鍵を生成・保存
- その鍵を使ってデータを暗号化
- 暗号化されたデータをアプリの内部ストレージ(プライベート領域)に保存
- 読み取り時は鍵で復号化
絶対に避けるべきこと:
- ソースコード内に暗号鍵をハードコーディング
- 単純なBase64エンコードを暗号化と勘違いする
- 同じ鍵を全ユーザーで共有する
JSSECガイドラインの活用
日本スマートフォンセキュリティ協会(JSSEC)が公開している「Androidアプリのセキュア設計・セキュアコーディングガイド」は、Androidアプリのセキュリティを体系的に学べる資料です。
JSSECガイドラインとは
このガイドラインは、Androidアプリ開発におけるセキュリティのベストプラクティスを、具体的なコード例とともに解説した包括的なドキュメントです。
掲載されている主な内容
- コンポーネント(Activity、Service、BroadcastReceiver、ContentProvider)の安全な実装
- Intentの適切な使用方法
- ファイルとデータベースの安全な扱い方
- 暗号化技術の正しい使い方
- WebViewのセキュア設定
- HTTPS通信の実装例
- ログ出力の注意事項
- パーミッションの適切な要求方法
開発者だけでなく、セキュリティ監査を行う立場の人にとっても、このガイドラインを理解しておくと監査でも役立ちます。
継続的な学習の重要性
JSSECガイドラインは、Androidのバージョンアップに合わせて改訂されています。
- 新しいAndroidバージョンへの対応
- 新たに発見された脆弱性への対策
- 最新の攻撃手法に対する防御策
参照するときは、JSSECのサイトで最新版を確認します。iOSも含めたモバイルアプリ全般の検証項目は、OWASPのMASVS(検証標準)とMASTG(テストガイド)が体系的にまとめています。
SC試験対策のポイント
スマホアプリのセキュリティは、Webアプリやネットワークの知識を組み合わせて問える題材です。ここでは、過去問で確認できた論点と、本記事の内容から押さえておきたい論点を整理します。
過去問の例:令和7年度 春期 午後 問3
令和7年度 春期 午後 問3は、スマートフォン用アプリケーションプログラムの脆弱性を題材にした問題です。IPAの出題趣旨では、JPCERTコーディネーションセンター(JPCERT/CC)とIPAが運営する脆弱性対策情報ポータルサイトJVNに、国内企業が開発したスマホアプリの脆弱性が公表されていることに触れています。IPA公表の解答例から確認できる論点は次のとおりです。
- アプリを解析すると、アプリ内に暗号化して埋め込んだアクセスキーと、その復号に使う共通鍵・初期ベクトルを入手でき、アクセスキーを復号できてしまう(秘密情報をアプリに埋め込むリスク)
- 通信解析ツールのプライベート認証局のルート証明書を端末にインストールして信頼させると、HTTPS通信の内容を解析できる(なお、Android 7.0以降を対象とするアプリは、既定ではユーザーが追加したCAを信頼しない。Network Security Configurationで明示的に許可した場合を除く)
- アプリに渡したURLをそのままWebViewで開けてしまうと、正規のドメインに見せかけたURL(
https://正規のホスト名.攻撃者のドメイン/や、@を使ったもの)や、攻撃者のドメインそのものを開かせられる。しかもWebViewには、利用者がURLを確認する手段がない - 対策として、WebViewを呼び出す前に、URLの先頭が
https://許可したホスト名/(ホスト名の後のスラッシュまで)と一致するかを検証する
本記事から押さえたい論点
令和8年度からSC試験はCBT方式となり、旧午後に当たるのは科目Bです。科目B(旧午後)でスマホアプリが事例の題材になった場合に備え、次の論点を自分の言葉で説明できるようにしておきます。
- Root化・Jailbreakでは、root権限の取得やSELinuxポリシーの変更などによる保護機構の迂回で、アプリ間の隔離を保証できなくなる
- 端末内のRoot化検知や難読化は回避されうるため、秘密情報をアプリに埋め込まず、重要な判定はサーバー側で行う
- Intentを受けるコンポーネントは
exported属性で公開範囲を絞り、受け取ったデータを検証する - WebViewは表示するURLを許可リストで制限し、証明書エラーを無視しない
- 証明書ピンニングには鍵変更時に接続不能になるリスクがあり、採用する場合はバックアップピンと更新手順をセットで設計する
- 機密情報は、iOSはKeychain、AndroidはKeystoreで管理する鍵で暗号化して保存する
【理解度チェック】スマホアプリのセキュリティ構造と脅威対策 練習問題(全10問)
ユーザーの手元でアプリ本体が動作するネイティブアプリには、特有のセキュリティリスクが存在します。 本記事で解説したAndroid・iOSのサンドボックス構造、Root化・Jailbreakの危険性、そしてIntentやWebViewの安全な実装について、理解度を確認するための練習問題を作成しました。 開発現場で役立つ知識の定着に、ぜひお役立てください。
記事の内容をどのくらい理解できましたか?習得度をチェックしてみましょう。
まとめ|多層防御でモバイルアプリを守る
スマートフォンアプリのセキュリティ対策は、Webアプリとは異なる固有の課題が多く存在します。「アプリが攻撃者の手元にある」という前提で、多層防御の考え方が不可欠です。
セキュリティ対策の5つの柱
1. サンドボックスへの過信は禁物
OSのサンドボックスは強力な防御壁ですが、Root化やJailbreak、OS脆弱性により突破される可能性があります。サンドボックスが破られた前提で、追加の防御策を講じましょう。
2. リバースエンジニアリング対策
コードの難読化で解析の手間を増やします。難読化は完全な防御ではないため、APIキーなどの機密情報をソースコードに埋め込まない、耐タンパ性を実装する、重要な判定はサーバー側で検証するなど、複数の対策を組み合わせます。
3. アプリ間連携の厳格化
Intentや外部からの入力は全て「悪意があるもの」として扱い、必ず検証します。exported属性を適切に設定し、不要な公開を避けます。
4. セキュアなデータ保存
機密情報は必ずOS提供のセキュアストレージを利用します(iOSはKeychainに保存、AndroidはKeystoreで管理する鍵で暗号化して内部ストレージに保存)。平文保存や外部ストレージへの保存は厳禁です。
5. 通信の保護
HTTPS通信を徹底し、証明書の検証を省略しません。証明書ピンニングは鍵変更時に接続不能になるリスクがあり、Androidの公式ドキュメントは原則非推奨としています。採用する場合は、公開鍵のピン、複数のバックアップピン、更新手順をセットで設計します。
セキュリティ・バイ・デザインの実践
これらの対策は、開発の後半で付け足すものではありません。設計段階から組み込む「セキュリティ・バイ・デザイン」の実践が重要です。
実践のポイント:
- 要件定義時にセキュリティ要件を明確化
- 設計段階で脅威分析(STRIDE等)を実施
- 開発時にセキュアコーディングを徹底
- テスト時に脆弱性診断を実施
- リリース後も継続的な監視とアップデート
継続的な学習と情報収集
モバイルセキュリティの脅威は日々進化しています。JSSECガイドラインなどの信頼できるリソースを活用し、常に最新の情報をキャッチアップすることが、セキュリティエンジニアとして成長する近道です。
ユーザーが安心して利用できる堅牢なアプリを実現するため、本記事で解説した知識を実践に活かしてください。
本記事は情報処理安全確保支援士(SC)試験対策を目的として作成しています。
参考資料
- OWASP MASVS v2.1.0(Mobile Application Security Verification Standard)(https://mas.owasp.org/MASVS/)
- OWASP MASTG(Mobile Application Security Testing Guide)(https://mas.owasp.org/MASTG/)
- OWASP Mobile Top 10 2024(https://owasp.org/www-project-mobile-top-10/)
- JSSEC「Androidアプリのセキュア設計・セキュアコーディングガイド」(https://www.jssec.org/dl/android_securecoding/index.html)
- Android Developers「Security with network protocols」(証明書ピンニング)(https://developer.android.com/privacy-and-security/security-ssl)
- Android Developers「Network security configuration」(https://developer.android.com/privacy-and-security/security-config)
- Android Developers「Android Keystore system」(https://developer.android.com/privacy-and-security/keystore)
- Android Developers「Play Integrity API」(https://developer.android.com/google/play/integrity)
- Android Developers「Android developer verification」(https://developer.android.com/developer-verification)
- Android Developers「Data and file storage overview」(https://developer.android.com/training/data-storage)
- AndroidX Security リリースノート(security-crypto)(https://developer.android.com/jetpack/androidx/releases/security)
- Android Developers「Enable app optimization」(R8)(https://developer.android.com/topic/performance/app-optimization/enable-app-optimization)
- Apple Developer「Establishing your app's integrity」(App Attest)(https://developer.apple.com/documentation/devicecheck/establishing-your-app-s-integrity)
- Apple Developer「Keychain services」(https://developer.apple.com/documentation/security/keychain-services)
- Apple Platform Security Guide(https://support.apple.com/guide/security/welcome/web)
- IPA 令和7年度 春期 情報処理安全確保支援士試験 解答例(https://www.ipa.go.jp/shiken/mondai-kaiotu/nl10bi0000009lh8-att/2025r07h_sc_pm_ans.pdf)
+おまけ:毎日1通の無料メール講座つき。登録した日が「1日目」、図解と論理で16週間かけて基礎も固まります。