🔑

iOS | KeyChain への保存、読込、削除処理を実装する

2022/06/25に公開

はじめに

iOS でデータをローカルに保存する方法はいくつかあります。

  • ディスク上のファイル
  • UserDefaults
  • Database
  • KeyChain

この中で、パスワードやアクセストークンなどの機密情報は KeyChain に保存する必要があります。
KeyChain を扱う場合、便利なライブラリもいくつか公開されているようですが、個人的にそこまで複雑なことをやる予定はないので、いくつか記事を参考にしながら保存、読込、削除ができるクラスを作ってみました。

KeyChainManager クラス

保存、読込、削除の処理はおおよそ同じような流れになっています。

保存する場合はそのデータそのものや、データを特定するために使われる値を指定して query を作ります。読込、削除の場合は、データ特定用の値を指定して query を作ります。そして、その query を保存、読込、削除メソッドに渡すことで処理が行われます。

import Foundation

class KeyChainHelper {
    
    static let shared = KeyChainHelper()
    private init() {}
    
    func save(_ data: Data, service: String, account: String) -> Bool {
        let query = [
            kSecValueData: data,
            kSecClass: kSecClassGenericPassword,
            kSecAttrService: service,
            kSecAttrAccount: account,
        ] as CFDictionary
        
        let matchingStatus = SecItemCopyMatching(query, nil)
        switch matchingStatus {
        case errSecItemNotFound:
            // データが存在しない場合は保存
            let status = SecItemAdd(query, nil)
            return status == noErr
        case errSecSuccess:
            // データが存在する場合は更新
            SecItemUpdate(query, [kSecValueData as String: data] as CFDictionary)
            return true
        default:
            print("Failed to save data to keychain")
            return false
        }
    }
    
    func read(service: String, account: String) -> String? {
        let query = [
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecClass: kSecClassGenericPassword,
            kSecReturnData: true
        ] as CFDictionary
        
        var result: AnyObject?
        SecItemCopyMatching(query, &result)
        
        if let data = (result as? Data) {
            return String(data: data, encoding: .utf8)
        } else {
            return nil
        }
    }
    
    func delete(service: String, account: String) -> Bool {
        let query = [
            kSecAttrService: service,
            kSecAttrAccount: account,
            kSecClass: kSecClassGenericPassword,
        ] as CFDictionary
        
        let status = SecItemDelete(query)
        return status == noErr
    }
}

kSecClass で kSecClassGenericPassword を指定した場合、値を特定するために kSecAttrService と kSecAttrAccount を指定します。この2つの値については自由な値を指定できるようですが、実際はどんな値を入れるのが望ましいのでしょうか?

こちらの記事には以下のような記述がありました。

There is no hard defined rule what value to use for both kSecAttrService and kSecAttrAccount. However, it is recommended to use strings that are meaningful. For example, if we are saving the Facebook access token, we can set kSecAttrService as “access-token” and kSecAttrAccount as “facebook“.

kSecAttrService に access-token、kSecAttrAccount に facebook など、意味のある文字列を指定するのが望ましいみたいですね。

参考

https://swiftsenpai.com/development/persist-data-using-keychain/
https://aska.hatenablog.com/entry/2014/09/23/042116
https://qiita.com/mario7/items/11f06502a022c01cfbb5
https://qiita.com/ayutaso/items/960e1b1f553e53ca60f4

Discussion