Swift — Keychain

Tom Tung
7 min readJun 17, 2021

前言:

在使用 app 的過程,會有許多敏感資料是不希望其他人能夠取得的,像是 password, authentication tokens, 或者其他重要的資料。

此時若把這些敏感資料存在 userDefaults 裡,會有暴露的風險。UserDefaults 只是將資料儲存為一個屬性列表檔案,存放於 App 的 Preferences 資料夾裡。

正確的作法:
使用 UserDefaults 來儲存小量資料,像是使用者在 App 裡的偏好設定、或是一些完全不敏感的東西。要儲存 App 的敏感資料,我們應該使用 Apple 提供的 Security 服務(Keychain)。

Keychain 基礎概念:

在開始前需要先了解一些術語。

  • Keychain:keychain(鑰匙串)是一個敏感資料的安全加密儲存位置,簡單來說就是敏感信息的 database(資料庫)
  • Keychain Item:keychain 的註冊處,可以將 機密訊息 裝入 item ,之後存入 keychain
  • Item Class:keychain item 可以有很多種形式的資料,像是 password, certificates … ,此時需要規定要用哪一種 item’s class 來儲存,例如 password 需要加密,但 certificates 不用。 可以視為 儲存訊息的模板(模型)
Photo by Developer Documentation

可以想成 keychain 是一個銀行的金庫,item 是金庫裡的保險櫃,data 則是要存放的金條(password),最後 item class 用來判定該使用哪種類型的保險櫃。

請注意!!
The keychain 會與用於 app 及其綁定 ID 的開發人員資訊相關聯。如果其中一個發生變化,data 便會不可訪問。

常見的 keychain 操作:

前面介紹完基本概念後,可以開始先基本操作:

  • 新增 items 到 keychain
  • 搜尋特定 items
  • 更新 items
  • 刪除 items

新增 items

為了新增 items 到 keychain,可以使用 SecItemAdd function。由於 keychain APIs 非常老舊,所以可能會覺得它的參數內容及 return 值 非常醜 XD。

(line 30)SecItemAdd 需要兩個參數

第一個參數是字典的形式,一般稱為 query。
裡頭會包含

  • 要儲存的資料(ex. 密碼),通常會以 Data 的形式儲存,使用 kSecValueData 作爲 key
  • 可以用來找到資料的參數(ex. 帳號)
  • the item class,特別使用 kSecClass 作為 key

最後需要傳轉型成 CFDictionary

第二個參數是 UnsafeMutablePointer<CFTypeRef?>? 的形式,一般稱 result,可以拿到 return 的值。 要 return 什麼值可以在 query 做設定,上面的範例則是 nil ,因為沒有在 query 設定 return 的值。
這參數主要用在搜尋特定 items。

使用 func SecItemAdd 後會返回 OSStatus ,有點像是 http狀態碼。
當返回 0 時,代表存儲成功,而其他數字則代表有些問題,可以在 https://www.osstatus.com/ 進行查詢。

搜尋 items

若要查詢 items 裡頭的資料,就需要在 query 加入前綴為 kSecReturn key

  • kSecReturnRef:當設為 trueresult 可以是 SecKeychainItem, SecKey, SecCertificate, SecIdentity, 或 CFData ,取決於 query 中的 kSecClass
    注意!!此 key 無法在 iOS 系統中 return 任何值
  • kSecReturnPersistentRef : 當設為 trueresult 將包含 CFData,可以使用它保存在磁盤或傳遞給不同的進程。
  • kSecReturnData :當設為 trueresult 會返回 kSecValueData key 的資料。
  • kSecReturnAttributes : 當設為 trueresult 會返回該 CFDictionary 上 item 的所有屬性。

這 API 對 Swift 的 type safety 並不友好,所以在使用 keychain 時必須做很多轉換。

為了要查詢 keychain 並 檢索 items,我們要使用 SecItemCopyMatching 這個 func 。 用法跟 SecItemAdd 一樣,兩個參數(query 及 result)並返回OSStatus

query 裡頭除了 kSecClasskSecAttrAccount 外,還需要return key 才能取得值。因為要拿到 Data ,所以使用 kSecReturnData

此外還需要儲存屬性(retrivedData)來存放 result
前面提到 resultCFTypeRef 屬性,而 CFTypeRef 可以僑接到 AnyObject ,因此可以用 AnyObject 來取代。

更新 item

為了更新 keychain 裡頭的 items ,需要使用 func SecItemUpdate 。 它一樣會返回 OSStatus 值,但跟前面兩者不同的是它的兩個參數都是 CFDictionaries

  • 第一個參數就是 query (熟悉的那個 query)
  • 第二個參數則是要更新的屬性及其新值

成功修改 kSecAttrAccounttom 的密碼(123321 → newPassword)

注意!如果可以的話,盡可能使查詢位置具體一點,才不會有改錯的問題。

有時會有 server 之類的 key,不同的 server 有相同的 account 。
SecItemUpdate 裡頭的 query 只有 kSecAttrAccount ,可能造成兩個 server 的密碼都會更新

刪除 items

為了刪除 item,需使用 func SecItemDelete 。 它只需一個參數(query),一樣會返回 OSStatus 值。
就像更新 item 時一樣,請確保 query 非常具體,以免誤刪

總結:

雖然 keychain 是個老舊的 API,但在存儲敏感資料上卻是一個可行的工具,而要注意的是return key 的操作可以取得不同類型的數據。

今天介紹的只不過是 keychain 的基本用法,keychain 能做的遠不止這些。然後也有許多 keychain 的框架,使得操作上更為簡單。

--

--