Keychain trong iOS: khái niệm, kiến trúc và làm việc với bí mật

Tác giả: IT Sectr Đã đăng: 2026-04-04 Thời gian đọc: 9 phút

Keychain là kho lưu trữ an toàn trong iOS được thiết kế để lưu trữ an toàn mật khẩu, khóa mật mã, chứng chỉ và ghi chú bí mật. Theo Apple Security Documentation (2025), Keychain sử dụng mã hóa phần cứng qua Secure Enclave trên tất cả thiết bị có chip A7 trở lên. Hiểu kiến trúc của iOS Keychain là cần thiết cho mọi nhà phát triển để lưu trữ token và bí mật ứng dụng một cách chính xác.

Những điểm chính

  • iOS Keychain là cơ sở dữ liệu SQLite được mã hóa để lưu trữ bí mật với bảo vệ phần cứng qua Secure Enclave.
  • Protection Class xác định khi nào dữ liệu khả dụng: khi thiết bị được mở khóa, sau lần mở khóa đầu tiên hoặc luôn luôn.
  • Access Control List (ACL) là cơ chế hạn chế truy cập vào các mục Keychain, bao gồm xác thực sinh trắc học.
  • SecItemAdd và SecItemCopyMatching là các API chính của Security framework để ghi và đọc các mục.
  • kSecAttrSynchronizable là flag cho phép đồng bộ hóa Keychain qua iCloud để truy cập trên tất cả thiết bị của người dùng.

Keychain trong iOS là gì?

iOS Keychain là cơ chế an toàn để lưu trữ dữ liệu bí mật, được tích hợp trong hệ điều hành Apple. Không giống như UserDefaults hoặc tệp thông thường, Keychain mã hóa tất cả các mục ở cấp độ phần cứng và cung cấp kiểm soát truy cập chi tiết dựa trên chính sách bảo mật.

Keychain được giới thiệu trong iOS 2.0 và từ đó đã trải qua những thay đổi đáng kể: iOS 7 bổ sung hỗ trợ khóa phần cứng qua Secure Enclave, iOS 9 giới thiệu chia sẻ Keychain giữa các ứng dụng qua Access Groups, iOS 13 bổ sung hỗ trợ liên kết sinh trắc học qua LAContext. Theo Apple WWDC Session (2024), hơn 90% ứng dụng iOS trong top 100 của App Store sử dụng Keychain để lưu trữ token xác thực.

Về mặt kiến trúc, Keychain là cơ sở dữ liệu SQLite được mã hóa nằm bên ngoài sandbox của ứng dụng. Mỗi mục (SecItem) được mã hóa bằng một khóa riêng biệt, khóa này được bảo vệ bởi khóa phần cứng Secure Enclave. Dịch vụ hệ thống Securityd quản lý quyền truy cập vào Keychain dựa trên quyền hạn của ứng dụng và lớp bảo vệ được yêu cầu.

Một lợi thế quan trọng của Keychain so với các phương pháp lưu trữ khác: dữ liệu được mã hóa và giải mã tự động bởi hệ điều hành. Nhà phát triển không cần triển khai mật mã thủ công - chỉ cần gọi SecItemAdd với các tham số chính xác. iOS đảm bảo rằng dữ liệu từ Keychain không thể bị đọc bởi các ứng dụng khác (khi Access Groups được cấu hình đúng).

Kiến trúc Keychain

Kiến trúc Keychain bao gồm nhiều cấp độ: vật lý (Secure Enclave), hệ thống (Security.framework), ứng dụng (API SecItem*) và logic (Access Groups, Protection Classes). Hiểu từng cấp độ giúp thiết kế kho lưu trữ bí mật một cách chính xác.

SecItemAdd và SecItemCopyMatching

API chính để làm việc với Keychain là các hàm Security framework: SecItemAdd để thêm, SecItemCopyMatching để đọc, SecItemUpdate để cập nhật và SecItemDelete để xóa. Mỗi hàm nhận một từ điển truy vấn (query) mô tả các thuộc tính của mục cần tìm hoặc lưu.

