الأمان والتشفير
ركّزت محاضرة الأمان والخصوصية في العام الماضي على كيف يمكنك أن تكون أكثر أمانًا كمستخدمِ حاسوبٍ. أمّا هذا العام، فسنركّز على مفاهيم الأمان والتشفير ذات الصلة بفهم الأدوات التي غُطّيت سابقًا في هذا المقرر، مثل استخدام دوال التجزئة (hash functions) في Git، أو دوال اشتقاق المفاتيح (key derivation functions) وأنظمة التشفير المتماثل وغير المتماثل (symmetric/asymmetric cryptosystems) في SSH.
هذه المحاضرة ليست بديلًا عن مقررٍ أكثر صرامةً واكتمالًا في أمان أنظمة الحاسوب (6.858) أو التشفير (6.857 و6.875). لا تقم بأي عملٍ في مجال الأمان دون تدريبٍ رسميٍّ في الأمان. ما لم تكن خبيرًا، فلا تخترع تشفيرك الخاص. ينطبق المبدأ نفسه على أمان الأنظمة.
تتناول هذه المحاضرة معالجةً غير رسميةٍ جدًا (لكنها عمليةٌ في اعتقادنا) للمفاهيم الأساسية في التشفير. لن تكون هذه المحاضرة كافيةً لتعليمك كيفية تصميم أنظمةٍ آمنةٍ أو بروتوكولات تشفير، لكننا نأمل أن تكون كافيةً لتعطيك فهمًا عامًا للبرامج والبروتوكولات التي تستخدمها فعلًا.
الإنتروبيا (Entropy)
الإنتروبيا هي مقياسٌ للعشوائية. هذا مفيدٌ، على سبيل المثال، عند تحديد قوة كلمة المرور.

