iOS App 程式開發

App Security:實作 App 的安全防護 小心保護使用者資料

App security 是軟體開發中非常重要的一環。App 的使用者都期望自己的資料是保密的,所以 App 裡的敏感資料不應該輕易被人拿走。這篇文章我們將會探討一些開發者在 App 安全性方面的常見錯誤,以及如何處理這些問題。如果你將敏感資料存放在 UserDefaults,就有可能會暴露 App 的資訊。
App Security:實作 App 的安全防護 小心保護使用者資料
App Security:實作 App 的安全防護 小心保護使用者資料
In: iOS App 程式開發
本篇原文(標題: Application Security Musts for every iOS App)刊登於作者 Medium,由 Arlind Aliu 所著,並授權翻譯及轉載。

App 的安全性是軟體開發中非常重要的一環。我們 App 的使用者都期望自己的資料是保密的,所以 App 裡的敏感資料不應該輕易被人拿走。這篇文章我們將會探討一些開發者在 App 安全性方面的常見錯誤,以及如何輕易處理這些問題。

在錯誤的地方儲存敏感資料

我研究過一些在 AppStore 上的 App,很多都犯了同一個錯誤,就是將敏感資料存放在不對的地方。

如果你將敏感資料存放在 UserDefaults 裡,就有可能會暴露 App 的資訊。

UserDefaults 只是將資料儲存為一個屬性列表檔案,存放於 App 的 Preferences 資料夾裡。它們並沒有以任何形式加密儲存在 App 中。

基本上來說,藉由使用 Mac 的第三方程式像是 iMazing,即使手機沒有越獄 (Jailbreak),你也可以輕易看到任何從 AppStore 下載的 App 內 UserDefaults 的資料。

這些 Mac App 雖然只是設計來讓你瀏覽或管理你手機上的第三方程式檔案,但你也可以輕易地瀏覽任何 App 的 UserDefaults

促使我撰寫這篇文章的原因,就是因為我發現大部分從 AppStore 安裝的 App,都將敏感資料存放在 UserDefaults 中,例如是 Access Token(存取權杖)、Active Renewable subscription flags(啟用可續訂訂閱標誌)、可用代幣的數量等資料。

一旦這些資料都能夠被輕易地擷取、更改,就會對 App 造成傷害,例如可以免費使用付費功能,甚至是被駭入網路層等等的。

正確的做法

在 iOS App 上儲存資料時你必須記住一點,UserDefaults 只是設計來儲存小量資料,像是使用者在 App 裡的偏好設定、或是一些完全不敏感的東西。要儲存 App 的敏感資料,我們應該使用 Apple 提供的 Security 服務。

Keychain Services API 可以幫你解決這些問題,老會提供 App 一個方法來存放小量的使用者資料,在一個名為 Keychain 的加密資料庫內。

在 Keychain 裡,你可以自由儲存密碼、以及一些使用者特別在意的機密資料,像是信用卡資訊或是機密的筆記內容。你也可以存放一些用 Certificate, Key, and Trust Services 管理的加密金鑰和憑證等。

app security

Keychain Services API

下文我們將描述如何儲存你使用者的密碼於 Keychain 內。

class KeychainService {
    func save(_ password: String, for account: String) {
        let password = password.data(using: String.Encoding.utf8)!
        let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                    kSecAttrAccount as String: account,
                                    kSecValueData as String: password]
        let status = SecItemAdd(query as CFDictionary, nil)
        guard status == errSecSuccess else { return print("save error")
    }
}

在 Query Dictionary 的部分,kSecClass:kSecClassGenericPassword 表示這個項目是一個密碼,Keychain Services 從中瞭解資料需要加密。

然後,我們利用已創建的 Query 呼叫 SecItemAdd,來把新密碼加到 Keychain。

要恢復資料,步驟亦十分相似:

func retrivePassword(for account: String) -> String? {
    let query: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
                                kSecAttrAccount as String: account,
                                kSecMatchLimit as String: kSecMatchLimitOne,
                                kSecReturnData as String: kCFBooleanTrue]
    
    
    var retrivedData: AnyObject? = nil
    let _ = SecItemCopyMatching(query as CFDictionary, &retrivedData)
    
    
    guard let data = retrivedData as? Data else {return nil}
    return String(data: data, encoding: String.Encoding.utf8)
}

我們可以寫個簡單的測試,來確保資料正確被儲存並恢復。

