0Pricing
Swift Academy · درس

تخزين الأسرار في Keychain

احفظ بيانات الاعتماد واسترجعها بأمان.

تخزين الأسرار في Keychain درس مجاني في Swift Academy على CoddyKit. هذا هو الدرس 1 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Swift Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Swift Academy 4 دروس في المجموع.

لماذا نستخدم Keychain

يجب ألا تُخزّن كلمات المرور والرموز والمفاتيح في UserDefaults أو ملفات عادية، إذ يسهل قراءتها. إن Keychain قاعدة بيانات مشفّرة يديرها نظام التشغيل للأسرار الصغيرة، وتحميها العتاد ورمز دخول المستخدم. وهو المكان الصحيح الوحيد لتخزين بيانات الاعتماد.

import Security
// Keychain stores secrets encrypted at rest,
// survives app updates, and gates access by policy.

العناصر عبارة عن قواميس

واجهة Keychain Services مبنية على C؛ إذ تصفون العنصر باستخدام قاموس استعلام من النوع [String: Any]، مع استخدام مفاتيح ثوابت kSec.... ويُعاد استخدام بنية القاموس نفسها للإضافة والبحث والتحديث والحذف.

import Security
let query: [String: Any] = [
    kSecClass as String: kSecClassGenericPassword,
    kSecAttrAccount as String: "user@example.com",
    kSecAttrService as String: "com.example.app"
]
_ = query

فئات العناصر

يحدد المفتاح kSecClass نوع العنصر. ويغطي kSecClassGenericPassword رموز التطبيق وأسراره، بينما يخزّن kSecClassInternetPassword بيانات اعتماد الخادم مع سمات المضيف والبروتوكول. وتستخدم معظم أسرار التطبيقات كلمة المرور العامة.

import Security
// kSecClassGenericPassword   -> tokens, API keys
// kSecClassInternetPassword  -> server logins
// kSecClassKey / Certificate -> crypto material
let cls = kSecClassGenericPassword
_ = cls

إضافة عنصر

يُدرج SecItemAdd عنصرًا جديدًا. أدرجوا البيانات ضمن kSecValueData من النوع Data، بالإضافة إلى السمات التي تحدده. ويعيد الدالة قيمة من النوع OSStatus؛ وتعني errSecSuccess نجاح العملية.

import Security
func save(_ token: String, account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecValueData as String: Data(token.utf8)
    ]
    return SecItemAdd(query as CFDictionary, nil)
        == errSecSuccess
}

التعامل مع العناصر المكررة

تؤدي إضافة عنصر ذي سمات تعريفية موجودة مسبقًا إلى إعادة errSecDuplicateItem. وتحاول عملية حفظ متينة تنفيذ SecItemAdd، ثم تلجأ إلى SecItemUpdate عند وجود تكرار، وفق نمط upsert.

import Security
func upsert(_ data: Data, account: String) -> Bool {
    let base: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    var add = base
    add[kSecValueData as String] = data
    let status = SecItemAdd(add as CFDictionary, nil)
    if status == errSecDuplicateItem {
        return SecItemUpdate(base as CFDictionary,
            [kSecValueData as String: data] as CFDictionary)
            == errSecSuccess
    }
    return status == errSecSuccess
}

قراءة عنصر

يبحث SecItemCopyMatching عن العناصر. وللحصول على بايتات السر، يجب ضبط kSecReturnData على true وضبط kSecMatchLimit على kSecMatchLimitOne. وتعود النتيجة عبر معلمة إخراج من النوع CFTypeRef.

import Security
func load(account: String) -> Data? {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account,
        kSecReturnData as String: true,
        kSecMatchLimit as String: kSecMatchLimitOne]
    var result: CFTypeRef?
    let status = SecItemCopyMatching(
        query as CFDictionary, &result)
    guard status == errSecSuccess else { return nil }
    return result as? Data
}

تحديث عنصر

تأخذ SecItemUpdate قاموستين: استعلامًا يعثر على العنصر، وقاموسًا بالسمات المطلوب تحديثها. لا تتغير إلا السمات التي تدرجها؛ أما كل ما عدا ذلك فيبقى محفوظًا.

