Keychain là kho lưu trữ thông tin xác thực an toàn trong iOS, watchOS, macOS và tvOS quản lý mật khẩu, khóa và chứng chỉ ở cấp độ phần cứng. Không giống như UserDefaults, dữ liệu trong Keychain được mã hóa và cách ly theo sandbox của ứng dụng. Theo Apple Developer Documentation, Keychain sử dụng cơ chế Secure Enclave và mã hóa phần cứng AES-256 để bảo vệ thông tin nhạy cảm.
Điểm chính
Keychain là cơ sở dữ liệu an toàn của hệ điều hành Apple được thiết kế để lưu trữ an toàn mật khẩu, khóa mật mã, chứng chỉ và các dữ liệu nhạy cảm khác. Keychain lần đầu tiên xuất hiện trong Mac OS 8.6 vào năm 1999 và trên iOS — từ phiên bản SDK đầu tiên.
Keychain giải quyết vấn đề cơ bản của phát triển di động: lưu trữ bí mật (token, mật khẩu, khóa mã hóa) trong hệ thống tệp không an toàn. Không giống như UserDefaults hay SQLite, dữ liệu trong Keychain được lưu trữ dưới dạng mã hóa và tự động mã hóa khi ghi vào đĩa.
Mỗi ứng dụng iOS hoạt động trong một sandbox biệt lập và chỉ có quyền truy cập vào keychain của riêng mình. Tuy nhiên, nhà phát triển có thể cấu hình quyền truy cập Keychain chia sẻ giữa các ứng dụng của cùng một nhà phát triển thông qua keychain access groups. iCloud Keychain của người dùng đồng bộ hóa dữ liệu giữa các thiết bị qua mã hóa đầu cuối.
Theo Apple Security White Paper (2025), Keychain sử dụng mã hóa phần cứng AES-256 với khóa liên kết với định danh thiết bị duy nhất (UID), khiến dữ liệu không thể truy cập khi trích xuất khỏi thiết bị.
Keychain hoạt động như một dịch vụ phân lớp: ứng dụng truy cập Security framework, framework này giao tiếp với daemon securityd quản lý cơ sở dữ liệu SQLite mã hóa của Keychain. Mỗi bản ghi được mã hóa bằng một khóa riêng biệt và các khóa tự được bảo vệ bởi mô-đun phần cứng Secure Enclave trên các thiết bị có chip A7 trở lên.
Security framework cung cấp giao diện C để làm việc với Keychain Services. Các hàm chính: SecItemAdd (thêm), SecItemCopyMatching (tìm kiếm), SecItemUpdate (cập nhật) và SecItemDelete (xóa). Mỗi thao tác chấp nhận một từ điển thuộc tính CFDictionaryRef mô tả loại yêu cầu.
Dữ liệu được phân loại theo lớp: kSecClassGenericPassword (mật khẩu tùy ý), kSecClassInternetPassword (mật khẩu dịch vụ Internet), kSecClassCertificate (chứng chỉ), kSecClassKey (khóa mật mã) và kSecClassIdentity (cặp khóa và chứng chỉ).
Khi lưu mật khẩu, ứng dụng tạo một từ điển với các thuộc tính: dịch vụ, tài khoản, mức truy cập, nhãn. Security framework truyền dữ liệu cho daemon securityd, daemon này mã hóa bản ghi bằng khóa liên kết với thiết bị và lưu vào SQLite của Keychain. Việc truy xuất diễn ra tương tự: truy vấn theo thuộc tính, tìm kiếm, giải mã và trả về dữ liệu.
Một đặc điểm quan trọng: khi xóa ứng dụng, iOS tự động xóa các bản ghi Keychain do ứng dụng đó tạo ra (từ iOS 10.3+). Khi cài đặt lại ứng dụng, dữ liệu Keychain từ lần cài đặt trước không khả dụng trừ khi đã sử dụng iCloud Keychain.
import Security
func savePassword(service: String, account: String, password: Data) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
kSecValueData as String: password,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked
]
SecItemAdd(query as CFDictionary, nil)
}
Keychain hỗ trợ năm lớp dữ liệu chính, mỗi lớp có bộ thuộc tính và phạm vi ứng dụng riêng. Việc chọn lớp xác định khóa tìm kiếm nào có sẵn và dữ liệu được mã hóa như thế nào.
Các lớp kSecClassGenericPassword và kSecClassInternetPassword được thiết kế để lưu trữ mật khẩu dạng chuỗi. Phiên bản Internet lưu thêm dữ liệu máy chủ: tên miền, giao thức, cổng và đường dẫn xác thực. GenericPassword được sử dụng để lưu trữ bất kỳ mật khẩu nào không gắn với máy chủ.
Khi lưu mật khẩu Internet, bạn có thể chỉ định kSecAttrServer (tên miền), kSecAttrProtocol (HTTPS, FTP, v.v.), kSecAttrPort và kSecAttrPath. Điều này cho phép xác định chính xác bản ghi khi tự động điền qua AutoFill.
Lớp kSecClassKey lưu trữ khóa đối xứng (AES) và bất đối xứng (RSA, EC). Lớp kSecClassCertificate lưu trữ chứng chỉ X.509. Lớp kSecClassIdentity kết hợp khóa riêng tư và chứng chỉ tương ứng thành một bản ghi.
Đối với khóa bất đối xứng, Keychain hỗ trợ các thuộc tính: kSecAttrKeyType (RSA, EC), kSecAttrKeySizeInBits (2048, 256), kSecAttrIsPermanent (có lưu vào Keychain không). Đối với chứng chỉ: kSecAttrLabel (tên dễ đọc), kSecAttrSubject (DN chủ sở hữu), kSecAttrIssuer (DN nhà phát hành).
| Lớp Keychain | Mục đích | Loại dữ liệu |
|---|---|---|
| kSecClassGenericPassword | Mật khẩu ứng dụng | Data (NSString) |
| kSecClassInternetPassword | Mật khẩu máy chủ | Data + thuộc tính máy chủ |
| kSecClassCertificate | Chứng chỉ X.509 | SecCertificate |
| kSecClassKey | Khóa mật mã | SecKey |
| kSecClassIdentity | Khóa + chứng chỉ | SecIdentity |
Giao diện chính để tương tác với Keychain trong mã iOS gốc là các hàm của Security framework. Đối với Swift và Objective-C, một giao diện C duy nhất có sẵn, được bọc trong các hàm tiện lợi.
Hàm SecItemAdd thêm một mục mới vào Keychain. Nó nhận đầu vào là một từ điển với lớp, dữ liệu và thuộc tính. Trả về trạng thái errSecSuccess (0) hoặc mã lỗi. Hàm SecItemCopyMatching thực hiện tìm kiếm theo thuộc tính và trả về dữ liệu tìm thấy.
Để tìm kiếm, sử dụng cờ kSecReturnData — nếu true, hàm trả về nội dung của bản ghi. Cờ kSecMatchLimit xác định số lượng kết quả: kSecMatchLimitOne (một) hoặc kSecMatchLimitAll (tất cả).
func readPassword(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 item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else { return nil }
return item as? Data
}
SecItemUpdate cho phép thay đổi thuộc tính hoặc dữ liệu của bản ghi hiện có. SecItemDelete xóa tất cả bản ghi khớp với từ điển được truyền vào. Khi xóa, điều quan trọng là chỉ định đủ thuộc tính để không vô tình xóa bản ghi của ứng dụng khác trong keychain chia sẻ.
func deletePassword(service: String, account: String) {
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account
]
SecItemDelete(query as CFDictionary)
}
Mỗi bản ghi Keychain có thuộc tính kSecAttrAccessible, xác định thời điểm dữ liệu có sẵn để đọc. Điều này cực kỳ quan trọng cho bảo mật: mức truy cập sai có thể khiến dữ liệu dễ bị tổn thương hoặc ngược lại, không khả dụng khi cần.
kSecAttrAccessibleWhenUnlocked — dữ liệu chỉ khả dụng khi thiết bị được mở khóa. kSecAttrAccessibleAfterFirstUnlock — khả dụng sau lần mở khóa đầu tiên sau khi khởi động lại. kSecAttrAccessibleAlways — luôn khả dụng (không khuyến nghị). Hậu tố ThisDeviceOnly ngăn chặn việc truyền dữ liệu qua sao lưu.
Trên các thiết bị có Secure Enclave, khóa mã hóa của Keychain có thể được bảo vệ thêm bằng sinh trắc học: kSecAccessControlBiometryCurrentSet hoặc kSecAccessControlUserPresence. Điều này thêm yêu cầu Face ID hoặc Touch ID cho mỗi thao tác đọc.
iCloud Keychain đồng bộ hóa bản ghi giữa các thiết bị Apple qua mã hóa đầu cuối. Apple không có quyền truy cập vào dữ liệu đã giải mã. Để bật đồng bộ hóa, hãy thêm thuộc tính kSecAttrSynchronizable với giá trị kCFBooleanTrue.
Quan trọng: các bản ghi được đồng bộ hóa không thể chứa thuộc tính ThisDeviceOnly vì chúng phải khả dụng trên các thiết bị khác. Nếu mất tất cả thiết bị, iCloud Keychain có thể được khôi phục qua mã khôi phục hoặc khóa khôi phục iCloud.
Đối với token xác thực, hãy sử dụng 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 bản sao lưu. Đối với chứng chỉ cần khả dụng cho các tác vụ nền, hãy sử dụng kSecAttrAccessibleAfterFirstUnlock. Không bao giờ sử dụng kSecAttrAccessibleAlways cho dữ liệu nhạy cảm.
Khuyến nghị luôn thêm hậu tố ThisDeviceOnly cho dữ liệu không yêu cầu đồng bộ hóa. Điều này ngăn chặn việc sao chép vô tình bí mật vào iCloud hoặc iTunes backup, nơi chúng có thể bị trích xuất bằng các công cụ phân tích. Đối với dữ liệu quan trọng, hãy kết hợp khả dụng với kSecAccessControlUserPresence — điều này thêm yêu cầu sinh trắc học hoặc mã để giải mã bản ghi.
Khi sử dụng iCloud Keychain, hãy lưu ý rằng các bản ghi được đồng bộ hóa khả dụng trên tất cả thiết bị của người dùng: nếu kẻ tấn công truy cập được một thiết bị, dữ liệu có thể bị xâm phạm trên tất cả. Đối với bí mật cô lập, hãy sử dụng ThisDeviceOnly.
Câu hỏi thường gặp
UserDefaults lưu trữ dữ liệu dưới dạng văn bản thuần túy trong tệp .plist, có thể truy cập khi phân tích IPA. Keychain mã hóa từng bản ghi riêng biệt, sử dụng mã hóa phần cứng và hỗ trợ mức truy cập, sinh trắc học và đồng bộ hóa qua iCloud.
Mỗi lần khởi chạy trình mô phỏng iOS, Keychain bị xóa. Trên thiết bị thật, hãy xóa ứng dụng — iOS sẽ xóa tất cả bản ghi Keychain của nhà phát triển đó. Để xóa có chọn lọc, hãy gọi SecItemDelete với các thuộc tính thích hợp.
Có, thông qua keychain access groups. Các ứng dụng phải có cùng team ID trong hồ sơ cấp phép và entitlements với cùng keychain-access-groups. Nhà phát triển cấu hình quyền truy cập chia sẻ trong Xcode Capabilities.
Apple không ghi lại giới hạn nghiêm ngặt, nhưng trong thực tế khuyến nghị không vượt quá 1–4 KB cho mỗi bản ghi. Đối với khối lượng lớn hơn, hãy sử dụng mã hóa tệp và lưu khóa trong Keychain, còn dữ liệu trong Documents.
Trên thiết bị jailbreak, bảo vệ Keychain giảm vì kẻ tấn công có quyền truy cập root và có thể đọc trực tiếp SQLite của Keychain. Đối với dữ liệu quan trọng, hãy sử dụng mã hóa bổ sung với khóa lấy từ máy chủ.
Tổng kết
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.
Đọc thêm