0
0
Ios-swiftHow-ToBeginner ยท 4 min read

How to Use Keychain in Swift for Secure Data Storage

Use the Keychain Services API in Swift to securely store sensitive data like passwords. You create queries with attributes such as kSecClass and kSecAttrAccount to add, update, or retrieve items from the keychain.
๐Ÿ“

Syntax

The Keychain API uses dictionaries with specific keys to define what data to store or retrieve. Common keys include kSecClass (type of item), kSecAttrAccount (identifier), and kSecValueData (the data to save).

Use SecItemAdd to add, SecItemCopyMatching to read, and SecItemUpdate to modify keychain items.

swift
let addQuery: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword,
  kSecAttrAccount as String: "user@example.com",
  kSecValueData as String: "password123".data(using: .utf8)!
]
let status = SecItemAdd(addQuery as CFDictionary, nil)
Output
status == errSecSuccess if item added successfully
๐Ÿ’ป

Example

This example shows how to save a password to the keychain, then read it back securely.

swift
import Foundation

func savePassword(account: String, password: String) -> Bool {
  let data = password.data(using: .utf8)!
  let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecValueData as String: data
  ]
  SecItemDelete(query as CFDictionary) // Remove old item if exists
  let status = SecItemAdd(query as CFDictionary, nil)
  return status == errSecSuccess
}

func getPassword(account: String) -> String? {
  let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: account,
    kSecReturnData as String: true,
    kSecMatchLimit as String: kSecMatchLimitOne
  ]
  var item: CFTypeRef?
  let status = SecItemCopyMatching(query as CFDictionary, &item)
  guard status == errSecSuccess, let data = item as? Data else { return nil }
  return String(data: data, encoding: .utf8)
}

// Usage
let saved = savePassword(account: "user@example.com", password: "password123")
let retrieved = getPassword(account: "user@example.com")
print("Saved: \(saved), Retrieved: \(retrieved ?? "none")")
Output
Saved: true, Retrieved: password123
โš ๏ธ

Common Pitfalls

  • Not deleting existing keychain items before adding new ones causes errSecDuplicateItem errors.
  • Forgetting to convert strings to Data when saving or back to String when reading.
  • Not setting kSecReturnData to true when reading, so no data is returned.
  • Ignoring the status code returned by keychain functions, which indicates success or failure.
swift
/* Wrong: Adding duplicate item without deleting */
let query: [String: Any] = [
  kSecClass as String: kSecClassGenericPassword,
  kSecAttrAccount as String: "user@example.com",
  kSecValueData as String: "pass".data(using: .utf8)!
]
let status1 = SecItemAdd(query as CFDictionary, nil)
let status2 = SecItemAdd(query as CFDictionary, nil) // Fails with errSecDuplicateItem

/* Right: Delete before add */
SecItemDelete(query as CFDictionary)
let status3 = SecItemAdd(query as CFDictionary, nil)
๐Ÿ“Š

Quick Reference

Keychain common keys and their purpose:

KeyDescription
kSecClassType of item (e.g., generic password)
kSecAttrAccountUnique identifier for the item
kSecValueDataData to store (password, token)
kSecReturnDataRequest to return data when reading
kSecMatchLimitLimit number of results (usually one)
โœ…

Key Takeaways

Use Keychain Services API with dictionary queries to securely store and retrieve data.
Always convert strings to Data when saving and back to String when reading.
Delete existing items before adding to avoid duplicate errors.
Check the status code returned by keychain functions to handle errors.
Set kSecReturnData to true when reading to get the stored data.