Các thuộc tính truy vấn chính: kSecClass - loại mục (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount - định danh duy nhất trong lớp, kSecValueData - dữ liệu được lưu (Data), kSecAttrAccessible - lớp bảo vệ. SecItemCopyMatching với flag kSecReturnData trả về dữ liệu mục, với kSecMatchLimit - số lượng kết quả.

Quan trọng: tất cả các hàm đều trả về OSStatus. Thao tác thành công trả về errSecSuccess (0). Lỗi: errSecItemNotFound (-25300) - không tìm thấy mục, errSecDuplicateItem (-25299) - mục đã tồn tại, errSecAuthFailed (-25293) - xác thực sinh trắc học thất bại. Nhà phát triển phải xử lý từng trạng thái một cách chính xác.

Lớp bảo vệ (Protection Class)

Protection Class là thuộc tính kSecAttrAccessible xác định khi nào dữ liệu trong Keychain khả dụng để đọc. iOS hỗ trợ sáu lớp bảo vệ với các mức độ khả dụng và bảo mật khác nhau.

Lớp được khuyến nghị cho hầu hết các tình huống là kSecAttrAccessibleWhenUnlockedThisDeviceOnly: dữ liệu chỉ khả dụng khi thiết bị được mở khóa và không được sao chép vào iCloud Backup. Đối với dữ liệu cần khả dụng sau khi khởi động lại (nhưng chỉ sau lần mở khóa đầu tiên), sử dụng kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly. Đối với dữ liệu quan trọng yêu cầu xác thực sinh trắc học mỗi lần truy cập, kết hợp kSecAttrAccessibleWhenUnlockedThisDeviceOnly với ACL yêu cầu sinh trắc học.

Các lớp không có hậu tố ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) cho phép sao chép vào iCloud Backup. Điều này thuận tiện cho người dùng nhưng giảm bảo mật - dữ liệu có thể được khôi phục từ bản sao lưu. Đối với token xác thực, luôn sử dụng ThisDeviceOnly.

Danh sách kiểm soát truy cập (ACL)

Access Control List (ACL) là cơ chế hạn chế thao tác trên mục Keychain dựa trên xác thực người dùng. ACL được thiết lập qua SecAccessControlCreateWithFlags và được truyền vào thuộc tính kSecAttrAccessControl khi lưu mục.

Các flag được hỗ trợ: kSecAccessControlUserPresence - bất kỳ xác thực nào (Face ID, Touch ID hoặc mật mã), kSecAccessControlBiometryCurrentSet - chỉ sinh trắc học (dấu vân tay hoặc khuôn mặt hiện đang đăng ký), kSecAccessControlDevicePasscode - chỉ mật mã. ACL áp dụng cho mọi thao tác: đọc, cập nhật và xóa mục cũng yêu cầu xác thực.

Trên iOS 15+, flag kSecAccessControlWatch xuất hiện - dành cho Apple Watch, cho phép xác thực qua đồng hồ đã ghép cặp. ACL có thể kết hợp: ví dụ, kSecAccessControlUserPresence hoặc kSecAccessControlBiometryAny với mật mã tùy chọn (hướng .or).

Các loại dữ liệu trong Keychain

iOS Keychain hỗ trợ bốn lớp mục chính (kSecClass), mỗi lớp được thiết kế cho loại dữ liệu riêng. Chọn đúng lớp giúp đơn giản hóa tổ chức và tìm kiếm mục.

kSecClassGenericPassword - mật khẩu chung: lớp được sử dụng phổ biến nhất. Lưu trữ dữ liệu nhị phân tùy ý (Data) với khóa duy nhất (kSecAttrAccount). Phù hợp cho token, khóa API, mã PIN. Không yêu cầu quyền bổ sung để sử dụng.

kSecClassInternetPassword - mật khẩu internet: lưu trữ dữ liệu liên quan đến tài nguyên mạng. Thuộc tính bổ sung: kSecAttrServer (tên miền máy chủ), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType. iOS có thể tự động điền các mật khẩu này qua AutoFill.

kSecClassKey - khóa mật mã: để lưu trữ khóa mã hóa (AES, RSA, EC). Khóa được lưu dưới dạng SecKeyRef, không phải Data. kSecClassCertificate - chứng chỉ X.509 để lưu trữ và xác minh chứng chỉ số. Cả hai lớp đều yêu cầu hiểu biết về các thao tác mật mã và cấu hình thuộc tính chính xác.

Trong thực tế, 95% trường hợp sử dụng Keychain trong ứng dụng di động được bao phủ bởi kSecClassGenericPassword để lưu token xác thực và kSecClassKey để lưu khóa mã hóa riêng tư. kSecClassCertificate hiếm khi được sử dụng - thường trong ứng dụng doanh nghiệp với PKI riêng.

Ví dụ mã: làm việc với Keychain trong Swift

Hãy xem các ví dụ thực tế về làm việc với Keychain trong Swift sử dụng Security framework. Mỗi ví dụ bao gồm xử lý lỗi và cấu hình Protection Class chính xác.

Lưu và đọc token

