メインコンテンツに移動

モバイルのデータ暗号化とは?iOS・Androidの安全な保存方法と実装例を徹底解説

スマートフォンには、ログイン情報、個人情報、決済関連情報、位置情報、写真、業務文書など、多くの重要なデータが保存されます。端末は日常的に持ち運ばれるため、紛失、盗難、不正なアプリ、端末解析などによってデータへアクセスされる危険があります。モバイルアプリを開発する際は、アプリが正常に動作することだけでなく、端末や通信経路からデータが取得された場合でも内容を読まれにくくする設計が必要です。

データ暗号化は、読み取れる情報を、正しい暗号鍵を持つ処理だけが元に戻せる形式へ変換する技術です。ただし、データを暗号化するだけでは十分ではありません。暗号鍵をアプリ内へ直接記述していたり、ログやバックアップへ暗号化前の情報を残していたりすれば、暗号化を導入していても情報漏えいが発生します。

モバイルにおけるデータ保護では、保存データ、通信データ、暗号鍵、認証情報、バックアップ、ログなどを一つの流れとして設計する必要があります。OWASPのモバイルアプリ向け基準でも、安全な保存、暗号技術、通信、認証は個別の重要領域として整理されています。

1. モバイルのデータ暗号化とは

モバイルのデータ暗号化とは、端末内に保存する情報やサーバーとの間で送受信する情報を、第三者が容易に解読できない形式へ変換することです。端末が攻撃者の手に渡る可能性も考慮し、暗号化だけでなく保存場所、鍵の利用条件、復号できる時間も制御します。

1.1 読み取れるデータを暗号文へ変換する

暗号化では、文章、画像、認証情報などの元データと暗号鍵を暗号処理へ入力し、元の内容を直接読み取れない暗号文を生成します。復号処理では、対応する暗号鍵を使用して暗号文を元のデータへ戻します。

端末内のファイルを取得されても、暗号鍵が適切に保護されていれば、攻撃者は内容を簡単には確認できません。ただし、暗号鍵と暗号文を同じ場所へ無防備に保存すると、両方を同時に取得されるため、保護の効果が大きく低下します。

1.2 保存時と通信時の両方を保護する

保存時の暗号化は、端末内のデータベース、ファイル、設定情報、認証情報などを保護します。通信時の暗号化は、アプリとサーバーの間で送受信される情報を、通信経路上で盗み見られたり書き換えられたりする危険から守ります。

保存時だけを暗号化しても、通信が平文であれば送信中の情報が漏えいします。反対に、通信だけを暗号化しても、端末内へ平文で保存された情報は端末解析によって取得される可能性があるため、両方を設計対象にする必要があります。

1.3 暗号化はアクセス制御とは異なる

アクセス制御は、利用者や処理にデータを利用する権限があるかを判断する仕組みです。暗号化は、データそのものを読み取れない形式へ変換する仕組みであり、両者は異なる役割を持ちます。

アプリのログイン画面だけでデータを保護しようとしても、端末内の保存ファイルを直接取得された場合には対応できません。認証、権限確認、暗号化、端末の安全な保存領域を組み合わせることで、複数の防御層を作ります。

1.4 暗号化しても完全に安全になるわけではない

アプリがデータを表示または処理するときには、一時的に復号された情報が記憶領域へ展開されます。端末が不正に改造されていたり、アプリの処理が解析されたりすれば、復号後の情報を狙われる可能性があります。

暗号化は重要な防御策ですが、単独ですべての攻撃を防ぐものではありません。保存期間の短縮、必要最小限のデータ取得、画面保護、ログ制御、サーバー側の権限確認などを組み合わせる必要があります。

1.5 独自の暗号方式を作ってはいけない

暗号処理は、わずかな実装ミスでも安全性が失われます。独自の文字置換、固定値による変換、単純な排他的論理和などは、見た目が読みにくくなっても、安全な暗号化とはいえません。

実装では、基本ソフトが提供する暗号機能や、十分に検証された暗号ライブラリを利用します。OWASPも、古い方式や独自方式を避け、業界で評価された暗号方式を正しく使用することを重視しています。

2. 暗号化・ハッシュ化・符号化の違い

暗号化、ハッシュ化、符号化は、いずれもデータの形を変える処理ですが、目的が異なります。これらを混同すると、パスワードを復号可能な状態で保存したり、符号化しただけの情報を暗号化済みと判断したりする問題が発生します。

比較項目暗号化ハッシュ化符号化
主な目的秘密情報の保護一致確認、改ざん確認表現形式の変換
元へ戻せるか正しい鍵があれば戻せる原則として元へ戻さない手順が分かれば戻せる
秘密性提供できる単独では提供しない提供しない
主な用途個人情報、ファイル、通信パスワード確認、完全性確認文字列、画像、通信形式
通常は必要通常は不要不要
代表例AES-GCM、公開鍵暗号SHA-256、パスワード用導出処理Base64、URL形式変換

2.1 暗号化は復号を前提とする

暗号化は、後から元の情報を利用する必要があるデータに適用します。住所、氏名、文書、認証用の更新情報など、アプリやサーバーが内容を再利用する場合は、暗号鍵を用いて復号します。

暗号文だけでは元の情報を読み取れないことが重要ですが、同時に正規の処理では確実に復号できなければなりません。そのため、暗号鍵の消失や破損は、データそのものを失うことにつながります。

2.2 ハッシュ化は一致確認に利用する

ハッシュ化は、入力データから一定の長さの値を生成する処理です。一般的には元のデータへ戻すことを前提とせず、入力内容が同じか、途中で変更されていないかを確認するために利用します。

