أفضل الممارسات في DPAPI لإبعاد الأسرار عن إعدادات النصّ الصريح في تطبيقات Windows
في المقال السابق «قائمة تحقّق للحدّ الأدنى من الأمان في تطوير تطبيقات Windows»، كتبتُ خطّ الأساس الأدنى: لا تترك الأسرار في الشيفرة المصدريّة أو في الإعدادات النصّيّة الصريحة، وفي Win32 / .NET، فإنّ DPAPI و ProtectedData غالباً ما يكونان أوّل ما يجب التفكير فيه.
هذه المرّة أودّ التوقّف عند تلك النقطة قليلاً، والتركيز على سؤال أضيق:
إن كان لا بدّ لتطبيق Windows من تخزين شيء محلّيّاً، فماذا يعني فعلاً أن يكون ذلك أفضل من النصّ الصريح؟
الهدف هنا برمجيّات مثل:
- تطبيقات سطح المكتب من نوع WPF أو WinForms أو WinUI
- تطبيقات Windows العميلة المبنيّة بـ C# / .NET
- التطبيقات التي ينتهي بها المطاف إلى الرغبة في الاحتفاظ بمعلومات اعتماد الاتّصال أو API tokens في ملفّ إعدادات محلّيّ
ليست هذه قصّة خياليّة عن دفاع كامل ضدّ كلّ مهاجم.
بل هي نقاش عمليّ حول كيفيّة التوقّف عن معاملة appsettings.json على أنّه مكان يمكن أن تجلس فيه الأسرار بنصّ صريح بلا عواقب.
1. النسخة المختصرة
في الممارسة العمليّة، الترتيب الأنظف للتفكير في هذا هو:
- لا تعطِ العميل سرّاً طويل العمر إن أمكنك تجنّب ذلك
- فضِّل Windows authentication، أو integrated authentication، أو تسجيل الدخول التفاعليّ، أو إدارة الأسرار في جانب الخادم
- إن كان التخزين المحلّيّ لا مفرّ منه، فلا تحتفظ به بنصّ صريح
- في Windows، يكون DPAPI /
ProtectedDataعادةً أوّل المرشّحين
- في Windows، يكون DPAPI /
- بالنسبة لتطبيقات سطح المكتب الاعتياديّة، يجب أن يكون
DataProtectionScope.CurrentUserنقطة البداية الافتراضيّة- أمّا
LocalMachineفإنّه أضيق بكثير في المواضع التي يلائمها فعلاً
- أمّا
- لا يحمي DPAPI من نقطة طرفيّة مخترقة بالكامل
- الشيفرة التي تعمل بسياق المستخدم نفسه تستطيع عادةً فكّ تشفير ما يستطيع ذلك المستخدم فكّ تشفيره
السؤال الجوهريّ خلف كلّ هذا سؤال أسمعه كثيراً:
«إن كان لا بدّ من وجود المفتاح في مكان ما على أيّ حال، أليس النصّ الصريح و DPAPI الشيء نفسه أساساً؟»
ذلك يبدو نصف صحيح، لكنّ النتيجة خاطئة.
- إذا بنيتَ طبقة AES خاصّة بك واحتفظتَ بالمفتاح في التطبيق نفسه أو في مجموعة الإعدادات نفسها، فإنّك في الكثير من الأحيان لا تبتعد كثيراً عن النصّ الصريح من الناحية العمليّة
- يحرّك DPAPI إدارة المفاتيح إلى نظام التشغيل ويربط فكّ التشفير بمستخدم Windows أو حاسوب محدّد
- ذلك يغيّر النتيجة في حوادث اعتياديّة جدّاً مثل نسخ ملفّ إعدادات، أو إرفاقه بتذكرة دعم، أو تركه في مجموعة نُسخ احتياطيّة، أو نقله إلى حاسوب آخر
بعبارة أخرى، النظر إلى المقولة المجرّدة «المفتاح موجود في مكان ما» وحدها يخفي الجزء المهمّ:
من يستطيع استخدامه، وفي أيّ سياق، وبأيّ سهولة.
أن نقول «إنّ الاثنين متماثلان» يشبه قليلاً قول إنّ المفتاح المخفيّ تحت ممسحة الباب والمفتاح الذي يُسلَّم بعد التحقّق من الهويّة متكافئان لأنّ كليهما لا يزال مفتاحاً.
2. لماذا تفشل الإعدادات النصّيّة الصريحة بسهولة هكذا
الإعدادات النصّيّة الصريحة خطيرة لأسباب اعتياديّة أكثر بكثير من نظريّة التشفير.
في العمل الفعليّ، تتسرّب الأسرار عبر مسارات كهذه:
- يُلحَق ملفّ الإعدادات بـ Git
- يحوي ZIP استكشاف الأخطاء ملفّ الإعدادات بأكمله
- يطلب الدعم من المستخدم إرفاق ملفّ الإعدادات
- تكشف النُّسخ الاحتياطيّة أو مشاركات الملفّات البيانات لشخص آخر
- تطبع السجلّات سلسلة الاتّصال أو الـ token كما هي
- يستطيع موظّف سابق أو مستخدم آخر على الجهاز نفسه قراءة الملفّ
أكبر نقطة ضعف للنصّ الصريح بسيطة:
لحظة أن يصبح قابلاً للقراءة، يكفّ عن كونه سرّاً.
- إن استطاع أحد فتح الملفّ، فقد ضاع السرّ
- إن استطاع أحد نسخه، فقد ضاع السرّ
- إن أُرسل بالبريد الإلكترونيّ، فقد ضاع السرّ
- إن وصل إلى مستودع، فقد تجد نفسك تنظّفه لزمن طويل جدّاً
لا يتطلّب ذلك مهاجماً عالي المهارة.
كون الشيء «يمكن فتحه في محرّر نصوص» هو في حدّ ذاته موقف ضعيف جدّاً.
3. «لكن المفتاح يجب أن يُخزَّن في مكان ما» ليست القصّة كاملة
هذا الاعتراض معقول، ويستحقّ إجابة دقيقة لأنّه بالضبط حيث تصبح الكتابة الأمنيّة مبهمة إذا تحرّكت بسرعة كبيرة.
الإجابة الصادقة هي:
- نعم، يحتاج التشفير إلى نقطة ثقة (trust anchor) في مكان ما
- لا، لا يعني ذلك أنّ كلّ تصميم تخزين متكافئ فعلاً
3.1. ما الذي يتغيّر فعلاً
عادةً ما يتلخّص الفرق الأمنيّ في ثلاثة أسئلة:
- هل يحمل التطبيق مادّة المفتاح مباشرةً بنفسه؟
- بأيّ هويّة أو حدّ يرتبط المفتاح؟
- إن سُرق الملفّ وحده، هل يمكن فكّ تشفيره دون اتّصال؟
هنا تبدأ الفجوة بين النصّ الصريح، والتشفير محلّيّ الصنع، و DPAPI في أن تصبح حقيقيّة.
| الأسلوب | يُقرأ الملفّ مباشرةً | يُنسخ الملفّ إلى حاسوب آخر | يستطيع مستخدم آخر على نفس الحاسوب قراءته | شيفرة تعمل بنفس المستخدم |
|---|---|---|---|---|
| النصّ الصريح | ينكشف السرّ على الفور | ينكشف السرّ على الفور | ينكشف السرّ على الفور | تستطيع قراءته بطبيعة الحال |
| تشفير مخصَّص مع تخزين المفتاح في الإعداد أو الثنائيّ نفسه | غالباً ما ينكشف عمليّاً | غالباً ما ينكشف عمليّاً | غالباً ما ينكشف عمليّاً | تستطيع فكّ تشفيره بطبيعة الحال |
DPAPI + CurrentUser |
لا يمكن قراءة الملفّ وحده على الفور | يصعب فكّ تشفيره عادةً | يصعب فكّ تشفيره عادةً | تستطيع فكّ تشفيره |
DPAPI + LocalMachine |
لا يمكن قراءة الملفّ وحده على الفور | يصعب فكّ تشفيره خارج ذلك الحاسوب عادةً | قد تستطيع أيّ عمليّة على ذلك الحاسوب فكّ تشفيره | تستطيع فكّ تشفيره |
النقطة المهمّة هي أنّ DPAPI يفصل بين:
- «القدرة على قراءة الملفّ»
- «القدرة على استخدام السرّ فعلاً»
النصّ الصريح يدمج هذين الأمرين في شيء واحد.
إن كان الملفّ قابلاً للقراءة، فالسرّ قابل للقراءة.
مع DPAPI، خاصّةً CurrentUser، يرتبط فكّ التشفير بـ:
- ذلك المستخدم في Windows
- في ذلك السياق في Windows
- عبر آليّة الحماية في نظام التشغيل
ذلك الفرق يهمّ كثيراً في الحوادث اليوميّة.
3.2. «لكن المستخدم نفسه ما زال يستطيع فكّ تشفيره» صحيحة
هذا الجزء لا ينبغي إخفاؤه أو تخفيفه.
إن عملت شيفرة خبيثة بسياق المستخدم نفسه، فبإمكانها عموماً فكّ تشفير ما يستطيع التطبيق فكّ تشفيره.
ولذلك فإنّ DPAPI ليس بصورة رئيسيّة دفاعاً ضدّ مواقف مثل:
- الجهاز مصاب أصلاً ببرمجيّة خبيثة
- يستطيع المهاجم تنفيذ شيفرة بصلاحيّة ذلك المستخدم
- تمّ الاستيلاء الكامل على النظام على مستوى المسؤول
في تلك الحال، فإنّ قول «لكنّ الإعدادات مشفّرة» ليس مطمئناً جدّاً.
يستطيع التطبيق فكّ تشفير البيانات، فالشيفرة العدائيّة في نفس السياق تستطيع ذلك أيضاً عادةً.
حيث يساعد DPAPI هو في الجانب الآخر من المشكلة في الغالب:
- تسرّب الملفّات
- وضعها في أماكن خاطئة بطريق الخطأ
- النسخ دون اتّصال
- مستخدم آخر على نفس الجهاز
- النصّ المشفَّر معرَّض للكشف دون سياق Windows الصحيح
الخلط بين هذين النموذجين للتهديد يقود إلى خطأين مختلفين:
- التقليل من شأن ما يساعد فيه DPAPI
- المبالغة فيما لا يساعد فيه
كلاهما فخّ سهل الوقوع فيه.
3.3. ما الذي تكسبه فعلاً
القيمة العمليّة لـ DPAPI أنّه يتيح لك الفصل بين:
قابليّة قراءة ملفّ الإعدادات
و
قابليّة استخدام السرّ نفسه
ولذلك تتغيّر النتيجة في حوادث مثل:
- يرسل المستخدم ملفّ الإعدادات إلى الدعم
- يحوي ZIP استكشاف الأخطاء ملفّ الإعدادات
- تتسرّب نسخة احتياطيّة لملفّ الإعدادات فقط
- يُنسخ الملفّ إلى مجلّد مشترك
- يستطيع مطوِّر رؤية النصّ المشفَّر لكنّه لا يستطيع قراءة قيمة السرّ بسهولة
تلك فائدة حقيقيّة جدّاً.
لا تحتاج إلى تخيّل مهاجم بمستوى الأفلام لكي يكون لذلك أهمّيّة. فهو يقلّل بالفعل من نطاق انفجار الأخطاء الاعتياديّة.
4. لماذا يكون DPAPI غالباً الإجابة بالحجم المناسب
عندما يحتاج تطبيق Windows إلى الاحتفاظ بسرّ محلّيّاً، يكون DPAPI ملائماً جيّداً في كثير من الأحيان لأسباب عمليّة.
4.1. ينقل إدارة المفاتيح إلى نظام التشغيل
إن قرّرتَ إدارة آليّتك القائمة على AES بنفسك، فعليك الآن التفكير في:
- توليد المفتاح
- تخزين المفتاح
- صلاحيّات الملفّات
- التدوير
- تأثير التسرّب
- كشف العبث
ذلك عمل أكبر ممّا يبدو لأوّل وهلة، وعندما يُنفَّذ بإهمال، فإنّ المفتاح ينتهي به المطاف عادةً بجوار النصّ المشفَّر على أيّ حال.
يزيل DPAPI الحاجة إلى الإجابة عن سؤال «أين ينبغي للتطبيق أن يحفظ مفتاح التشفير الخاصّ به؟» في شيفرة التطبيق.
ولذلك فإنّ من الأنفع التفكير في DPAPI ليس بوصفه:
- API لاختيار خوارزميّة تشفير
بل بوصفه:
- API لتفويض إدارة المفاتيح إلى Windows
4.2. يربط فكّ التشفير بمستخدم Windows أو الجهاز
بالنسبة لكثير من تطبيقات سطح المكتب، يكون CurrentUser هو الافتراضيّ الصحيح لأنّ فكّ التشفير يفترض طبيعيّاً:
- أنّ المستخدم قد سجّل الدخول
- أنّ التطبيق يعمل في سياق ذلك المستخدم
ذلك يمنحك خاصّيّة مفيدة:
نسخ النصّ المشفَّر فقط إلى جهاز آخر لا يجعله عادةً قابلاً للاستخدام على الفور.
4.3. يمنحك أيضاً حماية السلامة (integrity)
من الأخطاء الشائعة في تصاميم التشفير المخصّصة التوقّف عند «إنّه مشفَّر» ونسيان كشف العبث.
يتضمّن DPAPI حماية سلامة حول الـ blob المحميّ، ممّا يسهّل اكتشاف أنّ النصّ المشفَّر قد عُدِّل بدلاً من مجرّد الإخفاق بصورة غامضة لاحقاً.
4.4. سهل الاستخدام من C# / .NET
في C#، يوجد System.Security.Cryptography.ProtectedData بالفعل.
بالنسبة للبرمجيّات الموجَّهة لـ Windows فقط، من المفيد جدّاً ألّا تضيف مكتبات إضافيّة لمجرّد التوقّف عن تخزين كلمة مرور بنصّ صريح.
5. ما الذي يساعد فيه DPAPI، وما الذي لا يساعد فيه
من الأكثر أماناً رسم الخطّ بوضوح.
5.1. أين يساعد DPAPI
DPAPI مفيد لأمور مثل:
- تسرّب النصّ الصريح من ملفّات الإعدادات
- نسخ ملفّ إلى حاسوب آخر
- الوصول من مستخدم آخر على نفس الجهاز، بافتراض
CurrentUser - ظهور الأسرار في ملفّات النُّسخ الاحتياطيّة أو المرفقات
- تقليل القابليّة العفويّة للقراءة أثناء التطوير أو الصيانة
5.2. أين لا يحلّ DPAPI المشكلة الفعليّة
ليس شيئاً يجب الإفراط في الثقة به في هذه الحالات:
- شيفرة خبيثة تعمل بنفس المستخدم
- اختراق كامل للجهاز
- استيلاء على مستوى المسؤول
- النصّ الصريح الموجود أصلاً في الذاكرة بعد فكّ التشفير
- الأسرار المشتركة طويلة العمر الموزَّعة على كلّ عميل
النقطة الأخيرة مهمّة بصورة خاصّة.
تصاميم مثل:
- تضمين المفتاح API نفسه في كلّ تثبيت لدى العميل
- شحن نفس كلمة المرور المشتركة لكلّ نقطة طرفيّة
- توزيع مفتاح فكّ تشفير ثابت يعيش بالكامل في جانب العميل
ما زالت تتمتّع بنطاق انفجار واسع بمجرّد أن يُستخدَم جهاز واحد لاسترداد السرّ.
يمكن لـ DPAPI أن يجعل موقع التخزين أفضل من النصّ الصريح.
لكنّه لا يبرّر إبقاء أسرار في العميل ما كان ينبغي أن تكون هناك في المقام الأوّل.
6. الاختيار بين CurrentUser و LocalMachine
هذا واحد من أهمّ القرارات في التصميم بأكمله.
6.1. ابدأ بـ CurrentUser
بالنسبة لتطبيقات سطح المكتب الاعتياديّة في Windows، ينبغي أن يكون CurrentUser هو الافتراضيّ عادةً.
من الأمثلة الجيّدة:
- تطبيقات WPF أو WinForms أو WinUI لسطح المكتب للمستخدمين النهائيّين
- التطبيقات التي يكون لكلّ مستخدم فيها معلومات اعتماد أو إعدادات منفصلة
- التطبيقات التي تخزّن الإعدادات تحت
%LocalAppData%أو%AppData%
ذلك يتطابق طبيعيّاً مع فكرة:
«هذا سرّ يخصّ مستخدم Windows هذا.»
6.2. LocalMachine أكثر تحديداً بكثير
قد يبدو LocalMachine ملائماً، لكنّه بالنسبة لتطبيقات سطح المكتب الاعتياديّة عادةً ما يكون واسعاً جدّاً.
يلائم أكثر حالات مثل:
- جهاز موثوق ذو غرض واحد يشغّل خدمة Windows
- سرّ مقصود لمجموعة عمليّات معيّنة على ذلك الجهاز
- حالة يجب فيها فعلاً أن يبقى نفس السرّ على مستوى الجهاز عابراً لمستخدمي تسجيل الدخول
لكنّ التكاليف حقيقيّة:
- قد تستطيع العمليّات على ذلك الجهاز فكّ تشفيره على نطاق أوسع بكثير
- تصبح أجهزة المستخدمين المتعدّدين، وبيئات RDS، والطرفيّات المشتركة أكثر خطورة بكثير
- «الجميع يستطيع استخدامه، فيكون أسهل» تتحوّل غالباً إلى ندم لاحقاً
6.3. قاعدة بسيطة للاختيار
- تطبيق UI عاديّ ->
CurrentUser - حالة خاصّة فعلاً على مستوى الجهاز ->
LocalMachine - يجب أن يكون قابلاً لفكّ التشفير من قِبَل أيّ مستخدم على جهاز متعدّد المستخدمين -> عادةً يستحقّ التصميم إعادة تفكير أعمق
6.4. تحتاج الخدمات و impersonation إلى عناية أكثر
ما إن تدخل خدمات Windows أو impersonation الصورة، حتّى يصبح CurrentUser أكثر دقّة.
يجب أن تكون واضحاً بشأن:
- أيّ حساب يعمل فعلاً
- ما إذا كان ملفّه الشخصيّ محمّلاً
- أيّ سياق يُستخدَم وقت فكّ التشفير
إن تباعدت تلك الافتراضات، يسهل أن ينتهي بك المطاف في الموقف غير السارّ حيث يستطيع التطبيق حماية القيمة لكنّه لا يستطيع لاحقاً إلغاء حمايتها.
7. إرشادات الحدّ الأدنى للتنفيذ
إن كان هدفك المباشر هو ببساطة «التوقّف عن تخزين الأسرار بنصّ صريح في ملفّ الإعدادات»، فلا يحتاج التصميم إلى أن يصبح متقناً جدّاً. لكن هناك بضع نقاط تستحقّ الاحتفاظ بها.
7.1. احمِ قيم الأسرار فقط
عادةً ما يكون أسهل بكثير حماية حقول الأسرار وحدها بدلاً من تشفير ملفّ الإعدادات بأكمله.
أمور كهذه يمكن أن تبقى عادةً بنصّ صريح:
- عنوان URL للخادم
- اسم المستخدم
- اسم قاعدة البيانات
- أعلام الميزات (feature flags)
أمور كهذه هي أهداف الحماية الفعليّة:
- كلمات المرور
- API tokens
- refresh tokens
- معلومات اعتماد المجلّدات المشتركة
ذلك الفصل يُبقي:
- الإعدادات أسهل في التحرير
- الفروقات (diffs) أسهل في الفحص
- حدود السرّ أسهل في الفهم
- التصميم العامّ أبسط في التشغيل
7.2. استخدم مسار تخزين خاصّاً بكلّ مستخدم
بالنسبة لتطبيقات سطح المكتب الاعتياديّة، يكون الموقع الخاصّ بكلّ مستخدم عادةً الافتراضيّ الأنظف:
%LocalAppData%\Vendor\App\settings.json%AppData%\Vendor\App\settings.json
حتّى مع DPAPI، من الأفضل ألّا تضع الملفّ في دليل تثبيت أو في مسار مشترك بشكل عفويّ.
يحمي DPAPI القيمة الحسّاسة، لكنّ موقع التخزين الضعيف ما زال يعني:
- النصّ المشفَّر قابل للقراءة
- بنية الإعدادات مرئيّة
- يسهل ارتكاب الأخطاء التشغيليّة
يعمل الدفاع بطبقات أفضل من حركة واحدة.
7.3. optionalEntropy ليس مفتاحاً سحريّاً ثانياً
يتيح لك ProtectedData تمرير optionalEntropy، وهو مفيد لكن من السهل سوء فهمه.
إنّه ليس مفتاحاً ثانياً سرّيّاً سحريّاً لمجرّد أنّه مصفوفة بايتات أخرى.
- إن جلس في الملفّ نفسه، فهو ليس سرّيّاً فعلاً
- إن كان ثابتاً مثبتاً في الثنائيّ، فهو ليس مادّة سرّ قويّة
- لكنّه ما زال مفيداً بوصفه مميِّزاً للاستخدام وعلامة لمنع سوء الاستخدام
في الممارسة العمليّة، يعمل غالباً بشكل جيّد بوصفه:
- اسم التطبيق
- اسم الغرض
- علامة الإصدار
ممرَّراً كتسلسل بايتات ثابت بحيث لا يُقبل بطريق الخطأ blob محميّ على أنّه نوع آخر من الـ blobs المحميّة.
7.4. أفضل من النصّ الصريح لا يعني أنّه مقبول للالتزام (commit)
من السهل تفويت هذه النقطة.
النصّ المشفَّر بـ DPAPI أفضل بكثير من النصّ الصريح، لكنّ ذلك ما زال لا يعني أنّ ملفّ الإعدادات بأكمله ينتمي الآن إلى المستودع.
لماذا لا؟
- يعيش النصّ المشفَّر طويلاً
- قد يظهر نفس الجهاز أو السياق لاحقاً
- يحوي الملفّ غالباً معلومات غير سرّيّة أيضاً
- يمكن أن تكتسب الفِرَق العادة السيّئة بمعاملة «المحميّ» على أنّه «آمن في أيّ مكان»
«أفضل من النصّ الصريح» و«آمن للوضع في أيّ مكان» مقولتان مختلفتان جدّاً.
7.5. لا تسرِّب السرّ عبر السجلّات
إحدى أكثر الطرق شيوعاً لإحباط كلّ هذا العمل هي تسجيل القيمة بعد فكّ تشفيرها.
من الأمثلة النموذجيّة:
- طباعة سلسلة الاتّصال الكاملة عند فشل اتّصال
- تسجيل ترويسة
Authorizationعند خطأ API 401 - تضمين السرّ في رسالة استثناء
عند تلك النقطة قد يصبح ملفّ الإعدادات أنظف، لكنّ السجلّات تصبح بدلاً من ذلك مستودعاً للنصّ الصريح.
8. مثال أدنى بـ C# / .NET
فيما يلي مثال صغير يحمي سلسلة لتخزينها في ملفّ إعدادات باستخدام CurrentUser.
قيمة optionalEntropy الثابتة موجودة بوصفها علامة استخدام.
ينبغي ألّا تُعامَل بوصفها مفتاحاً سرّيّاً.
using System;
using System.Security.Cryptography;
using System.Text;
public static class DpapiSecretProtector
{
// Purpose marker only. This is not a second secret key.
private static readonly byte[] Entropy =
Encoding.UTF8.GetBytes("ComComponent:DesktopApp:SettingsSecret:v1");
public static string ProtectToBase64(string plaintext)
{
ArgumentNullException.ThrowIfNull(plaintext);
byte[] plainBytes = Encoding.UTF8.GetBytes(plaintext);
byte[] protectedBytes = Array.Empty<byte>();
try
{
protectedBytes = ProtectedData.Protect(
plainBytes,
optionalEntropy: Entropy,
scope: DataProtectionScope.CurrentUser);
return Convert.ToBase64String(protectedBytes);
}
finally
{
Array.Clear(plainBytes, 0, plainBytes.Length);
if (protectedBytes.Length > 0)
{
Array.Clear(protectedBytes, 0, protectedBytes.Length);
}
}
}
public static string UnprotectFromBase64(string protectedBase64)
{
ArgumentNullException.ThrowIfNull(protectedBase64);
byte[] protectedBytes = Convert.FromBase64String(protectedBase64);
byte[] plainBytes = Array.Empty<byte>();
try
{
plainBytes = ProtectedData.Unprotect(
protectedBytes,
optionalEntropy: Entropy,
scope: DataProtectionScope.CurrentUser);
return Encoding.UTF8.GetString(plainBytes);
}
finally
{
Array.Clear(protectedBytes, 0, protectedBytes.Length);
if (plainBytes.Length > 0)
{
Array.Clear(plainBytes, 0, plainBytes.Length);
}
}
}
}
الاستخدام بسيط:
string protectedPassword = DpapiSecretProtector.ProtectToBase64(password);
// Save into JSON, for example
// settings.DbPasswordProtected = protectedPassword;
string password = DpapiSecretProtector.UnprotectFromBase64(settings.DbPasswordProtected);
يمكن أن يبدو ملفّ الإعدادات حينئذٍ كذلك:
{
"ApiBaseUrl": "https://api.example.com/",
"UserName": "app-user",
"PasswordProtected": "AQAAANCMnd8BFdERjHoAwE..."
}
لذلك الشكل ميزات عمليّة:
- يبقى عنوان URL واسم المستخدم قابلَيْن للتحرير
- كلمة المرور وحدها هي التي تحتاج إلى حماية
- تبقى الإعدادات قابلة للقراءة بوصفها بنية
- إنّها أقلّ عرضة للحوادث بكثير من ترك كلمة المرور بنصّ صريح
9. تصاميم لا تزال خطرة
استخدام DPAPI لا يجعل التصميم المحيط جيّداً تلقائيّاً.
9.1. الاحتفاظ بالقيم المفكوكة فترة طويلة جدّاً
تجنّب الأنماط التي تكون فيها القيمة المفكوكة بعد ذلك:
- مسجَّلة
- معروضة
- مرفقة بالاستثناءات
- محمولة في كائنات طويلة العمر بلا سبب
«مشفَّر في حالة السكون» و«آمن أثناء الاستخدام» مشكلتان مختلفتان.
9.2. منح كلّ تثبيت السرّ نفسه
إن كان كلّ تثبيت يحمل في نهاية المطاف نفس مفتاح API أو نفس كلمة المرور المشتركة، فإنّ DPAPI لا يحلّ المسألة الجوهريّة.
بمجرّد أن يستطيع جهاز واحد فكّ تشفير القيمة، يمكن استرداد ذلك السرّ المشترك.
اتّجاه أفضل عادةً:
- ابقِ السرّ الحقيقيّ في جانب الخادم
- دع العميل يحمل token بدلاً من ذلك
- استخدم معلومات اعتماد خاصّة بكلّ مستخدم
- استخدم tokens قصيرة العمر
9.3. اختيار LocalMachine لأنّه ملائم
هذا إغراء شائع جدّاً.
- يعمل عبر تبديل المستخدمين
- تستطيع الخدمات قراءته
- يبدو أبسط
لكن في نظام سطح مكتب اعتياديّ، فإنّه يوسّع أيضاً نطاق من على ذلك الجهاز يستطيع فكّ تشفيره. ذلك وضع أمنيّ مختلف جدّاً.
9.4. إضافة تشفير مخصّص لطمأنة النفس
استبدال DPAPI بأنماط كهذه ليس عادةً تحسيناً حقيقيّاً:
- تضمين مفتاح AES في الشيفرة المصدريّة
- تخزين مفتاح AES في حقل إعدادات آخر
- معاملة سلسلة مموَّهة قليلاً وكأنّها مادّة مفتاح
هناك فجوة كبيرة جدّاً بين:
- «ليس نصّاً صريحاً»
و
- «آمن فعلاً»
10. متى لا يكفي DPAPI
DPAPI مفيد، لكنّه ليس عامّاً.
10.1. يجب أن يعمل التطبيق خارج Windows
DPAPI / ProtectedData إجابة موجَّهة لـ Windows.
إن كان على التطبيق أن يكون عابراً للمنصّات، فإنّ هذا الافتراض لم يعد ملائماً.
10.2. يجب مشاركة السرّ نفسه عبر أجهزة أو مستخدمين
إن كان المتطلَّب:
- نصّاً مشفَّراً واحداً يجب أن تفكّ تشفيره عدّة أجهزة حاسوب
- أو سرّاً واحداً مشتركاً بين عدّة مستخدمين
فأنت خارج المنطقة الأنسب لـ DPAPI.
عادةً تريد عند ذلك النظر إلى:
- إدارة الأسرار في جانب الخادم
- نظام هويّة أو معلومات اعتماد
- Windows authentication أو integrated authentication
- تصميم مختلف لمخزن معلومات اعتماد التطبيق
10.3. العنصر المخزَّن هو زوج معلومات اعتماد مستخدم صراحةً
بالنسبة لتطبيقات سطح المكتب المُحزَّمة (packaged) أو بعض حالات نمط WinUI، إن كانت البيانات المخزَّنة بوضوح:
- اسم مستخدم
- كلمة مرور
فقد يكون Credential Locker ملائماً أكثر.
لكنّ تركيز هذا المقال هو المشكلة الأكثر اعتياديّة لعميل Windows: التوقّف عن وضع الأسرار بنصّ صريح في ملفّات إعدادات التطبيقات.
11. ترتيب أولويّات عمليّ
إن بدا التصميم فوضويّاً، فإنّ هذا الترتيب عادةً طريقة جيّدة للخروج من الجمود.
الأولويّة 1: تجنّب الاحتفاظ بالسرّ في العميل أصلاً
- Windows authentication
- integrated authentication
- تسجيل الدخول التفاعليّ
- معالجة الأسرار في جانب الخادم
- tokens قصيرة العمر
الأولويّة 2: فضِّل الحدود الخاصّة بكلّ مستخدم
- أسرار خاصّة بكلّ مستخدم بدلاً من سرّ مشترك واحد
- tokens قابلة للتجديد بدلاً من معلومات اعتماد ثابتة طويلة العمر
- تجنّب وجود مفتاح مشترك واحد بين كلّ العملاء
الأولويّة 3: إن استلزم الأمر التخزين المحلّيّ، استخدم DPAPI
- عادةً
CurrentUser - خزِّن الملفّ بحسب كلّ مستخدم
- احمِ حقول الأسرار وحدها
- أبقِ القيمة المفكوكة بعيدة عن السجلّات
الأولويّة 4: عامل LocalMachine على أنّه مسار استثنائيّ
- هل يحتاج فعلاً إلى أن يكون على مستوى الجهاز؟
- هل سيوجد مستخدمون آخرون على ذلك الجهاز؟
- هل الخدمة أو النموذج التشغيليّ متّسق فعلاً مع ذلك الحدّ؟
12. الخلاصة
عندما يحتاج تطبيق Windows إلى الاحتفاظ بسرّ في ملفّ إعدادات، فإنّ تركه بنصّ صريح هو الجزء الذي يستحقّ تركه أوّلاً.
والإجابة العمليّة عن:
«إن كان لا بدّ من وجود المفتاح في مكان ما، أليست كلّها متماثلة أساساً؟»
هي:
- إن وضعتَ مفتاحك في المكان نفسه، فقد ينتهي الأمر بأن يكون قريباً جدّاً من النصّ الصريح
- DPAPI ليس الشيء نفسه
- فهو ينقل إدارة المفاتيح إلى Windows
- ويربط فكّ التشفير بمستخدم Windows أو الجهاز
- ويحول دون أن يصبح ملفّ مسرَّب تلقائيّاً سرّاً مسرَّباً
- لكنّه ما زال لا يحلّ:
- شيفرة تعمل بنفس المستخدم
- نقطة طرفيّة مخترقة بالكامل
- الأسرار المشتركة طويلة العمر التي لا يجب أن تعيش في العميل أصلاً
DPAPI ليس قلعة كاملة.
إنّه أقرب إلى استبدال نافذة شفّافة تماماً بنافذة تنتمي على الأقلّ إلى مبنى حقيقيّ.
في عمل عميل Windows، ذلك الفرق كبير بما يكفي ليكون مهمّاً.
وفي الممارسة العمليّة، عادةً ما يكون ذلك المكان الصحيح للبدء منه.
13. المراجع
- المقال السابق: https://comcomponent.com/ar/blog/2026/03/14/001-windows-app-security-minimum-checklist/
- Microsoft Learn:
CryptProtectData
https://learn.microsoft.com/en-us/windows/win32/api/dpapi/nf-dpapi-cryptprotectdata - Microsoft Learn:
ProtectedData
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.protecteddata?view=windowsdesktop-10.0 - Microsoft Learn:
DataProtectionScope
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.dataprotectionscope?view=windowsdesktop-10.0 - Microsoft Learn: How to: Use Data Protection
https://learn.microsoft.com/en-us/dotnet/standard/security/how-to-use-data-protection - Microsoft Learn: Credential locker for Windows apps
https://learn.microsoft.com/en-us/windows/apps/develop/security/credential-locker
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
كيف تعزل العمل الذي يحتاج إلى المسؤول فقط داخل تطبيقات Windows
دليل عمليّ لإبقاء واجهة تطبيق Windows عند asInvoker مع إسناد العمل المرفَّع إلى helper EXE عبر runas و named pipes، مع التحقّق من الطلبات...
قائمة تحقّق للحدّ الأدنى من الأمان في تطوير تطبيقات Windows
قائمة تحقّق عمليّة لخطّ الأساس الأمنيّ في تطبيقات Windows: حدود الصلاحيّات، توقيع التوزيع، حماية الأسرار، HTTPS، تحميل DLL، logging، وتحد...
أساسيّات أمان ميزة التحديث التلقائيّ - الأنماط السيّئة وأفضل الممارسات
نلخّص أساسيّات أمان ميزة التحديث التلقائيّ: لماذا لا يكفي HTTPS، وأهمّيّة signed metadata والتحقّق من جهة الـ client وفصل المفاتيح ومواجه...
المزالق الشائعة في تطوير مكوّنات COM و OCX / ActiveX - فخاخ Visual Studio بين 32-bit و 64-bit، والتسجيل، وصلاحيّات المسؤول
دليل عمليّ يكشف الأسباب الحقيقيّة لإخفاق مكوّنات COM و OCX و ActiveX: عدم تطابق 32-bit / 64-bit مع Visual Studio 2022، وأخطاء regsvr32 و ...
أين يجب التقاط الاستثناءات وتسجيلها ومعالجة الأخطاء - دليل عمليّ للحدود والمسؤوليّات في تسلسل الاستدعاء
دليل عمليّ يساعدك على تحديد مستوى تسلسل الاستدعاء الذي يجب فيه التقاط الاستثناء وكتابة السجلّ وتحويل الإخفاق إلى قرار، مع أمثلة C# وقائمة...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
ندعم تطوير برامج ويندوز للأعمال، وتكامل الأجهزة، وأدوات التواصل.