Keychain คือพื้นที่จัดเก็บข้อมูลที่ปลอดภัยใน iOS ที่ออกแบบมาเพื่อจัดเก็บรหัสผ่าน คีย์การเข้ารหัส ใบรับรอง และบันทึกที่เป็นความลับอย่างปลอดภัย ตาม Apple Security Documentation (2025) Keychain ใช้การเข้ารหัสผ่านฮาร์ดแวร์ผ่าน Secure Enclave บนอุปกรณ์ทั้งหมดที่มีชิป A7 ขึ้นไป การทำความเข้าใจสถาปัตยกรรมของ iOS Keychain เป็นสิ่งจำเป็นสำหรับนักพัฒนาทุกคนในการจัดเก็บโทเค็นและความลับของแอปพลิเคชันอย่างถูกต้อง
ประเด็นสำคัญ
iOS Keychain เป็นกลไกที่ปลอดภัยสำหรับจัดเก็บข้อมูลที่เป็นความลับ ซึ่งสร้างไว้ในระบบปฏิบัติการของ Apple แตกต่างจาก UserDefaults หรือไฟล์ทั่วไป Keychain เข้ารหัสรายการทั้งหมดในระดับฮาร์ดแวร์และให้การควบคุมการเข้าถึงอย่างละเอียดตามนโยบายความปลอดภัย
Keychain ถูกนำมาใช้ใน iOS 2.0 และตั้งแต่นั้นมาก็ผ่านการเปลี่ยนแปลงครั้งสำคัญ: iOS 7 เพิ่มการสนับสนุนคีย์ฮาร์ดแวร์ผ่าน Secure Enclave, iOS 9 เปิดตัวการแชร์ Keychain ระหว่างแอปผ่าน Access Groups, iOS 13 เพิ่มการสนับสนุนการผูกชีวมาตรผ่าน LAContext ตาม Apple WWDC Session (2024) แอป iOS มากกว่า 90% ใน 100 อันดับแรกของ App Store ใช้ Keychain สำหรับจัดเก็บโทเค็นการยืนยันตัวตน
ในเชิงสถาปัตยกรรม Keychain คือฐานข้อมูล SQLite ที่เข้ารหัสซึ่งอยู่ภายนอกแซนด์บ็อกซ์ของแอปพลิเคชัน แต่ละรายการ (SecItem) จะถูกเข้ารหัสด้วยคีย์แยกต่างหาก ซึ่งจะถูกป้องกันโดยคีย์ฮาร์ดแวร์ Secure Enclave บริการระบบ Securityd จัดการการเข้าถึง Keychain ตามสิทธิ์ของแอปพลิเคชันและคลาสการป้องกันที่ร้องขอ
ข้อได้เปรียบที่สำคัญของ Keychain เหนือวิธีการจัดเก็บอื่นๆ: ข้อมูลถูกเข้ารหัสและถอดรหัสโดยอัตโนมัติโดยระบบปฏิบัติการ นักพัฒนาไม่จำเป็นต้องใช้งานการเข้ารหัสด้วยตนเอง - เพียงเรียก SecItemAdd ด้วยพารามิเตอร์ที่ถูกต้อง iOS รับประกันว่าแอปพลิเคชันอื่นไม่สามารถอ่านข้อมูลจาก Keychain ได้ (เมื่อกำหนดค่า Access Groups อย่างถูกต้อง)
สถาปัตยกรรม Keychain ประกอบด้วยหลายระดับ: ทางกายภาพ (Secure Enclave), ระบบ (Security.framework), แอปพลิเคชัน (API SecItem*) และเชิงตรรกะ (Access Groups, Protection Classes) การทำความเข้าใจแต่ละระดับช่วยออกแบบพื้นที่จัดเก็บความลับได้อย่างถูกต้อง
API หลักสำหรับทำงานกับ Keychain คือฟังก์ชันของ Security framework: SecItemAdd สำหรับเพิ่ม, SecItemCopyMatching สำหรับอ่าน, SecItemUpdate สำหรับอัปเดต และ SecItemDelete สำหรับลบ แต่ละฟังก์ชันรับพจนานุกรมคำค้นหาที่อธิบายคุณลักษณะของรายการที่ต้องการค้นหาหรือจัดเก็บ
คุณลักษณะหลักของคำค้นหา: kSecClass - ประเภทรายการ (kSecClassGenericPassword, kSecClassKey, kSecClassCertificate), kSecAttrAccount - ตัวระบุเฉพาะภายในคลาส, kSecValueData - ข้อมูลที่จัดเก็บ (Data), kSecAttrAccessible - คลาสการป้องกัน SecItemCopyMatching ด้วยแฟล็ก kSecReturnData ส่งคืนข้อมูลรายการ, ด้วย kSecMatchLimit - จำนวนผลลัพธ์
สำคัญ: ฟังก์ชันทั้งหมดส่งคืน OSStatus การดำเนินการที่สำเร็จส่งคืน errSecSuccess (0) ข้อผิดพลาด: errSecItemNotFound (-25300) - ไม่พบรายการ, errSecDuplicateItem (-25299) - รายการมีอยู่แล้ว, errSecAuthFailed (-25293) - การยืนยันตัวตนด้วยชีวมาตรล้มเหลว นักพัฒนาต้องจัดการแต่ละสถานะอย่างถูกต้อง
Protection Class คือคุณลักษณะ kSecAttrAccessible ที่กำหนดว่าข้อมูลใน Keychain พร้อมใช้งานสำหรับการอ่านเมื่อใด iOS รองรับคลาสการป้องกันหกคลาสที่มีระดับความพร้อมใช้งานและความปลอดภัยต่างกัน
คลาสที่แนะนำสำหรับสถานการณ์ส่วนใหญ่คือ kSecAttrAccessibleWhenUnlockedThisDeviceOnly: ข้อมูลจะพร้อมใช้งานเมื่ออุปกรณ์ปลดล็อกเท่านั้นและไม่ถูกคัดลอกไปยัง iCloud Backup สำหรับข้อมูลที่ควรพร้อมใช้งานหลังจากรีบูต (แต่หลังจากการปลดล็อกครั้งแรกเท่านั้น) ให้ใช้ kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly สำหรับข้อมูลที่สำคัญซึ่งต้องการการยืนยันตัวตนด้วยชีวมาตรในทุกการเข้าถึง ให้รวม kSecAttrAccessibleWhenUnlockedThisDeviceOnly เข้ากับ ACL ที่ต้องการชีวมาตร
คลาสที่ไม่มีคำต่อท้าย ThisDeviceOnly (kSecAttrAccessibleWhenUnlocked, kSecAttrAccessibleAfterFirstUnlock) อนุญาตให้คัดลอกไปยัง iCloud Backup สะดวกสำหรับผู้ใช้แต่ลดความปลอดภัย - ข้อมูลสามารถกู้คืนจากการสำรองข้อมูลได้ สำหรับโทเค็นการยืนยันตัวตน ให้ใช้ ThisDeviceOnly เสมอ
Access Control List (ACL) เป็นกลไกที่จำกัดการดำเนินการกับรายการ Keychain ตามการยืนยันตัวตนของผู้ใช้ ACL ถูกกำหนดผ่าน SecAccessControlCreateWithFlags และส่งต่อไปยังคุณลักษณะ kSecAttrAccessControl เมื่อบันทึกรายการ
แฟล็กที่รองรับ: kSecAccessControlUserPresence - การยืนยันตัวตนใดๆ (Face ID, Touch ID หรือรหัสผ่าน), kSecAccessControlBiometryCurrentSet - เฉพาะชีวมาตร (ลายนิ้วมือหรือใบหน้าที่ลงทะเบียนอยู่ในปัจจุบัน), kSecAccessControlDevicePasscode - เฉพาะรหัสผ่าน ACL ใช้กับการดำเนินการทุกครั้ง: การอ่าน อัปเดต และลบรายการก็ต้องมีการยืนยันตัวตนเช่นกัน
บน iOS 15+ แฟล็ก kSecAccessControlWatch ปรากฏขึ้น - สำหรับ Apple Watch ซึ่งอนุญาตให้ยืนยันตัวตนผ่านนาฬิกาที่เชื่อมต่ออยู่ ACL สามารถรวมกันได้: ตัวอย่างเช่น kSecAccessControlUserPresence หรือ kSecAccessControlBiometryAny พร้อมรหัสผ่านเสริม (การวางแนว .or)
iOS Keychain รองรับคลาสรายการหลักสี่คลาส (kSecClass) แต่ละคลาสออกแบบมาสำหรับประเภทข้อมูลของตัวเอง การเลือกคลาสที่ถูกต้องช่วยให้การจัดระเบียบและการค้นหารายการง่ายขึ้น
kSecClassGenericPassword - รหัสผ่านทั่วไป: คลาสที่ใช้บ่อยที่สุด จัดเก็บข้อมูลไบนารีตามอำเภอใจ (Data) ด้วยคีย์เฉพาะ (kSecAttrAccount) เหมาะสำหรับโทเค็น คีย์ API รหัส PIN ไม่ต้องการสิทธิ์เพิ่มเติมในการใช้งาน
kSecClassInternetPassword - รหัสผ่านอินเทอร์เน็ต: จัดเก็บข้อมูลที่เกี่ยวข้องกับทรัพยากรเครือข่าย คุณลักษณะเพิ่มเติม: kSecAttrServer (โดเมนเซิร์ฟเวอร์), kSecAttrProtocol (https, ftp), kSecAttrPort, kSecAttrAuthenticationType iOS สามารถกรอกรหัสผ่านเหล่านี้โดยอัตโนมัติผ่าน AutoFill
kSecClassKey - คีย์การเข้ารหัส: สำหรับจัดเก็บคีย์การเข้ารหัส (AES, RSA, EC) คีย์ถูกจัดเก็บเป็น SecKeyRef ไม่ใช่ Data kSecClassCertificate - ใบรับรอง X.509 สำหรับจัดเก็บและตรวจสอบใบรับรองดิจิทัล ทั้งสองคลาสต้องการความเข้าใจเกี่ยวกับการดำเนินการเข้ารหัสและการกำหนดค่าคุณลักษณะที่ถูกต้อง
ในทางปฏิบัติ 95% ของการใช้ Keychain ในแอปมือถือครอบคลุมโดย kSecClassGenericPassword สำหรับจัดเก็บโทเค็นการยืนยันตัวตนและ kSecClassKey สำหรับจัดเก็บคีย์การเข้ารหัสส่วนตัว kSecClassCertificate ถูกใช้น้อยมาก - โดยทั่วไปในแอประดับองค์กรที่มี PKI ของตัวเอง
มาดูตัวอย่างเชิงปฏิบัติของการทำงานกับ Keychain ใน Swift โดยใช้ Security framework แต่ละตัวอย่างรวมถึงการจัดการข้อผิดพลาดและการกำหนดค่า Protection Class ที่ถูกต้อง
ตัวอย่างพื้นฐานบันทึกโทเค็นการยืนยันตัวตนใน Keychain ด้วยการป้องกัน WhenUnlockedThisDeviceOnly คีย์ (kSecAttrAccount) คือตัวระบุบริการ ข้อมูล (kSecValueData) คือโทเค็นในรูปแบบ Data
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)
}
ตัวอย่างสาธิตการใช้ SecAccessControlCreateWithFlags เพื่อผูกคีย์กับชีวมาตร การเข้าถึงรายการแต่ละครั้งจะต้องใช้ Face ID หรือ Touch ID
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)
}
}
ตัวอย่างแสดงการกำหนดค่า Access Group สำหรับการเข้าถึง Keychain ร่วมกันระหว่างแอปของนักพัฒนาคนเดียวกัน ต้องใช้สิทธิ์ keychain-access-groups
// Capabilities: Keychain Sharing เปิดใช้งานแล้ว
// 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)
}
การใช้ iOS Keychain อย่างถูกต้องต้องปฏิบัติตามกฎสำคัญหลายข้อที่ป้องกันช่องโหว่ทั่วไปและการสูญเสียข้อมูล
ใช้ ThisDeviceOnly สำหรับความลับการยืนยันตัวตนทั้งหมด: kSecAttrAccessibleWhenUnlockedThisDeviceOnly รับประกันว่าโทเค็นจะไม่ไปอยู่ใน iCloud Backup หากผู้โจมตีเข้าถึงการสำรองข้อมูล ข้อมูล Keychain ที่มีแฟล็กนี้จะไม่พร้อมใช้งาน ข้อยกเว้นคือข้อมูลที่ควรพร้อมใช้งานบนอุปกรณ์ทั้งหมดของผู้ใช้ (เช่น คีย์การเข้ารหัสสำหรับบริการที่เป็นกรรมสิทธิ์) ซึ่งใช้ kSecAttrAccessibleWhenUnlocked ร่วมกับ kSecAttrSynchronizable
อย่าจัดเก็บรหัสผ่านดิบ - จัดเก็บแฮชหรือโทเค็นเซสชัน Apple Security Guide (2025) แนะนำไม่ให้บันทึกรหัสผ่านผู้ใช้ใน Keychain ในรูปแบบข้อความธรรมดา ให้บันทึกโทเค็นรีเฟรชที่ได้รับจากเซิร์ฟเวอร์หลังจากการยืนยันตัวตนที่สำเร็จผ่าน OAuth 2.0 แทน รหัสผ่านใช้สำหรับรับโทเค็นเท่านั้นและถูกลบออกจากหน่วยความจำทันที
จัดการข้อผิดพลาด Keychain อย่างถูกต้อง: การดำเนินการ Keychain แต่ละครั้งส่งคืน OSStatus ที่ต้องตรวจสอบ ให้ความสนใจเป็นพิเศษกับ errSecItemNotFound (โทเค็นหมดอายุหรือถูกลบ) และ errSecAuthFailed (ชีวมาตรล้มเหลว) ในกรณีแรก แอปควรขอการยืนยันตัวตนใหม่ ในกรณีที่สอง แสดงวิธีอื่นให้ผู้ใช้ (รหัสผ่าน) อย่าละเลยสถานะ errSecItemNotFound - จะทำให้แอปขัดข้องเมื่อพยายามอ่าน nil
ทดสอบ Keychain บนอุปกรณ์จริง: ตัวจำลองไม่มี Secure Enclave และไม่รองรับ ACL ทางชีวมาตร ตรวจสอบสถานการณ์เสมอ: การเปิดใช้ครั้งแรก การกู้คืนจากการสำรองข้อมูล การเปลี่ยนรหัสผ่านอุปกรณ์ การลบและติดตั้งแอปใหม่ บนอุปกรณ์จริง Keychain จะคงอยู่เมื่อแอปถูกลบ แต่เฉพาะในกรณีที่ไม่ได้ใช้แฟล็ก kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly ซึ่งจะถูกล้างเมื่อรหัสผ่านถูกลบ
ลดจำนวนการดำเนินการ Keychain: การอ่านหรือเขียนแต่ละครั้งเป็นการเรียกไปยังบริการระบบ Securityd ซึ่งสามารถบล็อกเธรดได้ แคชโทเค็นที่อ่านแล้วในหน่วยความจำระหว่างเซสชันและเข้าถึง Keychain อีกครั้งเมื่อรีสตาร์ทแอปหรือข้อผิดพลาดการยืนยันตัวตน (401 จากเซิร์ฟเวอร์) เท่านั้น iOS จะล็อก Keychain โดยอัตโนมัติเมื่ออุปกรณ์ถูกล็อก ดังนั้นให้วางแผนการอ่านผ่าน LAContext ด้วยคำขอชีวมาตร
คำถามที่พบบ่อย
ได้ ใช้คลาสการป้องกัน kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly หรือ kSecAttrAccessibleAfterFirstUnlock ข้อมูลจะพร้อมใช้งานหลังจากปลดล็อกอุปกรณ์ครั้งแรกหลังรีบูต สำหรับการเข้าถึงอัตโนมัติเมื่อเปิดแอป (โดยไม่รอการปลดล็อก) ให้ใช้ kSecAttrAccessibleAlways แต่จะลดความปลอดภัย
เรียก SecItemDelete ด้วยคำค้นหาที่มี kSecClass สำหรับแต่ละประเภทข้อมูล สำหรับการล้างรายการแอปทั้งหมดอย่างสมบูรณ์ ให้ดำเนินการ: SecItemDelete([kSecClass as String: kSecClassGenericPassword] as CFDictionary) ทำซ้ำสำหรับ kSecClassKey, kSecClassCertificate และ kSecClassInternetPassword
kSecAttrAccessible กำหนดว่าข้อมูลพร้อมใช้งานเมื่อใด (เมื่อปลดล็อก หลังจากปลดล็อกครั้งแรก ฯลฯ) kSecAttrAccessControl กำหนดว่าใครสามารถเข้าถึงได้ (ชีวมาตร รหัสผ่าน การยืนยันตัวตนใดๆ) รวมกัน: อันดับแรก Protection Class จากนั้น ACL ตัวอย่างเช่น ข้อมูลพร้อมใช้งานเมื่อปลดล็อกเท่านั้น และหลังจาก Face ID เท่านั้น
สาเหตุ: ไม่เคยบันทึกรายการ, รายการถูกลบเมื่อลบรหัสผ่าน (หากใช้ kSecAttrAccessibleWhenPasscodeSet), แอปถูกติดตั้งใหม่ (Keychain คงอยู่แต่ไม่ถูกกู้คืนจากการสำรองข้อมูลบนอุปกรณ์ใหม่), Access Group หรือตัวระบุทีมนักพัฒนาเปลี่ยนแปลงไป ตรวจสอบ kSecAttrService และ kSecAttrAccount
ใช้ LAContext จาก LocalAuthentication: เรียก context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil) หากส่งคืน true - อุปกรณ์รองรับ Touch ID หรือ Face ID สำหรับ ACL Keychain ให้ใช้แฟล็ก biometryCurrentSet (เฉพาะข้อมูลชีวมาตรปัจจุบัน) หรือ biometryAny (ข้อมูลที่ลงทะเบียนก่อนหน้านี้)
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม