前言:
在使用 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 不用。 可以視為 儲存訊息的模板(模型)
可以想成 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
:當設為true
,result
可以是SecKeychainItem
,SecKey
,SecCertificate
,SecIdentity
, 或CFData
,取決於 query 中的kSecClass
注意!!此 key 無法在 iOS 系統中 return 任何值kSecReturnPersistentRef
: 當設為true
,result
將包含CFData
,可以使用它保存在磁盤或傳遞給不同的進程。kSecReturnData
:當設為true
,result
會返回kSecValueData
key 的資料。kSecReturnAttributes
: 當設為true
,result
會返回該CFDictionary
上 item 的所有屬性。
這 API 對 Swift 的 type safety 並不友好,所以在使用 keychain 時必須做很多轉換。
為了要查詢 keychain 並 檢索 items,我們要使用 SecItemCopyMatching
這個 func 。 用法跟 SecItemAdd
一樣,兩個參數(query 及 result)並返回OSStatus
。
query 裡頭除了 kSecClass
及 kSecAttrAccount
外,還需要return key 才能取得值。因為要拿到 Data ,所以使用 kSecReturnData
。
此外還需要儲存屬性(retrivedData
)來存放 result
。
前面提到 result
是 CFTypeRef
屬性,而 CFTypeRef
可以僑接到 AnyObject
,因此可以用 AnyObject
來取代。
更新 item
為了更新 keychain 裡頭的 items ,需要使用 func SecItemUpdate
。 它一樣會返回 OSStatus
值,但跟前面兩者不同的是它的兩個參數都是 CFDictionaries
- 第一個參數就是 query (熟悉的那個 query)
- 第二個參數則是要更新的屬性及其新值
成功修改 kSecAttrAccount
為 tom
的密碼(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 的框架,使得操作上更為簡單。