كما توضّح شريطة XKCD أعلاه، فإن كلمة مرور مثل "correcthorsebatterystaple" أكثر أمانًا من مثل "Tr0ub4dor&3". لكن كيف تكمّم شيئًا كهذا؟
تُقاس الإنتروبيا بوحدة البتات (bits)، وعند الاختيار بشكلٍ عشوائيٍّ منتظمٍ من مجموعة النتائج المحتملة، تكون الإنتروبيا مساويةً لـ log_2(عدد الاحتمالات). رمي عملةٍ عادلةٍ يعطي 1 بتٍ من الإنتروبيا. رمي نردةٍ (سداسية الأوجه) لديه ~2.58 بتٍ من الإنتروبيا.
يجب أن تعتبر أن المهاجم يعرف نموذج كلمة المرور، لكنه لا يعرف العشوائية (مثلًا من رمي النرد) المستخدمة لاختيار كلمة مرورٍ معيّنة.
كم عدد بتات الإنتروبيا الكافية؟ يعتمد ذلك على نموذج التهديد الخاص بك. للتخمين عبر الإنترنت، كما تشير شريطة XKCD، فإن ~40 بتًا من الإنتروبيا جيدةٌ جدًا. لمقاومة التخمين دون اتصالٍ بالإنترنت، ستكون هناك حاجةٌ إلى كلمة مرورٍ أقوى (مثل 80 بتًا أو أكثر).
دوال التجزئة (Hash functions)
تُرسم دالة التجزئة المشفّرة بياناتٍ ذات حجمٍ عشوائيٍّ إلى حجمٍ ثابت، ولها خصائص خاصةٌ. المواصفة التقريبية لدالة التجزئة هي كما يلي:
hash(value: array<byte>) -> vector<byte, N> (for some fixed N)
مثالٌ على دالة تجزئةٍ هو SHA1، والتي تُستخدم في Git. إنها ترسم مدخلاتٍ ذات أحجامٍ عشوائيةٍ إلى مخرجاتٍ من 160 بتًا (والتي يمكن تمثيلها بـ40 حرفًا سداسيًا عشريًا). يمكننا تجربة تجزئة SHA1 على مدخلٍ باستخدام الأمر sha1sum:
$ printf 'hello' | sha1sum
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
$ printf 'hello' | sha1sum
aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
$ printf 'Hello' | sha1sum
f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0
على مستوىً عالٍ، يمكن التفكير في دالة التجزئة كدالةٍ يصعب عكسها وتبدو عشوائيةً (لكنها حتمية) (وهذا هو النموذج المثالي لدالة التجزئة). تمتلك دالة التجزئة الخصائص التالية:
- حتمية (Deterministic): نفس المدخل يولّد دائمًا نفس المخرج.
- غير قابلةٍ للعكس (Non-invertible): من الصعب إيجاد مدخلٍ
mبحيثhash(m) = hلمخرجٍ مرغوبٍhمعيّن. - مقاومة اصطدام الهدف (Target collision resistant): بمعلومية مدخلٍ
m_1، من الصعب إيجاد مدخلٍ مختلفٍm_2بحيثhash(m_1) = hash(m_2). - مقاومة الاصطدام (Collision resistant): من الصعب إيجاد مدخلين
m_1وm_2بحيثhash(m_1) = hash(m_2)(لاحظ أن هذه خاصيةٌ أقوى بصرامةٍ من مقاومة اصطدام الهدف).
ملاحظة: على الرغم من أنه قد يعمل لأغراضٍ معيّنة، فإن SHA-1 لم يعد يُعتبر دالة تجزئةٍ مشفّرةٍ قوية. قد تجد هذا الجدول الخاص بـأعمار دوال التجزئة المشفّرة مثيرًا للاهتمام. لكن لاحظ أن التوصية بدوال تجزئةٍ معيّنة خارج نطاق هذه المحاضرة. إذا كنت تقوم بعملٍ يهمّ فيه هذا الأمر، فأنت بحاجةٍ إلى تدريبٍ رسميٍّ في الأمان/التشفير.
التطبيقات
- Git، للتخزين الموجّه بالمحتوى (content-addressed storage). فكرة دالة التجزئة مفهومٌ أكثر عموميةً (هناك دوال تجزئةٍ غير مشفّرة). لماذا يستخدم Git دالة تجزئةٍ مشفّرة؟
- ملخصٌ قصيرٌ لمحتويات الملف. غالبًا ما يمكن تنزيل البرمجيات من خوادم مرآةٍ (مترجّحةٍ ربما) (potentially less trustworthy mirrors)، مثل صور Linux ISO، وسيكون من الجيد ألا تضطر إلى الثقة بها. تنشر المواقع الرسمية عادةً التجزئات إلى جانب روابط التنزيل (التي تشير إلى خوادم مرآةٍ تابعةٍ لجهاتٍ ثالثة)، بحيث يمكن التحقق من التجزئة بعد تنزيل الملف.
- مخططات الالتزام. لنفترض أنك تريد الالتزام بقيمةٍ معيّنة، لكنك تكشف القيمة نفسها لاحقًا. على سبيل المثال، أريد إجراء قرعة عملةٍ عادلةٍ "في رأسي"، دون عملةٍ مشتركةٍ موثوقةٍ يمكن لطرفين رؤيتها. يمكنني اختيار قيمةٍ
r = random()، ثم مشاركةh = sha256(r). بعدها يمكنك أن تقول "صورة" أو "كتابة" (سنتفق على أنrالزوجي يعني صورة، وrالفردي يعني كتابة). بعد اختيارك، يمكنني كشف قيمتيr، ويمكنك تأكيد أنني لم أغشّ بالتحقق من أنsha256(r)يطابق التجزئة التي شاركتها سابقًا.
دوال اشتقاق المفاتيح (Key derivation functions)
مفهومٌ ذو صلةٍ بالتجزئات المشفّرة، دوال اشتقاق المفاتيح (KDFs) تُستخدم لعددٍ من التطبيقات، بما في ذلك إنتاج مخرجاتٍ ذات طولٍ ثابتٍ لاستخدامها كمفاتيحٍ في خوارزميات تشفيرٍ أخرى. عادةً، تكون KDFs بطيئةً عن قصد، من أجل إبطاء هجمات القوة الغاشمة دون اتصالٍ بالإنترنت.
التطبيقات
- إنتاج مفاتيح من عبارات المرور (passphrases) لاستخدامها في خوارزميات تشفيرٍ أخرى (مثل التشفير المتماثل، انظر أدناه).
- تخزين بيانات اعتماد تسجيل الدخول. تخزين كلمات المرور كنصٍّ صريحٍ أمرٌ سيئ؛ النهج الصحيح هو توليد وتخزين ملحٍ عشوائيٍّ
salt = random()لكل مستخدمٍ، وتخزينKDF(password + salt)، والتحقق من محاولات تسجيل الدخول بإعادة حساب KDF بإعطاء كلمة المرور المُدخلة والملح المخزّن.
التشفير المتماثل (Symmetric cryptography)
إخفاء محتويات الرسائل ربما يكون أول مفهومٍ يخطر ببالك عندما تفكر في التشفير. يحقق التشفير المتماثل ذلك بالمجموعة الوظيفية التالية:
keygen() -> key (this function is randomized)
encrypt(plaintext: array<byte>, key) -> array<byte> (the ciphertext)
decrypt(ciphertext: array<byte>, key) -> array<byte> (the plaintext)
لدالة التشفير (encrypt) الخاصية التالية: بمعلومية المخرج (النص المشفَّر)، من الصعب تحديد المدخل (النص الصريح) دون المفتاح. ودالة فك التشفير (decrypt) لها خاصية الصحة الواضحة، وهي أن decrypt(encrypt(m, k), k) = m.
مثالٌ على نظام تشفيرٍ متماثلٍ واسع الاستخدام اليوم هو AES.
التطبيقات
- تشفير الملفات لتخزينها في خدمة سحابيةٍ غير موثوقة. يمكن دمج هذا مع KDFs، بحيث يمكنك تشفير ملفٍ بعبارة مرور. ولّد
key = KDF(passphrase)ثم خزّنencrypt(file, key).
التشفير غير المتماثل (Asymmetric cryptography)
يشير المصطلح "غير متماثل" إلى وجود مفتاحين لهما دورين مختلفين. المفتاح الخاص (private key)، كما يوحي اسمه، يُقصد إبقاؤه خاصًا، بينما يمكن مشاركة المفتاح العام (public key) علنًا ولن يؤثر ذلك على الأمان (بخلاف مشاركة المفتاح في نظامٍ متماثل). توفر أنظمة التشفير غير المتماثل المجموعة الوظيفية التالية، للتشفير/فك التشفير وللتوقيع/التحقق:
keygen() -> (public key, private key) (this function is randomized)
encrypt(plaintext: array<byte>, public key) -> array<byte> (the ciphertext)
decrypt(ciphertext: array<byte>, private key) -> array<byte> (the plaintext)
sign(message: array<byte>, private key) -> array<byte> (the signature)
verify(message: array<byte>, signature: array<byte>, public key) -> bool (whether or not the signature is valid)
لدوال التشفير/فك التشفير خصائص مشابهةٌ لنظيراتها في الأنظمة المتماثلة. يمكن تشفير رسالةٍ باستخدام المفتاح العام. بمعلومية المخرج (النص المشفَّر)، من الصعب تحديد المدخل (النص الصريح) دون المفتاح الخاص. ودالة فك التشفير لها خاصية الصحة الواضحة، وهي أن decrypt(encrypt(m, public key), private key) = m.
يمكن مقارنة التشفير المتماثل وغير المتماثل بالأقفال المادية. النظام المتماثل مثل قفل الباب: أي شخصٍ يملك المفتاح يمكنه قفله وفتحه. التشفير غير المتماثل مثل قفلٍ معلّقٍ مع مفتاح. يمكنك إعطاء القفل المفتوح لشخصٍ ما (المفتاح العام)، ويمكنه وضع رسالةٍ في صندوقٍ ثم وضع القفل، وبعد ذلك، لن تتمكن إلا أنت من فتح القفل لأنك احتفظت بالمفتاح (المفتاح الخاص).
لدوال التوقيع/التحقق الخصائص نفسها التي تتمنى أن تكون للتوقيعات المادية، من حيث صعوبة تزوير التوقيع. مهما كانت الرسالة، دون المفتاح الخاص، من الصعب إنتاج توقيعٍ بحيث يُرجع verify(message, signature, public key) صحيحًا. وبالطبع، لدالة التحقق خاصية الصحة الواضحة، وهي أن verify(message, sign(message, private key), public key) = true.
توزيع المفاتيح (Key distribution)
التشفير غير المتماثل رائع، لكنه يواجه تحديًا كبيرًا في توزيع المفاتيح العامة / ربط المفاتيح العامة بالهويات في العالم الحقيقي. هناك حلولٌ عديدةٌ لهذه المشكلة. تمتلك Signal حلًا بسيطًا: الثقة عند أول استخدام (trust on first use)، ودعم تبادل المفاتيح العامة خارج النطاق (out-of-band) (تتحقق من "أرقام الأمان" safety numbers الخاصة بأصدقائك شخصيًا). وتمتلك PGP حلًا مختلفًا، وهو شبكة الثقة. وتمتلك Keybase حلاً آخر هو الإثبات الاجتماعي (إلى جانب أفكارٍ أخرى لطيفة). لكل نموذجٍ مزاياه؛ نحن (المدرّسون) نفضّل نموذج Keybase.
دراسات حالة (Case studies)
مديرو كلمات المرور
هذه أداةٌ أساسيةٌ يجب على الجميع محاولة استخدامها (مثل KeePassXC وpass و1Password). يجعل مديرو كلمات المرور من المناسب استخدام كلمات مرورٍ فريدةٍ وعشوائيةٍ عالية الإنتروبيا لجميع عمليات تسجيل الدخول، ويحفظون جميع كلمات مرورك في مكانٍ واحدٍ مشفّرٍ بتشفيرٍ متماثلٍ بمفتاحٍ منتجٍ من عبارة مرورٍ باستخدام KDF.
استخدام مدير كلمات المرور يتيح لك تجنب إعادة استخدام كلمات المرور (لذا تتأثر أقل عندما تُخترق المواقع)، واستخدام كلمات مرورٍ عالية الإنتروبيا (لذا تقل احتمالية اختراقك)، وتحتاج فقط إلى تذكّر كلمة مرورٍ واحدةٍ عالية الإنتروبيا.
المصادقة ثنائية العوامل
تتطلب المصادقة ثنائية العوامل (2FA) منك استخدام عبارة مرور ("شيء تعرفه") إلى جانب أداة مصادقة 2FA (مثل YubiKey — "شيء تملكه") من أجل الحماية من كلمات المرور المسروقة وهجمات التصيّد.
تشفير القرص الكامل
إبقاء قرص حاسوبك المحمول مشفّرًا بالكامل طريقةٌ سهلةٌ لحماية بياناتك في حالة سرقة حاسوبك. يمكنك استخدام cryptsetup + LUKS على لينكس، أو BitLocker على ويندوز، أو FileVault على macOS. هذا يشفر القرص بأكمله بتشفيرٍ متماثلٍ، بمفتاحٍ محميٍّ بعبارة مرور.
المراسلة الخاصة
استخدم Signal أو Keybase. يُبنى الأمان من طرفٍ إلى طرفٍ انطلاقًا من تشفير المفاتيح غير المتماثلة. الحصول على المفاتيح العامة لجهات اتصالك هو الخطوة الحاسمة هنا. إذا أردت أمانًا جيدًا، فأنت بحاجةٍ إلى مصادقة المفاتيح العامة خارج النطاق (مع Signal أو Keybase)، أو الثقة بالإثباتات الاجتماعية (مع Keybase).
SSH
لقد غطّينا استخدام SSH ومفاتيح SSH في محاضرةٍ سابقة. لننظر إلى الجوانب التشفيرية لهذا.
عندما تشغّل ssh-keygen، فإنه يولّد زوج مفاتيحٍ غير متماثل، public_key, private_key. يولَّد هذا بشكلٍ عشوائيٍّ، باستخدام إنتروبيا يوفّرها نظام التشغيل (مجمّعةً من أحداثٍ عتاديةٍ، إلخ). يُخزَّن المفتاح العام كما هو (إنه عام، لذا فإبقاؤه سرًا ليس مهمًا)، لكن في حالة السكون، يجب تشفير المفتاح الخاص على القرص. يطالب برنامج ssh-keygen المستخدم بعبارة مرور، ويُمرَّر ذلك عبر دالة اشتقاق مفاتيحٍ لإنتاج مفتاحٍ، يُستخدم بعد ذلك لتشفير المفتاح الخاص بتشفيرٍ متماثل.
أثناء الاستخدام، بمجرد أن يعرف الخادم المفتاح العام للعميل (المخزّن في ملف .ssh/authorized_keys)، يمكن للعميل المتصل إثبات هويته باستخدام التوقيعات غير المتماثلة. يتم ذلك عبر الاستجابة للتحدي. على مستوىً عالٍ، يختار الخادم رقمًا عشوائيًا ويرسله إلى العميل. يوقّع العميل بعد ذلك هذه الرسالة ويرسل التوقيع إلى الخادم، الذي يتحقق من التوقيع مقابل المفتاح العام المسجَّل. هذا يثبت بفعاليةٍ أن العميل يملك المفتاح الخاص المطابق للمفتاح العام الموجود في ملف .ssh/authorized_keys الخاص بالخادم، لذا يمكن للخادم السماح للعميل بتسجيل الدخول.
الموارد
- ملاحظات العام الماضي: من حين كانت هذه المحاضرة تركّز أكثر على الأمان والخصوصية كمستخدمِ حاسوبٍ
- الإجابات التشفيرية الصحيحة: تجيب عن سؤال "أيّ تشفيرٍ يجب أن أستخدمه لـX؟" للعديد من X الشائعة.
تمارين
- الإنتروبيا.
- لنفترض أن كلمة مرورٍ تُختار كتراصٍّ لأربع كلماتٍ قاموسيةٍ صغيرة (lower-case)، حيث تُختار كل كلمةٍ بشكلٍ عشوائيٍّ منتظمٍ من قاموسٍ حجمه 100,000. مثالٌ على هذه الكلمة هو
correcthorsebatterystaple. كم عدد بتات الإنتروبيا التي تمتلكها؟ - فكّر في مخططٍ بديلٍ حيث تُختار كلمة المرور كتسلسلٍ من 8 محارفٍ أبجديةٍ رقميةٍ عشوائيةٍ (تشمل الحروف الصغيرة والكبيرة). مثالٌ
rg8Ql34g. كم عدد بتات الإنتروبيا التي تمتلكها؟ - أيّهما أقوى؟
- لنفترض أن المهاجم يمكنه تجربة تخمين 10,000 كلمة مرورٍ في الثانية. في المتوسط، كم من الوقت سيستغرق كسر كلٍّ من كلمتي المرور؟
- لنفترض أن كلمة مرورٍ تُختار كتراصٍّ لأربع كلماتٍ قاموسيةٍ صغيرة (lower-case)، حيث تُختار كل كلمةٍ بشكلٍ عشوائيٍّ منتظمٍ من قاموسٍ حجمه 100,000. مثالٌ على هذه الكلمة هو
- دوال التجزئة المشفّرة. نزّل صورة Debian من خادم مرآةٍ (مثلًا من هذا الخادم الأرجنتيني). تحقّق من التجزئة (مثلًا باستخدام الأمر
sha256sum) مع التجزئة المسترجعة من موقع Debian الرسمي (مثلًا هذا الملف المستضاف علىdebian.org، إذا كنت قد نزّلت الملف المرتبط من الخادم الأرجنتيني). - التشفير المتماثل. شفّر ملفًا بتشفير AES، باستخدام OpenSSL:
openssl aes-256-cbc -salt -in {input filename} -out {output filename}. انظر إلى المحتويات باستخدامcatأوhexdump. فك تشفيره بالأمرopenssl aes-256-cbc -d -in {input filename} -out {output filename}وتأكد من تطابق المحتويات مع الأصل باستخدامcmp. - التشفير غير المتماثل.
- أعد إعداد مفاتيح SSH على حاسوبٍ تملك حق الوصول إليه (وليس Athena، لأن Kerberos يتفاعل بشكلٍ غريبٍ مع مفاتيح SSH). تأكد من أن مفتاحك الخاص مشفّرٌ بعبارة مرور، بحيث يكون محميًا في حالة السكون.
- أعد إعداد GPG
- أرسل إلى Anish بريدًا إلكترونيًا مشفّرًا (المفتاح العام).
- وقّع التزامًا (commit) في Git بالأمر
git commit -Sأو أنشئ وسمًا (tag) موقّعًا بالأمرgit tag -s. تحقق من التوقيع على الالتزام بالأمرgit show --show-signatureأو على الوسم بالأمرgit tag -v.