func testPaswordRetrive() {
    let  password = "123456"
    let account = "User"
    keyChainService.save(password, for: account)
    XCTAssertEqual(keyChainService.retrivePassword(for: account), password)
}

在第一次使用 Keychain API 時可能會有點複雜,如果你必須儲存多於一個密碼的話,我建議你建立一個外觀模式 (Facade) 介面,助你以最佳方法在 App 的使用情境上來幫助你儲存及改動資料。

如果你想要了解更多關於外觀模式、以及如何為複雜的子系統建立簡單的 Wrapper 的話,可以閱讀這篇文章

同時,有很多開源程式庫可以簡化 Keychain API 的使用,例如是 SAMKeychainSwiftKeychainWrapper

儲存密碼和執行權限

在 iOS 開發者的職涯裡,我亦看過很多人不停犯同樣的錯誤。

很多時候開發者不是在 App 裡儲存密碼方便重複使用,就是直接以使用者帳戶及密碼進行網路登入請求。如果你正直接儲存密碼在 UserDefault 裡的話,那麼從本篇文章的開頭你就應該知道有多危險了。

儲存密碼到 Keychain 可以將安全性提高,但儲存密碼和敏感資料時,無論是儲存到 Keychain 或其他地方,我們都應該先將資料加密。假如攻擊者可以駭入 Keychain 的安全防護、或是透過網路攻擊我們,那麼他就可以直接以原始文件形式取得我們的密碼;所以更好的方法是儲存密碼,並將密碼以雜湊 (Hash) 形式加密,以用於登入請求中。

加密敏感資料

自己來實作雜湊加密可以很複雜,而且有點矯枉過正;所以在這篇文章中,我們將會使用 iOS 開源套件 CryptoSwift 來實作,CryptoSwift 是在 Swift 中實作的標準和安全加密演算法一個不斷增長的集合。

現在讓我們來嘗試 CryptoSwift 提供的演算法,來儲存並取回在 Keychain 中的密碼。

func saveEncryptedPassword(_ password: String, for account: String) {
    let salt = Array("salty".utf8)
    let key = try! HKDF(password: Array(password.utf8), salt: salt, variant: .sha256).calculate().toHexString()
    keychainService.save(key, for: account)
}

這個方法會提取一個帳號和密碼,並以加密後的雜湊字串(而不是直接用字串)儲存於 Keychain 裡。

讓我們來拆解一下這個方法。

我們建立了一個 Salt 字串,來減低我們的資料被攻擊的可能性。如果我們只針對密碼進行雜湊加密,那麼駭客可能會用一個常見密碼列表,製作出他們的雜湊值來比對我們所建立的;這樣一來,他們就可能輕易針對一個帳號找出其密碼。

現在我們可以授權予伺服器,去使用我們的帳號和客製化的金鑰,而不是直接使用密碼。

authManager.login(key, user)

當然,App 和伺服器應該共享相同的 Salt 字串,而後端亦需要使用相同的演算法來比較相同的金鑰,以驗證使用者身分。藉著這個方式,我們能夠將安全性提升到另一個層次,讓別人很難攻擊我們 App。

總結

為我們的 App 實作安全防護是個絕不能忽略的步驟。回顧我們的文章,我們一開始討論了儲存敏感資料到 UserDefaults 帶來的危險,以及需要儲存敏感資料到 Keychain 的原因。之後我們亦討論了要先加密後來儲存敏感資料,才能提升安全等級,也提到了共享敏感資料(在我們的範例中就是使用者身分)時,與伺服器溝通的正確方式。

如果你喜歡這篇文章,或是有任何疑問或意見,歡迎留言或是電郵到 [email protected]。你亦可以追蹤我的 Medium,以閱讀更多提升 iOS 開發者技能的文章。

本篇原文(標題: Application Security Musts for every iOS App)刊登於作者 Medium,由 Arlind Aliu 所著,並授權翻譯及轉載。
作者簡介: Arlind,Elasticbrains Munich 的資產 iOS 開發人員。
電郵: [email protected]
Medium: https://medium.com/@arlindaliu.dev
譯者簡介:楊敦凱-目前於科技公司擔任 iOS Developer,工作之餘開發自有 iOS App同時關注網路上有趣的新玩意、話題及科技資訊。平時的興趣則是與自身專業無關的歷史、地理、棒球。來信請寄到:[email protected]
作者
AppCoda 編輯團隊
此文章為客座或轉載文章,由作者授權刊登,AppCoda編輯團隊編輯。有關文章詳情,請參考文首或文末的簡介。
評論
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。