どうも、フリーランスのITエンジニア兼ブロガー兼投資家のKerubitoです。
キーチェーンにデータ(ユーザーID、パスワード、証明書、秘密鍵)を保存したい。
よくあることかと。
ライブラリを使うと簡単にできるんですが、使わなくとも意外と簡単にできました。
・Swiftバージョン:5
・Xcodeバージョン:13.0
・使用デバイス:iPhoneX(15.0)
キーチェーンとは?
キーチェーンはユーザーIDやパスワード、クレジットカード情報といったセキュアなデータを集中管理してくれます。
端末のストレージに保存されるのですが、隔離された領域、しかも暗号化(証明書などは暗号化されない)され、外部から読み取ることが非常に困難。
加えて、iCloudと連携することによって、複数のデバイスでキーチェーンに保存された情報を共有することができます。
今回はSwiftを使ってこの便利なキーチェーンにユーザーIDとパスワードを保存してみようと思います。
世間には便利なライブラリが出回っているのですが、ライブラリは入れたら入れたで管理が大変。
プロジェクトが肥大化してしまいますし、アップデートで不具合が出る可能性もあります。
自作するのが大変なら有効な手段ですが、今回のような「ちょっとしたもの」であればライブラリを使うまでもないケースもあるかと。
キーチェーンにデータを保存する
まずはキーチェーンにデータを保存しましょう。
クエリを作成し、渡します。
このクエリでキーチェーンに保存するデータの種類(kSecClassGenericPasswordは一般的なパスワード)、一意性を担保するkSecAttrService、kSecAttrAccountを指定します。
SecItemCopyMatching()でデータの存在を確認し、あるなしで登録(SecItemAdd)、更新(SecItemUpdate)をそれぞれやっています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | func save(id: String, key: String, data: T) { let encoder = JSONEncoder() do { let encoded = try encoder.encode(data) let query: [String: Any] = [ kSecClass as String: kSecClassGenericPassword, kSecAttrService as String: key, kSecAttrAccount as String: id, kSecValueData as String: encoded, ] let status = SecItemCopyMatching(query as CFDictionary, nil) switch status { case errSecItemNotFound: SecItemAdd(query as CFDictionary, nil) case errSecSuccess: SecItemUpdate(query as CFDictionary, [kSecValueData as String: encoded] as CFDictionary) default: print("該当なし") } } catch { print("エラー") } } |
キーチェンからデータを読み出す
今度はキーチェーンからデータを読み出します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | func load(id: String, key: String, type: T.Type) -> T? { 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 } do { return try JSONDecoder().decode(type, from: value) } catch { print("エラー") } default: print("該当なし") } return nil } |
読み出し側もクエリを作成するところは同じ。
はい、以上です。
簡単にキーチェーンへのアクセスが可能です。
画面と呼び出し
おまけの画面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | struct ContentView: View { @ObservedObject private var model = ContentModel() var body: some View { VStack(spacing: 15) { TextField("", text: $model.userid) .textFieldStyle(RoundedBorderTextFieldStyle()) .frame(width: 300) TextField("", text: $model.password) .textFieldStyle(RoundedBorderTextFieldStyle()) .frame(width: 300) Text("キーチェーンに保存されたパスワード:\(model.passwordKeychain)") Button(action: { model.onSave() }) { Text("キーチェーンに保存").frame(width: 300, height: 45) } Button(action: { model.onLoad() }) { Text("キーチェーンから読み出し").frame(width: 300, height: 45) } } } } |
おまけのモデル
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | internal class ContentModel: ObservableObject { @Published public var userid: String = "" @Published public var password: String = "" @Published public var useridKeychain: String = "" @Published public var passwordKeychain: String = "" internal init() { } internal func onSave() { Keychain().save(id: userid, key: "keychain.sample.com", data: password) } internal func onLoad() { passwordKeychain = Keychain().load(id: userid, key: "keychain.sample.com", type: String.self) ?? "" } } |
実行結果
実行するとこのようになります。
ソースはこちら。
https://github.com/Kerubito0/Keychain.git
以上、楽しい開発ライフを!