Swift ライブラリ未使用でKeyChainでデータを保存/読み込む


KeyChainとUserDefaultsの比較

こちらの記事が大変わかりやすいです。

https://qiita.com/sachiko-kame/items/261d42c57207e4b7002a

KeyChainを保存する

セキュアに保持したいデータを取り扱うと想定。
取り扱うデータはString型のみと想定。

    func saveKeyChain(value: String) -> Bool {
        let id = "id"
        let key = "serviceName"
        guard let data = value.data(using: .utf8) else {
            return false
        }
        
        let query: [String: Any] = [
            kSecClass              as String: kSecClassGenericPassword,
            kSecAttrService        as String: key,
            kSecAttrAccount        as String: id,
            kSecValueData          as String: data,
        ]
        let status = SecItemCopyMatching(query as CFDictionary, nil)
        
        var itemUpdateStatus: OSStatus?
        
        print(status)

        switch status {
        case errSecItemNotFound:
            itemUpdateStatus = SecItemAdd(query as CFDictionary, nil)

        case errSecSuccess:
            itemUpdateStatus = SecItemUpdate(query as CFDictionary, [kSecValueData as String: data] as CFDictionary)

        default:
            print("該当なし")
        }
        
        if itemUpdateStatus == errSecSuccess {
            print("正常終了")
        } else {
            return false
        }
        return true
    }

KeyChainを読み込む

   func loadKeyChain() -> String? {
        let id = "id"
        let key = "serviceName"
        
        let query: [String: Any] = [
            kSecClass              as String: kSecClassGenericPassword,
            kSecAttrService        as String: key,
            kSecAttrAccount        as String: id,
            kSecMatchLimit         as String: kSecMatchLimitOne,
            kSecReturnAttributes   as String: true,
            kSecReturnData         as String: true,
        ]
            
        var item: CFTypeRef?
        let status = SecItemCopyMatching(query as CFDictionary, &item)
        switch status {
        case errSecItemNotFound:
            return nil
        case errSecSuccess:
            guard let item = item,
                  let value = item[kSecValueData as String] as? Data else {
                      print("データなし")
                      return nil
                  }
            guard let loadString = String(data: value, encoding: .utf8) else {
                return nil
            }
            return loadString
        default:
            print("該当なし")
        }
        return nil
    }

参考

参考記事はJSONEncoderを利用してどのような型でも保存できるようにしています。

https://kerubito.net/technology/11681/

https://qiita.com/ayutaso/items/960e1b1f553e53ca60f4

追記

読み込み時でクラッシュするプロジェクトがあり、その中では以下のように書き換えました。

                  let value = item[kSecValueData as String] as? Data else {

                         let value = item[kSecValueData as CFString] as? Data else {

少し説明すると、kSecValueDataはCFStringにもかかわらずStringでキャストしようとしているとエラー出ました。
itemのvalueを取り出す為のkeyとしてkSecValueDataをString型として利用したいので、CFString型にキャストするように変更しています。