Một ví dụ cơ bản lưu token xác thực vào Keychain với bảo vệ WhenUnlockedThisDeviceOnly. Khóa (kSecAttrAccount) là định danh dịch vụ, dữ liệu (kSecValueData) là token ở định dạng Data.

swift
import Security

enum KeychainError: Error {
    case unexpectedStatus(OSStatus)
}

func saveToken(token: String, service: String) throws {
    let data = Data(token.utf8)
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: "auth_token",
        kSecValueData as String: data,
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemDelete(query as CFDictionary)
    let status = SecItemAdd(query as CFDictionary, nil)
    guard status == errSecSuccess else {
        throw KeychainError.unexpectedStatus(status)
    }
}

func readToken(service: String) throws -> String {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrService as String: service,
        kSecAttrAccount as String: "auth_token",
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne
    ]
    var result: AnyObject?
    let status = SecItemCopyMatching(
        query as CFDictionary, &result
    )
    guard status == errSecSuccess,
        let data = result as? Data else {
        throw KeychainError.unexpectedStatus(status)
    }
    return String(decoding: data, as: UTF8.self)
}

Lưu với liên kết sinh trắc học

Ví dụ minh họa sử dụng SecAccessControlCreateWithFlags để liên kết khóa với sinh trắc học. Mỗi lần truy cập mục sẽ yêu cầu Face ID hoặc Touch ID.

swift
import LocalAuthentication

func saveWithBiometry(data: Data, key: String) throws {
    let accessControl = SecAccessControlCreateWithFlags(
        nil,
        kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
        .biometryCurrentSet,
        nil
    )

    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: key,
        kSecValueData as String: data,
        kSecAttrAccessControl as String: accessControl as Any
    ]

    SecItemDelete(query as CFDictionary)
    let status = SecItemAdd(query as CFDictionary, nil)
    guard status == errSecSuccess else {
        throw KeychainError.unexpectedStatus(status)
    }
}

Chia sẻ Keychain giữa các ứng dụng

Ví dụ cho thấy cách cấu hình Access Group để chia sẻ Keychain giữa các ứng dụng của cùng một nhà phát triển. Yêu cầu quyền keychain-access-groups.

swift
// Capabilities: Keychain Sharing đã bật
// App IDs: group.com.example.shared

func saveSharedToken(token: Data) {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: "shared_token",
        kSecValueData as String: token,
        kSecAttrAccessGroup as String:
            "group.com.example.shared",
        kSecAttrAccessible as String:
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly
    ]
    SecItemAdd(query as CFDictionary, nil)
}

Thực hành tốt nhất khi làm việc với Keychain

Sử dụng đúng cách iOS Keychain yêu cầu tuân theo một số quy tắc chính giúp ngăn ngừa các lỗ hổng phổ biến và mất dữ liệu.

Sử dụng ThisDeviceOnly cho tất cả bí mật xác thực: kSecAttrAccessibleWhenUnlockedThisDeviceOnly đảm bảo token không nằm trong iCloud Backup. Nếu kẻ tấn công truy cập được vào bản sao lưu, dữ liệu Keychain với flag này sẽ không khả dụng. Ngoại lệ là dữ liệu cần khả dụng trên tất cả thiết bị của người dùng (ví dụ: khóa mã hóa cho dịch vụ độc quyền), sử dụng kSecAttrAccessibleWhenUnlocked với kSecAttrSynchronizable.

Không lưu trữ mật khẩu thô - lưu trữ hàm băm hoặc token phiên. Apple Security Guide (2025) khuyến nghị không bao giờ lưu mật khẩu người dùng trong Keychain dưới dạng văn bản thuần túy. Thay vào đó, hãy lưu token làm mới nhận được từ máy chủ sau khi xác thực thành công qua OAuth 2.0. Mật khẩu chỉ được sử dụng để lấy token và bị xóa ngay lập tức khỏi bộ nhớ.

Xử lý lỗi Keychain chính xác: mỗi thao tác Keychain trả về OSStatus cần được kiểm tra. Đặc biệt chú ý đến errSecItemNotFound (token hết hạn hoặc bị xóa) và errSecAuthFailed (sinh trắc học thất bại). Trong trường hợp đầu tiên, ứng dụng nên yêu cầu xác thực mới; trong trường hợp thứ hai, hiển thị cho người dùng phương pháp thay thế (mật mã). Không bao giờ bỏ qua trạng thái errSecItemNotFound - nó sẽ gây ra sự cố khi cố đọc nil.