利用者のパスワードを保存する場合は、復号可能な暗号化よりも、パスワード保存用に設計された導出処理を使用します。単純な高速ハッシュだけでは総当たり攻撃に弱いため、ソルトや計算負荷を考慮した方式が必要です。

2.3 符号化は秘密性を提供しない

Base64などの符号化は、バイナリデータを文字列として扱いやすくするための変換です。変換手順は秘密ではなく、誰でも容易に元へ戻せるため、個人情報や認証情報の保護には利用できません。

設定ファイル内の秘密情報をBase64へ変換しても、安全性は向上しません。画面上で直接読みにくくなるだけであり、解析すれば短時間で元の情報を取得できます。

2.4 署名と暗号化にも違いがある

電子署名は、データの作成者や改ざんの有無を確認するために利用します。暗号化が内容を秘密にする処理であるのに対し、署名はデータの真正性と完全性を確認する役割を持ちます。

重要な通信では、暗号化と署名または認証タグを組み合わせます。AES-GCMのような認証付き暗号では、内容を隠すだけでなく、暗号文が途中で変更されていないかも確認できます。

2.5 目的から処理方法を選択する

後から内容を読み取る必要があるなら暗号化、元へ戻さず一致確認だけを行うならハッシュ化、通信や保存のために表現形式を変更するだけなら符号化を利用します。

一つのデータへ複数の処理を組み合わせる場合もあります。例えば、暗号文を通信可能な文字列へ変換するために、暗号化後のバイナリデータをBase64へ符号化する構成が考えられます。

3. モバイルで暗号化すべきデータ

すべての情報を同じ方法で暗号化すると、性能と保守性が低下します。最初にデータを分類し、漏えいした場合の影響、保存期間、利用頻度に応じて保護方法を決めることが重要です。

3.1 認証情報とセッション情報

アクセストークン、更新用トークン、利用者を識別する秘密情報は、第三者に取得されるとアカウントの不正利用につながります。通常の設定保存領域へ平文で保存せず、基本ソフトが提供する安全な保存機能を利用します。

認証情報は必要以上に長く保存しないことも重要です。短い有効期限、サーバー側の無効化、端末変更時の再認証などを組み合わせ、漏えい時の影響を限定します。

3.2 個人情報

氏名、住所、電話番号、生年月日、位置情報などは、利用目的に必要な範囲だけを保存します。サーバーから毎回取得できる情報であれば、端末へ長期間保存しない選択も検討します。

オフライン利用のために保存する場合は、暗号化されたデータベースやファイルを利用し、復号鍵を安全な鍵管理領域で保護します。表示後に不要となる一時情報も、保存領域や記録出力へ残らないようにします。

3.3 金融・医療・業務関連情報

決済履歴、口座情報、診療情報、社内文書などは、漏えい時の影響が大きいデータです。暗号化だけでなく、利用者の再認証、画面表示の制限、操作履歴、サーバー側の権限確認が必要になります。

業務用アプリでは、管理対象端末だけで利用できるようにしたり、一定時間操作がなければ復号済み画面を閉じたりする設計も有効です。データの重要度に応じて防御層を増やします。

3.4 写真・動画・音声

本人確認書類、医療画像、現場写真、録音データなどは、通常の画像表示領域へ保存すると他のアプリや利用者から見える可能性があります。公開領域への保存を避け、アプリ専用領域と暗号化を組み合わせます。

大容量ファイルを暗号化すると処理時間や記憶領域の使用量が増えるため、全体を一度に記憶領域へ読み込まない設計が必要です。分割処理や一時ファイルの削除も考慮します。

3.5 利用者が入力した一時情報

入力途中の申込内容、検索履歴、下書き、貼り付けた文字列なども、内容によっては重要情報になります。正式な保存対象でなくても、自動保存や障害記録によって端末へ残る場合があります。

一時情報には保存期限を設定し、処理完了後やログアウト時に削除します。暗号化するかどうかだけでなく、そもそも保存する必要があるかを判断することが重要です。

4. 端末内の保存データを守る設計

端末内データの保護では、アプリ専用領域へ保存するだけでなく、データ自体の暗号化と暗号鍵の分離が重要です。OWASPの基準では、意図的に保存した重要データだけでなく、バックアップやログなどへの意図しない漏えいも確認対象になります。

4.1 保存するデータを最小限にする

最も安全なデータは、端末に存在しないデータです。画面表示のたびにサーバーから取得できる情報や、短時間しか必要ない情報は、永続的に保存しない設計を検討します。

保存量を減らすと、漏えい時の影響だけでなく、暗号鍵の管理、移行、削除、バックアップ対応も簡単になります。利便性のために保存する場合は、保存期間と削除条件を明確にします。

4.2 アプリ専用領域を利用する

一般公開される共有領域やダウンロード領域は、利用者や他の処理からアクセスされる可能性があります。重要データは、基本ソフトがアプリごとに分離する専用領域へ保存します。

ただし、アプリ専用領域であっても、端末解析や不正改造に対して完全に安全ではありません。重要度の高い情報は、専用領域に保存したうえで暗号化します。

4.3 暗号鍵と暗号文を分離する

暗号化されたデータベースと、その復号鍵を同じ設定ファイルへ保存すると、攻撃者が両方を同時に取得できます。暗号文はアプリ領域へ保存し、鍵はKeychainやAndroid Keystoreなどの鍵管理機能で保護します。

鍵管理機能では、鍵そのものを取り出さず、基本ソフト内部で暗号処理だけを実行できる場合があります。これにより、アプリの処理領域へ鍵の生データを展開する機会を減らせます。