import Security
func update(_ newData: Data, account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    let attrs: [String: Any] = [
        kSecValueData as String: newData]
    return SecItemUpdate(query as CFDictionary,
        attrs as CFDictionary) == errSecSuccess
}

حذف عنصر

تزيل SecItemDelete العناصر المطابقة. ويؤدي حذف عنصر غير موجود إلى إرجاع errSecItemNotFound، ويمكنك التعامل معه باعتباره نجاحًا عند مسح بيانات الاعتماد أثناء تسجيل الخروج.

import Security
func delete(account: String) -> Bool {
    let query: [String: Any] = [
        kSecClass as String: kSecClassGenericPassword,
        kSecAttrAccount as String: account]
    let status = SecItemDelete(query as CFDictionary)
    return status == errSecSuccess
        || status == errSecItemNotFound
}

فك ترميز OSStatus

الأخطاء هي رموز OSStatus صحيحة. وتحول SecCopyErrorMessageString أحدها إلى وصف مفهوم للمستخدم، وهو أمر بالغ الأهمية عند تصحيح سبب فشل الحفظ أو القراءة.

import Security
func describe(_ status: OSStatus) -> String {
    return SecCopyErrorMessageString(status, nil)
        as String? ?? "OSStatus \(status)"
}

تحديد العناصر بشكل فريد

تُطابَق العناصر وفق مجموعة سماتها، وعادةً ما تكون kSecAttrService مع kSecAttrAccount لكلمات المرور العامة. اختر سلسلة خدمة ثابتة خاصة بتطبيقك حتى لا تتصادم الأسرار المختلفة.

import Security
// Uniqueness for generic passwords usually comes from:
//   service (your bundle id) + account (the username)
let service = "com.example.app.auth"
let account = "current-user"
_ = (service, account)

غلاف برمجي صغير

لأن واجهة API الخام مطوّلة، تغلفها الفرق في نوع صغير يوفّر save وread وdelete. وبذلك تُحصر إجراءات kSec في مكان واحد وتبقى مواضع الاستدعاء نظيفة.

import Security
struct TokenStore {
    let service = "com.example.app.auth"
    func read(_ account: String) -> Data? {
        let q: [String: Any] = [
            kSecClass as String: kSecClassGenericPassword,
            kSecAttrService as String: service,
            kSecAttrAccount as String: account,
            kSecReturnData as String: true,
            kSecMatchLimit as String: kSecMatchLimitOne]
        var out: CFTypeRef?
        return SecItemCopyMatching(q as CFDictionary, &out)
            == errSecSuccess ? out as? Data : nil
    }
}

اختبار سريع

تذكّر الطريقة الصحيحة لاسترداد بايتات سر مخزّن.

مراجعة

لقد تعلمت عمليات CRUD في Keychain:

  • خزّن بيانات الاعتماد في Keychain المشفّر، وليس في UserDefaults أو الملفات.
  • صِف العناصر باستخدام قواميس استعلام kSec، واختر فئة مثل kSecClassGenericPassword.
  • تغطي SecItemAdd / SecItemCopyMatching / SecItemUpdate / SecItemDelete دورة حياة العنصر؛ وعالج errSecDuplicateItem باستخدام upsert.
  • حدّد العناصر باستخدام الخدمة والحساب، وفك ترميز OSStatus لأغراض تصحيح الأخطاء.

الأسئلة الشائعة

هل درس «تخزين الأسرار في Keychain» مجاني؟

نعم — نص درس «تخزين الأسرار في Keychain» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Swift Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Swift Academy 4 دروس في المجموع.

ماذا ستتعلم في «تخزين الأسرار في Keychain»؟

احفظ بيانات الاعتماد واسترجعها بأمان. تتمرن على Swift Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.

هل أحتاج إلى خبرة سابقة لأبدأ Swift Academy؟

لا تُشترط خبرة سابقة. Swift Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 1 من أصل 4.

كم من الوقت يستغرق درس «تخزين الأسرار في Keychain»؟

معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.

هل يمكنني كتابة وتشغيل أكواد في درس Swift Academy هذا؟

نعم. كل درس في Swift Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تخزين الأسرار في Keychain
  2. التحكم في الوصول إلى Keychain
  3. المصادقة البيومترية
  4. حماية البيانات والتشفير
← العودة إلى Swift Academy