A Day In The Life

とあるプログラマの備忘録

enum駆使してKeychainをUserDefaultsっぽく使う

以前、こちらの記事で enum(列挙型)を使って UserDefaults のキー名を管理する方法について紹介しました。この方法を応用(ってほど大袈裟なもんじゃないけど)して Keychain にセキュアなデータを保存してみたいと思います。

Keychainって何?

API アクセスに使う認証用のトークン文字列なんかは UserDefaults じゃなくてもっとセキュアなところに保存する必要があります。そんなときに使うのが Keychain です。詳しい説明は以下の記事が参考になると思いますので以下をご一読ください

Keychainに文字列を保存する

enum に Keychain 用のキー名を列挙してデータ保存と取り出しのためのメソッドを追加します。

import Foundation
import Security

public enum Keychain: String {
  // キー名    
  case accessToken = "accessToken"
  case password = "password"

  // データの保存
  public func set(_ value: String) {
    let query: [String: AnyObject] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrAccount as String: self.rawValue as AnyObject,
      kSecValueData as String: value.data(using: .utf8)! as AnyObject
    ]
    SecItemDelete(query as CFDictionary)
    // エラー処理サボったのでこのへんはよしなに直してください
    SecItemAdd(query as CFDictionary, nil)
  }
  // データの取り出し
  public func value() -> String? {
    let query: [String: AnyObject] = [
      kSecClass as String: kSecClassGenericPassword,
      kSecAttrAccount as String: self.rawValue as AnyObject,
      kSecReturnData as String: kCFBooleanTrue,
      kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(query as CFDictionary, &result)   
    guard status == noErr else {
      return nil
    }
    guard let data = result as? Data else {
      return nil
    }
    return String(data: data, encoding: .utf8)
  }
}

使い方

使い方は以下のようになります。

// データの保存
Keychain.accessToken.set("xxxxxx")
// データの取り出し
if let value = Keychain.accessToken.value() {
  print(value) // xxxxxxと出力される
}

ユニットテスト時の注意点

こちらのコードを XCTest でテストする時は Host Application を指定するのを忘れないでください。 f:id:glass-_-onion:20170201151902p:plain Host Application ってなんだそれって方は以下の記事を参考にしてください

参考記事

関連記事