4.4 認証付き暗号を利用する

暗号文の内容を秘密にするだけでは、攻撃者による書き換えを検出できない場合があります。認証付き暗号を利用すると、復号時に暗号文の完全性も確認できます。

AES-GCMは、暗号化された内容と認証タグを組み合わせる方式です。認証に失敗した場合は、破損または改ざんされたデータとして扱い、内容を利用してはいけません。

4.5 平文の一時ファイルを残さない

暗号化ファイルを編集する際に、平文の一時ファイルを公開領域へ作成すると、その一時ファイルから情報が漏れる可能性があります。処理途中の保存場所も保護対象に含めます。

一時ファイルが必要な場合はアプリ専用領域を使い、処理完了後に削除します。異常終了時にも残る可能性があるため、次回起動時に不要な一時ファイルを確認して削除する仕組みも用意します。

5. 通信データを暗号化する

モバイルアプリとサーバー間の通信では、HTTPSを利用し、証明書を正しく検証することが基本です。平文通信を許可すると、同じ通信環境にいる第三者が内容を確認したり書き換えたりする危険があります。

5.1 HTTPSを標準にする

ログイン情報や個人情報を含まない通信であっても、原則としてHTTPSを利用します。設定情報や更新情報を書き換えられると、アプリの動作や利用者の判断に影響する可能性があるためです。

一部の通信だけをHTTPSにするのではなく、画像、設定、外部連携を含むすべての通信先を確認します。開発環境で利用していたHTTP接続が、本番設定へ残らないようにします。

5.2 証明書検証を無効化しない

開発中に自己署名証明書を利用するため、すべての証明書を信頼する処理を追加することがあります。この処理が本番へ残ると、攻撃者が用意した証明書でも通信できる状態になります。

証明書エラーを無条件に無視する独自処理は避けます。開発用証明書が必要な場合は、開発構成だけに限定した信頼設定を使用し、本番構成から分離します。

5.3 iOSでは通信保護機能を維持する

AppleのApp Transport Securityは、アプリの通信に安全な接続方針を適用し、プライバシーとデータ完全性を高めます。例外設定で制限全体を無効にすると、意図せず安全でない接続を許可する可能性があります。

接続先の都合で例外が必要な場合も、すべての通信を許可するのではなく、対象を必要最小限に限定します。例外が不要になった時点で削除できるよう、理由と対象を記録します。

5.4 Androidでは平文通信を拒否する

Androidでは、ネットワークセキュリティ設定を利用して、接続先ごとの信頼設定や平文通信の可否を管理できます。安全でない通信を全体的に許可する設定は避けるべきです。

Androidの通信設定例

 

<?xml version="1.0" encoding="utf-8"?> <network-security-config>    <base-config cleartextTrafficPermitted="false">        <trust-anchors>            <certificates src="system" />        </trust-anchors>    </base-config> </network-security-config>

 

この設定では平文通信を許可せず、端末が信頼する証明書を利用します。開発用の例外が必要な場合は、本番設定と混在させず、開発専用設定として分離します。

5.5 通信暗号化と本文暗号化を使い分ける

HTTPSはアプリと通信先の間を保護しますが、サーバーへ到着した後のデータ利用までは制御しません。通信先のサーバーでも内容を読めない状態が必要なら、本文を別途暗号化する設計が必要です。

ただし、本文暗号化を追加すると、鍵配布、端末変更、検索、バックアップ、複数端末同期が複雑になります。業務要件と脅威を整理し、HTTPSだけで十分か、利用者間の暗号化まで必要かを判断します。

6. 暗号鍵を安全に管理する

暗号化の安全性は、暗号方式だけでなく暗号鍵の管理に左右されます。強力な方式を採用していても、鍵がアプリ内へ固定されていれば、アプリ解析によって暗号文を復号される可能性があります。

6.1 鍵をソースコードへ直接記述しない

アプリのソースコード、設定ファイル、画像資源へ固定鍵を記述すると、配布されたアプリから鍵を抽出される可能性があります。難読化しても、秘密情報を完全に保護することはできません。

Flutterの公式資料でも、難読化は資源の暗号化や逆解析の防止にはならず、アプリ内へ秘密情報を保存すること自体が望ましくないと説明されています。

6.2 端末ごとに異なる鍵を使用する

すべての端末で同じ暗号鍵を使用すると、一台から鍵が漏えいしただけで、他の利用者のデータにも影響します。可能な限り、利用者、端末、データ領域ごとに異なる鍵を使用します。

端末内で生成した鍵を安全な鍵管理領域へ保存すれば、生の鍵をサーバーから配布せずに済みます。ただし、端末移行やデータ共有が必要な場合は、鍵の復旧方法を別途設計します。

6.3 鍵の用途を限定する

一つの鍵を暗号化、署名、認証など複数の用途へ使い回すと、設計が複雑になり、問題発生時の影響範囲も広がります。用途ごとに鍵を分離することが望まれます。

Android Keystoreでは、鍵を生成する際に利用目的、暗号方式、利用者認証の必要性などを制限できます。鍵が存在するだけでなく、どの処理で使用できるかを制御することが重要です。

6.4 ノンスを再利用しない

AES-GCMなどでは、暗号化ごとに一度だけ使用する値が必要です。同じ鍵と同じノンスの組み合わせを繰り返し使用すると、安全性が失われる可能性があります。

通常は暗号ライブラリに安全な生成を任せ、自作した連番や固定値を使用しません。暗号文を保存する際は、復号に必要なノンスと認証タグも適切に保持します。

