Keychain هو مخزن آمن في iOS مصمم لتخزين كلمات المرور والمفاتيح التشفيرية والشهادات والملاحظات السرية بشكل آمن. وفقاً لـ وثائق Apple Security (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 (2024)، أكثر من 90% من تطبيقات iOS في أعلى 100 في App Store تستخدم Keychain لتخزين رموز المصادقة.
من الناحية المعمارية، Keychain هي قاعدة بيانات SQLite مشفرة تقع خارج مساحة التطبيق (sandbox). كل عنصر (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 للحذف. تأخذ كل دالة قاموس استعلام (query) يصف سمات العنصر المطلوب البحث عنه أو حفظه.
السمات الرئيسية للاستعلام: 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 بنص واضح. بدلاً من ذلك، احفظ رمز التحديث (refresh token) المستلم من الخادم بعد مصادقة ناجحة عبر 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. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا