🔒

機密情報と非機密情報の保存方法の違い

2024/12/08に公開

概要

iOSアプリにおいて、非機密情報と機密情報を分けて管理するために、UserDefaultsとKeychainという2つの仕組みを使い分ける。ここでは、それぞれの特徴と使用例について扱う。

UserDefaults

特徴

項目 内容
用途 ユーザーの設定やアプリの状態を保存するために使われる。
保存場所 アプリのサンドボックス内のファイルシステムに保存される。
セキュリティ データは暗号化されず、アプリのサンドボックス内で保護される。機密情報の保存には向かない。
アクセス 簡単にアクセスできる。

適しているデータ

  • アプリの設定(テーマモード、通知のオン/オフ)
  • 最終ログイン日時やアプリのバージョン情報
  • ユーザーの非機密な状態情報(アプリを初めて起動したときにチュートリアルを表示するための一時的なフラグ)

1. 保存、取得

let defaults = UserDefaults.standard

// 初回起動時のフラグを取得
if !defaults.bool(forKey: "hasSeenTutorial") {
    print("チュートリアルを表示")
    
    // チュートリアルを表示後、フラグを保存
    defaults.set(true, forKey: "hasSeenTutorial")
} else {
    print("チュートリアルは既に表示済み")
}

2. 更新

let defaults = UserDefaults.standard

// フラグをリセット
defaults.set(false, forKey: "hasSeenTutorial")
print("フラグを未表示に更新")

3. 削除

let defaults = UserDefaults.standard

// フラグを削除
defaults.removeObject(forKey: "hasSeenTutorial")
print("フラグを削除。")

Keychain

特徴

項目 内容
用途 機密性の高い情報を安全に保存するための仕組み。
保存場所 データは暗号化されてiOSシステムが管理するKeychainに保存される。
セキュリティ AppleのSecurityフレームワークを使用しており、データは暗号化される。
アクセス UserDefaultsよりもやや複雑。

適しているデータ

  • パスワード
  • 認証トークン
  • 機密性の高いAPIキー

1. 保存

クエリのキー

  • kSecClass
  • kSecAttrAccount
  • kSecAttrService
  • kSecValueData

1.1 kSecClass(アイテムの種類を指定)

役割:Keychainに保存するデータの種類(クラス)を指定する。
例:保存するデータが「一般的なパスワード」なのか、「証明書」なのかを指定する。

簡単な説明 どんなときに使うか 具体例
kSecClassGenericPassword アプリで使うパスワードや設定を保存するためのもの アプリのログイン情報やAPIキーを保存したいとき アプリの「ID」と「パスワード」、設定データ
kSecClassInternetPassword インターネットのログイン情報を保存するためのもの Webサービスやオンラインのアカウント情報を保存したいとき GmailやTwitterのログイン情報
kSecClassCertificate 安全な通信のための「証明書」を保存するもの データ通信を安全にするための情報を使うとき サーバーやアプリの信頼性を証明するデータ
kSecClassKey 暗号化やデータ保護に必要な「鍵」を保存するもの データを暗号化したり、デジタル署名を使いたいとき 安全な通信をするための秘密鍵や公開鍵
kSecClassIdentity 証明書と秘密鍵のペアを保存するもの サインや認証に使う証明書と鍵を一緒に管理したいとき Webサービスでユーザーの身分を確認するための証明書と秘密鍵

1.2 kSecAttrAccount(アカウント名を指定)

役割: 保存するデータに関連付けるアカウント名(例: ユーザー名やメールアドレス)を指定する。
例: アカウントを特定するために使用。

簡単な説明 どんなときに使うか 具体例
文字列 保存するアイテムのアカウント名 保存するアイテムをアカウントごとに管理したいとき user@example.comなど

1.3 kSecAttrService(サービス名を指定)

役割: 保存するデータを特定のサービスに関連付けるための識別子を指定する。
例: アプリやサービスを識別するために使用。

簡単な説明 どんなときに使うか 具体例
文字列 保存先のサービス名を指定 アプリや特定のサービス名に関連付けてデータを保存したいとき com.example.myappなど

1.4 kSecValueData(保存するデータそのもの)

役割: 実際に保存するデータ(例: パスワードやAPIキー)を指定する。
例: ユーザーのパスワードや認証情報を保存する。

簡単な説明 どんなときに使うか 具体例
Data型の値 保存するデータそのもの ユーザーのパスワードやAPIキーをKeychainに保存したいとき password123など(データ型で)
import Security

func savePasswordForAccount(account: String, service: String, password: String) -> Bool {
    let passwordData = password.data(using: .utf8)! // パスワードをデータ型に変換

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service,
        kSecValueData as String: passwordData
    ]

    SecItemDelete(query as CFDictionary) // 既存データを削除
    let status = SecItemAdd(query as CFDictionary, nil)

    return status == errSecSuccess
}

// 使用例
if savePasswordForAccount(account: yourEmail, service: "com.example.myapp", password: enteredPassword) {
    print("Keychainに保存しました!")
} else {
    print("保存に失敗しました。")
}

2. 取得

クエリのキー

  • kSecClass
  • kSecAttrAccount
  • kSecAttrService
  • kSecReturnData

2.1 kSecReturnData(データ部分を返すか指定

役割: 保存されている値(パスワードなど)を取得するかを指定する。
例: 実際のパスワードデータを取得したい場合に使用。

簡単な説明 どんなときに使うか 具体例
真偽値 データを返す設定 パスワードやAPIキーを取得したいとき true

※kSecReturnDataをfalseにするのは、データ本体が不要で、存在確認やアカウント名・サービス名などの属性情報だけを取得したい場合など。

func getPasswordForAccount(account: String, service: String) -> String? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]

    var item: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &item)

    if status == errSecSuccess, let data = item as? Data {
        return String(data: data, encoding: .utf8)
    }
    return nil
}

// 使用例
if let password = getPasswordForAccount(account: "user@example.com", service: "com.example.myapp") {
    print("取得したパスワード: \(password)")
} else {
    print("パスワードが見つかりませんでした。")
}

3. 更新

クエリのキー

  • kSecClass
  • kSecAttrAccount
  • kSecAttrService
func updatePasswordForAccount(account: String, service: String, newPassword: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service
    ]

    let updateFields: [String: Any] = [
        kSecValueData as String: newPassword.data(using: .utf8)!
    ]

    let status = SecItemUpdate(query as CFDictionary, updateFields as CFDictionary)
    return status == errSecSuccess
}

// 使用例
if updatePasswordForAccount(account: yourEmail, service: "com.example.myapp", newPassword: newPassword) {
    print("パスワードを更新しました!")
} else {
    print("更新に失敗しました。")
}

4. 削除

クエリのキー

  • kSecClass
  • kSecAttrAccount
  • kSecAttrService
func deletePasswordForAccount(account: String, service: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecAttrService as String: service
    ]

    let status = SecItemDelete(query as CFDictionary)
    return status == errSecSuccess
}

// 使用例
if deletePasswordForAccount(account: yourEmail, service: "com.example.myapp") {
    print("Keychainデータを削除しました!")
} else {
    print("削除に失敗しました。")
}

まとめ

UserDefaultsは非機密情報の保存に適し、Keychainはパスワードやトークンなど機密情報を安全に保存するために使用される。

Discussion