6.5 鍵の更新と破棄を設計する

長期間同じ鍵を使用すると、漏えい時に影響するデータ量が増えます。一定期間、アプリの重要な更新、利用者の再登録などを契機として鍵を更新する設計を検討します。

鍵を破棄すると、その鍵で暗号化した情報を復号できなくなります。ログアウト時に削除する鍵、端末変更時に移行する鍵、長期保存データに利用する鍵を区別して管理します。

7. iOSでデータを暗号化する

iOSでは、小さな秘密情報をKeychainへ保存し、大きなデータはCryptoKitなどで暗号化してファイルやデータベースへ保存する構成が一般的です。AppleはKeychainを、パスワードや暗号鍵などの小さな秘密情報を保存する場所として案内しています。

7.1 Keychainへ小さな秘密情報を保存する

Keychainは、パスワード、トークン、暗号鍵などの小さなデータを保存するための暗号化された保存機能です。通常の利用者設定保存領域とは用途を分けます。

大きな文書や画像全体をKeychainへ保存するのではなく、それらを暗号化するための鍵を保存します。暗号文本体はアプリ専用ファイルやデータベースへ保存します。

Keychainへデータを保存する例

 

import Foundation import Security enum KeychainError: Error {    case saveFailed(OSStatus)    case readFailed(OSStatus)    case invalidData } func saveToKeychain(    service: String,    account: String,    value: Data ) throws {    let deleteQuery: [String: Any] = [        kSecClass as String: kSecClassGenericPassword,        kSecAttrService as String: service,        kSecAttrAccount as String: account    ]    SecItemDelete(deleteQuery as CFDictionary)    let addQuery: [String: Any] = [        kSecClass as String: kSecClassGenericPassword,        kSecAttrService as String: service,        kSecAttrAccount as String: account,        kSecValueData as String: value,        kSecAttrAccessible as String:            kSecAttrAccessibleWhenUnlockedThisDeviceOnly    ]    let status = SecItemAdd(        addQuery as CFDictionary,        nil    )    guard status == errSecSuccess else {        throw KeychainError.saveFailed(status)    } }

 

この例では、端末がロック解除されている間だけ利用でき、別端末への移行対象にならない属性を指定しています。実際の属性は、バックグラウンド処理や端末移行の要件に合わせて選択します。

7.2 Keychainから秘密情報を読み出す

Keychainからデータを取得する際は、保存時と同じサービス名とアカウント名を利用します。取得できなかった場合を、単なる空文字として扱わず、未登録、アクセス不可、破損などに分けて処理します。

Keychainからデータを取得する例

 

func readFromKeychain(    service: String,    account: String ) throws -> Data? {    let query: [String: Any] = [        kSecClass as String: kSecClassGenericPassword,        kSecAttrService as String: service,        kSecAttrAccount as String: account,        kSecReturnData as String: true,        kSecMatchLimit as String: kSecMatchLimitOne    ]    var result: CFTypeRef?    let status = SecItemCopyMatching(        query as CFDictionary,        &result    )    if status == errSecItemNotFound {        return nil    }    guard status == errSecSuccess else {        throw KeychainError.readFailed(status)    }    guard let data = result as? Data else {        throw KeychainError.invalidData    }    return data }

 

読み出した秘密情報は、必要な処理にだけ利用し、長時間保持しないようにします。また、記録出力や障害報告へ値を含めないことが重要です。

7.3 CryptoKitでAES-GCMを使用する

CryptoKitでは、AES-GCMを利用してデータの暗号化と完全性確認を実装できます。暗号化結果には、復号に必要なノンス、暗号文、認証タグが含まれます。

CryptoKitによる暗号化例

 

import CryptoKit import Foundation enum EncryptionError: Error {    case invalidCombinedData } func encrypt(    plaintext: Data,    using key: SymmetricKey ) throws -> Data {    let sealedBox = try AES.GCM.seal(        plaintext,        using: key    )    guard let combined = sealedBox.combined else {        throw EncryptionError.invalidCombinedData    }    return combined } func decrypt(    encryptedData: Data,    using key: SymmetricKey ) throws -> Data {    let sealedBox = try AES.GCM.SealedBox(        combined: encryptedData    )    return try AES.GCM.open(        sealedBox,        using: key    ) }

 

復号時に認証タグの確認へ失敗した場合は、データが破損または改ざんされている可能性があります。例外を無視して不完全なデータを利用してはいけません。

7.4 暗号鍵をKeychainと組み合わせる

AES-GCMで使用する対称鍵をアプリのソースコードへ記述せず、初回起動時に生成してKeychainへ保存します。以後は暗号化や復号の直前に取得します。

アプリを削除して再インストールした場合の挙動や、端末移行時に鍵を移すかどうかも決める必要があります。鍵を移行しない構成では、古い暗号文を新しい環境で復号できません。

7.5 保護属性を利用状況に合わせる

Keychain項目には、端末がロック解除された後に利用できるものや、ロック解除中だけ利用できるものなど、複数の保護属性があります。最も厳しい属性を無条件に選ぶと、必要なバックグラウンド処理が動作しない可能性があります。

重要なのは、アプリがいつデータを利用する必要があるかを整理することです。画面表示中だけ必要な秘密情報と、端末ロック中のバックグラウンド更新に必要な情報を分けて保存します。

8. Androidでデータを暗号化する

Androidでは、Android Keystoreへ暗号鍵を保存し、その鍵を利用してデータを暗号化します。Keystore内の鍵は取り出しにくい形で保護でき、鍵の用途や利用者認証の条件も指定できます。