Kiểm tra Keychain trên thiết bị thật: trình mô phỏng không có Secure Enclave và không hỗ trợ ACL sinh trắc học. Luôn kiểm tra các kịch bản: khởi chạy lần đầu, khôi phục từ bản sao lưu, thay đổi mật khẩu thiết bị, xóa và cài đặt lại ứng dụng. Trên thiết bị thật, Keychain vẫn tồn tại khi ứng dụng bị xóa, nhưng chỉ khi flag kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly không được sử dụng, flag này bị xóa khi mật mã bị loại bỏ.

Giảm thiểu số lượng thao tác Keychain: mỗi thao tác đọc hoặc ghi là một cuộc gọi đến dịch vụ hệ thống Securityd, có thể chặn luồng. Lưu cache các token đã đọc trong bộ nhớ trong suốt phiên làm việc và chỉ truy cập lại Keychain khi khởi động lại ứng dụng hoặc lỗi xác thực (401 từ máy chủ). iOS tự động khóa Keychain khi thiết bị bị khóa, vì vậy hãy lên kế hoạch đọc qua LAContext với yêu cầu sinh trắc học.

Câu hỏi thường gặp

Tôi có thể lưu dữ liệu vào Keychain và đọc nó sau khi khởi động lại không?

Có, sử dụng lớp bảo vệ kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly hoặc kSecAttrAccessibleAfterFirstUnlock. Dữ liệu sẽ khả dụng sau lần mở khóa thiết bị đầu tiên sau khi khởi động lại. Để truy cập tự động khi khởi chạy ứng dụng (không cần chờ mở khóa), sử dụng kSecAttrAccessibleAlways, nhưng điều này làm giảm bảo mật.

Làm thế nào để xóa Keychain khi người dùng đăng xuất?

Gọi SecItemDelete với truy vấn chứa kSecClass cho mỗi loại dữ liệu. Để xóa hoàn toàn tất cả các mục ứng dụng, thực thi: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary). Lặp lại cho kSecClassKey, kSecClassCertificate và kSecClassInternetPassword.

Sự khác biệt giữa kSecAttrAccessible và kSecAttrAccessControl là gì?

kSecAttrAccessible xác định khi nào dữ liệu khả dụng (khi mở khóa, sau lần mở khóa đầu tiên, v.v.). kSecAttrAccessControl xác định ai có thể truy cập (sinh trắc học, mật mã, bất kỳ xác thực nào). Chúng kết hợp: đầu tiên là Protection Class, sau đó là ACL. Ví dụ, dữ liệu chỉ khả dụng khi mở khóa VÀ chỉ sau Face ID.

Tại sao SecItemCopyMatching trả về errSecItemNotFound?

Nguyên nhân: mục chưa bao giờ được lưu, mục đã bị xóa khi mật mã bị loại bỏ (nếu kSecAttrAccessibleWhenPasscodeSet được sử dụng), ứng dụng đã được cài đặt lại (Keychain vẫn tồn tại nhưng không được khôi phục từ bản sao lưu trên thiết bị mới), Access Group hoặc định danh nhóm nhà phát triển đã thay đổi. Kiểm tra kSecAttrService và kSecAttrAccount.

Làm thế nào để kiểm tra xem thiết bị có hỗ trợ sinh trắc học trong Keychain không?

Sử dụng LAContext từ LocalAuthentication: gọi context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil). Nếu trả về true - thiết bị hỗ trợ Touch ID hoặc Face ID. Đối với ACL Keychain, sử dụng flag biometryCurrentSet (chỉ dữ liệu sinh trắc học hiện tại) hoặc biometryAny (bất kỳ dữ liệu nào đã đăng ký trước đó).

Tổng kết

  • iOS Keychain là kho lưu trữ được bảo vệ phần cứng cho bí mật với mã hóa qua Secure Enclave và kiểm soát truy cập qua ACL.
  • Security framework cung cấp các hàm SecItemAdd, SecItemCopyMatching, SecItemUpdate và SecItemDelete để làm việc với các mục.
  • Protection Class (kSecAttrAccessible) được chọn dựa trên tình huống: WhenUnlockedThisDeviceOnly là tiêu chuẩn cho token.
  • ACL với sinh trắc học (kSecAttrAccessControl) thêm yêu cầu Face ID hoặc Touch ID cho mỗi thao tác đọc.
  • kSecClassGenericPassword bao phủ 95% trường hợp - lưu trữ token, khóa API, mã PIN và ghi chú.
  • ThisDeviceOnly ngăn sao chép bí mật vào iCloud Backup - bắt buộc cho token xác thực.
  • Xử lý OSStatus đúng cách và kiểm tra trên thiết bị thật là thực hành cần thiết để sử dụng Keychain đáng tin cậy.

Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay

IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.

Thảo luận dự án

Đọc thêm