Securely Managing User Details in the Keychain on iOS: Part II

Share this post on:
A digital security-themed poster featuring a smartphone with a prominent lock and shield icon on its screen, symbolizing secure data management. The background includes interconnected icons representing locks, keys, and security elements, all set against a warm, gradient background. This visual emphasizes the importance of protecting user data in mobile applications

Security is crucial when managing user data in mobile applications. For iOS apps, Keychain offers a secure method for storing sensitive information like user credentials, API keys, and other confidential data. In this blog, we’ll delve into using Keychain in Swift to store, retrieve, update, and delete user details.

In our previous blog, we defined the key and some functions of Keychain Services. In this blog, we’ll review an example of how you can store user-sensitive data securely using Keychain in Swift.

We’ll cover the steps to store, retrieve, update, and delete user details, ensuring your application handles confidential information safely and effectively.

Read our guide on defining quick actions in your iOS app to enhance user experience and streamline

Setting Up Keychain in Your Swift Project

To interact with Keychain, we’ll use Apple’s Security framework. Begin by importing the Security module in your Swift file:

import Security

Keychain Helper Class

To make Keychain interactions easier, we can create a helper class that will encapsulate all the necessary functions for storing, retrieving, updating, and deleting data.

class KeychainHelper {

    static let shared = KeychainHelper()

    private init() {}

    // MARK: - Save Data

    func save(_ data: Data, service: String, account: String) {

        let query: [String: Any] = [

            kSecClass as String: kSecClassGenericPassword,

            kSecAttrService as String: service,

            kSecAttrAccount as String: account,

            kSecValueData as String: data

        ]

        // Delete any existing items

        SecItemDelete(query as CFDictionary)

        // Add the new item

        let status = SecItemAdd(query as CFDictionary, nil)

        guard status == errSecSuccess else { print("Error: \(status)"); return }

    }

    // MARK: - Retrieve Data

    func retrieve(service: String, account: String) -> Data? {

        let query: [String: Any] = [

            kSecClass as String: kSecClassGenericPassword,

            kSecAttrService as String: service,

            kSecAttrAccount as String: account,

            kSecReturnData as String: true,

            kSecMatchLimit as String: kSecMatchLimitOne

        ]

        var dataTypeRef: AnyObject? = nil

        let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)

        guard status == errSecSuccess else { print("Error: \(status)"); return nil }

        return dataTypeRef as? Data

    }

    // MARK: - Update Data

    func update(_ data: Data, service: String, account: String) {

        let query: [String: Any] = [

            kSecClass as String: kSecClassGenericPassword,

            kSecAttrService as String: service,

            kSecAttrAccount as String: account

        ]

        let attributes: [String: Any] = [

            kSecValueData as String: data

        ]

        let status = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)

        guard status == errSecSuccess else { print("Error: \(status)"); return }

    }

    // MARK: - Delete Data

    func delete(service: String, account: String) {

        let query: [String: Any] = [

            kSecClass as String: kSecClassGenericPassword,

            kSecAttrService as String: service,

            kSecAttrAccount as String: account

        ]

        let status = SecItemDelete(query as CFDictionary)

        guard status == errSecSuccess else { print("Error: \(status)"); return }

    }

}

Storing User Details

To store user details such as username and password, convert them to Data and use the save method:

let username = "user123"

let password = "password123"

if let usernameData = username.data(using: .utf8),

   let passwordData = password.data(using: .utf8) {

    KeychainHelper.shared.save(usernameData, service: "com.example.myapp", account: "username")

    KeychainHelper.shared.save(passwordData, service: "com.example.myapp", account: "password")

}

Retrieving User Details

To retrieve the stored details, use the retrieve method and convert the data back to a String:

if let usernameData = KeychainHelper.shared.retrieve(service: "com.example.myapp", account: "username"),

   let passwordData = KeychainHelper.shared.retrieve(service: "com.example.myapp", account: "password"),

   let username = String(data: usernameData, encoding: .utf8),

   let password = String(data: passwordData, encoding: .utf8) {

    print("Username: \(username)")

    print("Password: \(password)")

}

 

Updating User Details

To update existing details, use the update method with the new data:

let newUsername = "newUser123"

if let newUsernameData = newUsername.data(using: .utf8) {

    KeychainHelper.shared.update(newUsernameData, service: "com.example.myapp", account: "username")

}

Deleting User Details

To delete stored details, use the delete method:

KeychainHelper.shared.delete(service: "com.example.myapp", account: "username")

KeychainHelper.shared.delete(service: "com.example.myapp", account: "password")

Key Points to Consider During Keychain Implementation

When implementing Keychain for secure storage in iOS applications, focus on the following high-priority points:

Security and Encryption

  • Keychain data is encrypted using the device’s secure hardware.
  • Choose appropriate security attributes to control access (e.g., kSecAttrAccessibleWhenUnlocked).

    Access Control
  • Use Access Control Lists (ACLs) to specify who or what can access Keychain items.
  • Grant minimal necessary permissions to reduce security risks.

    Data Persistence
  • Keychain items persist across app launches, device reboots, and even app reinstallation if backed up.
  • iCloud Keychain can synchronize Keychain items across the user’s devices if enabled.

    Data Isolation
  • Keychain items are isolated to the app that created them by default.
  • Use App Groups to share Keychain items between multiple apps in the same group.

    Accessibility Levels
  • Set appropriate accessibility levels based on security requirements (e.g., kSecAttrAccessibleWhenUnlocked).
  • Understand how accessibility settings impact Keychain item availability.

    Error Handling and Testing
  • Always check status codes returned by Keychain functions and handle errors gracefully.
  • Test Keychain functionality across different scenarios (app reboots, device reboots, various accessibility settings).

    Secure Storage Practices
  • Use Keychain for small pieces of sensitive information (passwords, tokens, keys).
  • Minimize storage of Personally Identifiable Information (PII) directly in Keychain.

By focusing on these high-priority points, you can implement Keychain effectively and securely in your iOS applications.

You can find similar examples on GitHub that illustrate how to store user details and cryptographic keys in Keychain. These examples provide comprehensive details on securely managing sensitive data within your iOS applications.

Conclusion

Using Keychain in Swift provides a secure and reliable way to store sensitive user information. By encapsulating Keychain operations in a helper class, we can streamline the process and make our code more maintainable. With the steps outlined in this blog, you can easily implement secure data storage in your iOS applications, ensuring your users’ information is protected.

Happy coding!