8.1 Android Keystoreで鍵を生成する

Android Keystoreは、暗号鍵や証明書を管理する仕組みです。対応端末では、鍵の処理を安全な機器領域で実行できる場合があります。

アプリ側では鍵の識別名を使って処理を呼び出します。鍵の生データを設定ファイルへ保存したり、サーバーから固定鍵として配布したりする構成を避けられます。

AES鍵を生成する例

 

import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import java.security.KeyStore import javax.crypto.KeyGenerator import javax.crypto.SecretKey private const val KEY_ALIAS = "mobile_data_key" fun getOrCreateSecretKey(): SecretKey {    val keyStore = KeyStore.getInstance(        "AndroidKeyStore"    ).apply {        load(null)    }    val existingKey = keyStore.getKey(        KEY_ALIAS,        null    ) as? SecretKey    if (existingKey != null) {        return existingKey    }    val keyGenerator = KeyGenerator.getInstance(        KeyProperties.KEY_ALGORITHM_AES,        "AndroidKeyStore"    )    val parameterSpec = KeyGenParameterSpec.Builder(        KEY_ALIAS,        KeyProperties.PURPOSE_ENCRYPT or            KeyProperties.PURPOSE_DECRYPT    )        .setBlockModes(            KeyProperties.BLOCK_MODE_GCM        )        .setEncryptionPaddings(            KeyProperties.ENCRYPTION_PADDING_NONE        )        .setKeySize(256)        .build()    keyGenerator.init(parameterSpec)    return keyGenerator.generateKey() }

 

この例では、AES-GCMで暗号化と復号に利用する鍵を生成しています。端末や対応条件によって利用可能な鍵長や安全な機器領域の状況が異なるため、対象環境で確認します。

8.2 AES-GCMでデータを暗号化する

暗号化時には、Keystoreから鍵の参照を取得し、Cipherへ渡します。生成された初期化ベクトルは復号時に必要になるため、暗号文と一緒に保存します。

Androidの暗号化例

 

import javax.crypto.Cipher import javax.crypto.SecretKey data class EncryptedPayload(    val initializationVector: ByteArray,    val ciphertext: ByteArray ) fun encryptData(    plaintext: ByteArray,    secretKey: SecretKey ): EncryptedPayload {    val cipher = Cipher.getInstance(        "AES/GCM/NoPadding"    )    cipher.init(        Cipher.ENCRYPT_MODE,        secretKey    )    val encrypted = cipher.doFinal(plaintext)    return EncryptedPayload(        initializationVector = cipher.iv,        ciphertext = encrypted    ) }

 

初期化ベクトルは秘密情報ではありませんが、暗号文との対応関係を正しく保つ必要があります。固定値を指定せず、暗号処理が生成した値を利用します。

8.3 暗号文を復号する

復号時には、暗号化に使用した鍵、初期化ベクトル、暗号文を指定します。認証タグが暗号文に含まれるため、内容が変更されていれば復号処理が失敗します。

Androidの復号例

 

import javax.crypto.Cipher import javax.crypto.SecretKey import javax.crypto.spec.GCMParameterSpec fun decryptData(    payload: EncryptedPayload,    secretKey: SecretKey ): ByteArray {    val cipher = Cipher.getInstance(        "AES/GCM/NoPadding"    )    val parameterSpec = GCMParameterSpec(        128,        payload.initializationVector    )    cipher.init(        Cipher.DECRYPT_MODE,        secretKey,        parameterSpec    )    return cipher.doFinal(        payload.ciphertext    ) }

 

復号失敗を単なるデータなしとして扱うと、改ざんや鍵不一致を見逃します。利用者へ表示する文言と、内部で記録するエラー分類を分け、安全な範囲で原因を追跡します。

8.4 利用者認証を鍵の利用条件にする

重要度の高い鍵には、指紋、顔、端末認証などを完了した場合だけ使用できる条件を設定できます。これにより、端末がロック解除されたまま第三者へ渡った場合の危険を減らせます。

ただし、生体認証は暗号化そのものではありません。利用者が鍵の使用を許可する仕組みとして組み合わせます。認証できない場合の代替手段や、端末設定変更時の挙動も設計します。

8.5 外部保存領域を避ける

重要データを共有可能な外部領域へ保存すると、他の処理や利用者から取得される可能性があります。業務要件で外部領域へ保存する必要がある場合は、データを暗号化し、鍵をKeystoreで保護します。

Androidの公式資料でも、外部領域へ重要データを保存する場合は強力な暗号化を使用し、鍵をAndroid Keystoreで保護することが推奨されています。

9. クロスプラットフォーム開発で暗号化する

FlutterやReact Nativeなどでも、最終的にはiOSとAndroidの安全な保存機能を利用する必要があります。共通プログラムだけで秘密情報を保護しようとせず、各基本ソフトのKeychainやKeystoreへ接続します。

9.1 通常の設定保存と安全な保存を分ける

テーマ、表示言語、初回説明の確認状態などは、一般的な設定保存機能で管理できます。一方、認証情報、暗号鍵、秘密の識別情報などは、安全な保存機能へ分離します。

Flutterの一般的な設定保存機能は、小さなキーと値を永続化するためのものであり、秘密情報専用の鍵管理機能ではありません。保存機能の便利さだけで選ばず、データの重要度で使い分けます。

9.2 Flutterでは安全な保存用プラグインを利用する

Flutterでは、iOSのKeychainやAndroidの安全な保存機能へ接続するプラグインを利用できます。代表的な安全保存プラグインは、基本ソフトごとの保護機能を利用して小さな値を保存します。

Flutterで認証情報を保存する例

 

import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class SecureTokenRepository {  SecureTokenRepository(this._storage);  final FlutterSecureStorage _storage;  static const _accessTokenKey = 'access_token';  Future<void> saveAccessToken(    String token,  ) async {    if (token.isEmpty) {      throw ArgumentError(        'トークンを空にすることはできません',      );    }    await _storage.write(      key: _accessTokenKey,      value: token,    );  }  Future<String?> readAccessToken() {    return _storage.read(      key: _accessTokenKey,    );  }  Future<void> deleteAccessToken() {    return _storage.delete(      key: _accessTokenKey,    );  } }

 

プラグインの利用だけで安全性が保証されるわけではありません。各基本ソフトで使用される保護属性、バックアップ設定、端末移行時の挙動を確認します。

9.3 大容量データを安全保存へ直接入れない

安全なキーと値の保存機能は、トークンや暗号鍵などの小さな情報に向いています。画像、文書、データベース全体を保存すると、性能や容量の問題が発生します。

大容量データは共通側またはネイティブ側で暗号化し、暗号文をファイルとして保存します。暗号化に使用する鍵だけを安全な保存機能へ入れる構成にします。

9.4 プラグインの更新状況を確認する

安全な保存プラグインは、基本ソフトの仕様変更へ対応する必要があります。更新が停止したプラグインを使い続けると、新しい端末や基本ソフトで保存・復号できなくなる可能性があります。

導入時には、更新履歴、対応環境、未解決問題、移行手順を確認します。重要な保存処理を一つのプラグインへ直接依存させず、アプリ側に保存用の抽象層を設けると交換しやすくなります。

9.5 共通の保存窓口を作る

画面や業務処理から安全な保存機能を直接呼び出すと、保存方法の変更がアプリ全体へ影響します。認証情報保存、暗号鍵保存、一般設定保存を別々の管理部品へ分けます。

保存する値の名前、削除条件、失敗時の処理を一元管理すると、ログアウト、端末変更、アカウント切り替えの実装も整理しやすくなります。

10. データベースとファイルを暗号化する

モバイルアプリでは、構造化された情報をデータベースへ保存し、画像や文書をファイルへ保存することがあります。それぞれの保存方法に合わせて暗号化範囲と鍵管理を設計します。

10.1 データベース全体を暗号化する

データベース全体の暗号化では、保存ファイルを取得されても、正しい鍵がなければ内容を読みにくくできます。オフラインで大量の個人情報や業務情報を扱う場合に有効です。

一方、起動時や照会時に復号処理が必要になるため、性能への影響を確認します。利用予定のデータ量に近い試験データを用意し、低性能端末でも測定します。

10.2 特定の列だけを暗号化する

すべての項目を暗号化せず、氏名、住所、識別番号など重要な列だけを暗号化する方法があります。検索や並べ替えに必要な情報を平文で残しながら、秘密情報を保護できます。

ただし、平文の関連情報から利用者を推測できる場合があります。直接的な個人情報だけでなく、複数項目を組み合わせた場合の識別可能性も評価します。

10.3 ファイルごとに異なる鍵を利用する

重要度の高い文書では、ファイルごとに異なるデータ暗号鍵を生成し、その鍵を別の管理鍵で保護する構成があります。一つのファイル鍵が漏えいしても、他のファイルへの影響を抑えられます。

ただし、鍵の数が増えるため、識別、更新、削除、同期が複雑になります。保護対象の重要度と運用負担を比較して採用します。

10.4 大容量ファイルは分割処理する

動画や高解像度画像を一度に記憶領域へ読み込むと、アプリが停止したり強制終了したりする可能性があります。入力を一定量ずつ読み込みながら暗号化する方法を検討します。

暗号化途中で処理が失敗した場合に、不完全な暗号文や平文の一時ファイルが残らないようにします。完了前のファイルと正式なファイルを区別し、成功後に置き換えます。

10.5 暗号化済みデータの版を管理する

暗号方式、鍵、保存形式を将来変更できるよう、暗号文には形式の版や鍵識別子を関連付けます。版情報がなければ、どの方式で復号すべきか判断できなくなります。

ただし、暗号方式の詳細や秘密鍵を暗号文と一緒に保存する必要はありません。復号に必要な非秘密情報だけを持たせ、移行時に旧形式から新形式へ変換します。

11. 認証トークンとセッションを保護する

認証トークンは、パスワードそのものではなくても、利用者としてサーバーへアクセスできる重要情報です。保存場所、有効期限、更新、失効、ログアウト処理を一体として設計します。

11.1 アクセストークンを長期間保存しない

アクセストークンは有効期限を短くし、必要なときだけ利用します。長期間有効なトークンが漏えいすると、不正利用が長く継続する可能性があります。

期限切れ後は更新用トークンを使って新しいアクセストークンを取得します。更新用トークンはさらに重要度が高いため、安全な保存領域へ保管します。

11.2 トークンを通常設定へ保存しない

認証トークンを一般的な設定保存機能へ平文で保存すると、端末解析やバックアップから取得される可能性があります。iOSではKeychain、AndroidではKeystoreと連携する安全な保存方式を利用します。

トークンの有無だけが必要な画面では、トークン値そのものを画面状態へ保持し続けないようにします。認証管理層が必要な通信にだけ付与します。

11.3 ログへトークンを出力しない

通信確認のために要求ヘッダー全体を記録すると、認証トークンが開発記録、障害報告、外部監視サービスへ送信される可能性があります。秘密情報を除外する記録処理が必要です。

本番環境では通信本文や認証ヘッダーを原則として出力しません。問題調査に識別情報が必要な場合は、元の値ではなく、個人を直接特定できない追跡番号を使用します。

11.4 ログアウト時に削除する

利用者がログアウトしたら、アクセストークン、更新用トークン、暗号化された一時データ、利用者固有の鍵を適切に削除します。画面をログイン画面へ戻すだけでは不十分です。

複数アカウントを切り替えられるアプリでは、前の利用者のデータが次の利用者へ表示されないことを確認します。記憶領域、保存領域、画像一時保存のすべてを対象にします。

11.5 サーバー側でも失効できるようにする

端末からトークンを削除しても、すでにコピーされたトークンは有効期限まで利用される可能性があります。重要なサービスでは、サーバー側で更新用トークンやセッションを無効化できる仕組みが必要です。

パスワード変更、不審なアクセス、端末紛失の報告などを契機に、対象端末のセッションを失効させます。モバイル側の暗号化だけに依存せず、サーバー側でも被害を制限します。

12. 生体認証と暗号化を組み合わせる

指紋や顔による認証は、利用者が秘密情報へアクセスする際の確認方法として利用できます。ただし、生体情報そのものをアプリが保存したり、生体認証だけでデータを暗号化したりするわけではありません。

12.1 生体認証は鍵の利用を許可する

一般的な構成では、暗号鍵を安全な鍵管理領域へ保存し、その鍵を使用する前に生体認証を求めます。認証に成功すると、一定の条件下で暗号処理が許可されます。

Androidでは、Keystoreの鍵利用条件と生体認証を組み合わせる方法が提供されています。重要情報や有料機能へのアクセス前に、利用者の存在を確認できます。

12.2 生体情報をアプリ内へ保存しない

指紋画像や顔画像を認証用としてアプリ独自に保存すると、漏えい時に変更できない生体情報が危険にさらされます。基本ソフトが提供する生体認証機能を利用します。

アプリが受け取るのは、通常、認証が成功したか失敗したかという結果です。生体情報の照合処理を独自に再現しないことが重要です。

12.3 代替認証を設計する

けが、端末交換、センサー故障、設定変更などにより、生体認証を利用できなくなる場合があります。端末の認証情報、アプリの暗証番号、再ログインなどの代替手段を用意します。

代替手段が生体認証より極端に弱いと、攻撃者は弱い経路を利用します。保護するデータの重要度に合わせて、代替認証の強度を決めます。

12.4 認証の有効時間を決める

操作のたびに生体認証を求めると、利用者の負担が大きくなります。一方、一度の認証で長時間すべてのデータを閲覧できると、端末を他人へ渡した場合の危険が増えます。

送金、個人情報の表示、秘密文書の開封など、重要操作の直前だけ再認証を求める方法があります。アプリ起動時の認証と重要操作時の再認証を分けて設計します。

12.5 画面ロックとアプリ認証を連携する

アプリが前面から離れた後、一定時間を超えて戻った場合には再認証を求めます。短時間の画面切り替えまで毎回認証すると操作性が低下するため、猶予時間を設定します。

猶予時間中でも、アプリ切り替え画面に重要情報が表示されないようにします。復帰時の認証だけでなく、背景へ移動した瞬間の画面保護も必要です。

13. バックアップ・ログ・画面からの漏えいを防ぐ

暗号化対象のデータベースを安全にしても、バックアップ、記録出力、画面画像、貼り付け領域などへ同じ情報が残れば漏えいします。意図しない保存経路を洗い出すことが重要です。

13.1 バックアップ対象を確認する

アプリの保存領域が端末やクラウドのバックアップ対象になる場合、認証情報や暗号鍵まで複製される可能性があります。保存場所と基本ソフトのバックアップ動作を確認します。

別端末へ暗号文だけが復元され、復号鍵が存在しない状態になることもあります。移行させるデータ、端末固有にする鍵、復元後に再取得する情報を分けます。

13.2 ログから重要情報を除外する

開発中の記録出力には、氏名、メールアドレス、通信本文、認証情報などが含まれやすくなります。障害調査サービスへ自動送信される記録も確認します。

記録出力用の共通処理を作り、秘密項目を伏せ字または除外します。本番構成では詳細な記録を無効にし、必要な状態番号や処理結果だけを残します。

13.3 画面画像への表示を制限する

認証情報、口座情報、医療情報などを表示している画面が、画面画像や画面録画へ残る可能性があります。重要画面では、基本ソフトが提供する画面保護機能を検討します。

ただし、画面画像を全面的に禁止すると、問い合わせや業務記録に支障が出る場合があります。情報の重要度と利用目的に応じて、保護対象画面を限定します。

13.4 アプリ切り替え画面を保護する

アプリが背景へ移動すると、直前の画面がアプリ切り替え画面の画像として表示されることがあります。秘密情報を表示していた場合、アプリを開かなくても内容が見える可能性があります。

背景移動時に覆い画面を表示し、復帰時に元へ戻す方法があります。金融、医療、業務アプリなどでは、利用者認証と組み合わせます。

13.5 貼り付け領域への保存を減らす

認証番号、口座番号、個人情報などを貼り付け領域へコピーすると、別のアプリや利用者が内容を取得する可能性があります。秘密情報のコピー機能は必要性を確認します。

コピーを許可する場合は、一定時間後に削除する方法や、利用者へ注意を表示する方法があります。ただし、基本ソフトや利用環境によって完全な制御が難しいため、過度に依存しない設計が必要です。

14. 暗号化機能を試験・監視する

暗号化処理は、正常に暗号化できることだけでなく、改ざん、鍵消失、端末移行、基本ソフト更新などの異常状態を確認する必要があります。試験不足は、公開後にデータを復号できなくなる重大な障害につながります。

14.1 暗号化と復号の往復を試験する

元データを暗号化し、復号した結果が元データと完全に一致することを確認します。空データ、日本語、絵文字、大容量データなど、複数の入力を使用します。

毎回異なる暗号文が生成される方式では、暗号文そのものの一致ではなく、復号結果と認証結果を確認します。暗号文の保存と読み込みを含む結合試験も必要です。

14.2 改ざんされた暗号文を拒否する

暗号文の一部、初期化ベクトル、認証タグを変更し、復号が失敗することを確認します。改ざんされた内容を部分的に表示したり、自動的に初期値として扱ったりしてはいけません。

失敗時には、安全なエラー処理を行い、必要であればサーバーからデータを再取得します。利用者向けメッセージへ暗号処理の内部情報を詳しく表示しないようにします。

14.3 鍵が存在しない場合を試験する

アプリ再インストール、端末移行、保存領域の破損などにより、暗号文は存在するものの鍵が存在しない状態が発生する可能性があります。この状態でアプリが停止し続けないことを確認します。

復旧できない端末内データは安全に削除し、サーバーから再同期する方法があります。サーバーにも存在しない唯一のデータであれば、鍵の復旧方法を別途用意する必要があります。

14.4 更新時の移行を試験する

暗号方式、鍵識別名、保存形式を変更した場合、旧版アプリで保存したデータを新版で復号できるか確認します。公開済みアプリでは、既存利用者のデータを無視できません。

移行は、旧形式で読み出し、新形式で再暗号化し、保存成功後に旧データを削除する順序で行います。途中で失敗しても元データを失わないようにします。

14.5 実機で安全性を確認する

模擬環境だけでなく、実際の端末でロック、再起動、生体認証変更、アプリ削除、バックアップ復元などを確認します。鍵管理機能は端末設定や機器構成の影響を受ける場合があります。

OWASPのモバイルアプリ試験資料では、保存領域に重要情報が残っていないかを静的・動的に確認する試験方法が整理されています。開発チームの確認に加え、重要なアプリでは第三者試験も検討します。

15. モバイル暗号化を導入する手順

暗号化は、開発の最後に一つの機能として追加するより、データ設計の段階から組み込むほうが安全です。保存対象、鍵、通信、認証、削除、移行を順番に整理します。

15.1 データ一覧を作成する

アプリが取得、生成、保存、送信するデータを一覧化します。利用者が入力する情報だけでなく、端末識別情報、位置情報、画像、通信記録、一時ファイルも含めます。

それぞれについて、保存場所、保存期間、利用目的、漏えい時の影響、削除条件を記録します。保存場所が分からないデータは、適切に保護することも削除することもできません。

15.2 重要度を分類する

データを公開可能、社内限定、個人情報、重大な秘密情報などに分類します。すべてを最高水準で保護するのではなく、影響度に応じて対策を選択します。

認証トークンや暗号鍵は安全な鍵管理領域へ、一般設定は通常設定へ、機密文書は暗号化ファイルへ保存するなど、分類と保存方法を対応させます。

15.3 脅威を整理する

端末紛失、盗難、不正改造、悪意あるアプリ、通信盗聴、サーバー侵害、内部不正など、想定する攻撃を整理します。すべての攻撃に同じ対策が有効とは限りません。

端末紛失には保存時暗号化と再認証、通信盗聴にはHTTPS、アプリ解析には固定鍵の排除など、脅威と対策を対応させます。目的のない暗号化を増やさないことも重要です。

15.4 小規模な実装で検証する

本格実装の前に、暗号化、保存、復号、鍵消失、端末再起動までを含む小規模な試作を行います。性能と実装難易度を確認し、採用する方式を決めます。

大容量データを扱う場合は、本番に近いデータ量で測定します。高性能な開発端末だけでなく、最低対応端末でも起動時間、暗号化時間、記憶領域使用量を確認します。

15.5 運用と鍵更新まで文書化する

暗号化機能の実装方法だけでなく、鍵の作成、更新、削除、障害復旧、端末移行、利用者退会時の処理を文書化します。担当者が変わっても運用できる状態が必要です。

採用した暗号方式、利用する基本ソフト機能、外部部品、保存形式を定期的に確認します。基本ソフトやライブラリの更新に合わせて、試験と移行計画を見直します。

おわりに

モバイルのデータ暗号化では、端末内に保存するデータと通信中のデータを分けて考え、それぞれに適した保護方法を適用します。認証情報や暗号鍵はKeychainやAndroid Keystoreなどの安全な保存機能で保護し、大きなデータはAES-GCMなどの検証された方式で暗号化して保存します。

ただし、暗号化処理を追加するだけでは十分ではありません。ソースコードへの固定鍵、平文のログ、無制限のバックアップ、保護されていない一時ファイル、証明書検証の無効化などが残っていれば、別の経路からデータが漏えいします。保存しない設計、短い保存期間、再認証、サーバー側の失効処理も重要です。

安全なモバイルアプリを構築するには、データ一覧の作成、重要度の分類、脅威の整理、鍵管理、異常状態の試験までを一つの流れとして設計する必要があります。暗号技術を独自に作らず、iOSとAndroidが提供する安全機能と実績のある暗号方式を正しく組み合わせることが、長期的に保守できるデータ保護につながります。

LINE Chat