تحديات كريبتوبالز › المجموعة 2 2 / 8 السابق التالي

المجموعة 2

هذه أول مجموعة من عدة مجموعات عن تشفير شيفرات الكتل (block ciphers). هذا هو التشفير الأساسي اليومي، النوع الذي ستراه مُنفَّذًا في معظم البرمجيات الشبكية التي تستخدم التشفير.

هذه المجموعة سهلة نسبيًا. من يُكمل المجموعة 1 يميل إلى إكمال المجموعة 2 بسرعة إلى حد ما.

ثلاثة من تحديات هذه المجموعة قيّمة للغاية في كسر التشفير الواقعي؛ أحدها يتيح لك فك تشفير رسائل مشفَّرة بالوضع الافتراضي لـ AES، والآخران يتيحان لك إعادة كتابة رسائل مشفَّرة بأشهر أوضاع AES.

9. تنفيذ حشو (padding) PKCS#7

تحوّل شيفرة الكتل كتلة ثابتة الحجم (عادة 8 أو 16 بايت) من النص الصريح (plaintext) إلى نص مشفَّر (ciphertext). لكننا لا نريد تقريبًا أبدًا تحويل كتلة واحدة؛ بل نشفّر رسائل بأحجام غير منتظمة.

من طرق التعامل مع الرسائل غير المنتظمة الحشو، أي إنشاء نص صريح يكون مضاعفًا زوجيًا لحجم الكتلة. أشهر مخطط حشو يُسمى PKCS#7.

إذن: احشُ أي كتلة إلى طول كتلة محدد، بإلحاق عدد بايتات الحشو بنهاية الكتلة. على سبيل المثال،

"YELLOW SUBMARINE"

... بعد حشوها إلى 20 بايت تصبح:

"YELLOW SUBMARINE\x04\x04\x04\x04"

10. تنفيذ وضع CBC

وضع CBC هو وضع لشيفرة الكتل يتيح لنا تشفير رسائل بأحجام غير منتظمة، رغم أن شيفرة الكتل لا تحوّل أصلًا سوى الكتل الفردية.

في وضع CBC، تُضاف كل كتلة نص مشفَّر إلى كتلة النص الصريح التالية قبل النداء التالي لنواة الشيفرة.

أول كتلة نص صريح، التي ليس لها كتلة نص مشفَّر سابقة مرتبطة بها، تُضاف إلى «كتلة نص مشفَّر صفرية وهمية» تُسمى متجه التهيئة (initialization vector)، أو IV.

نفّذ وضع CBC يدويًا بأخذ دالة ECB التي كتبتها سابقًا، وتعديلها لتقوم بـالتشفير بدل فك التشفير (تحقق من ذلك بفك تشفير ما تشفّره للاختبار)، واستخدام دالة XOR من التمرين السابق لدمجهما.

الملف هنا مفهوم (إلى حد ما) عند فك تشفيره بـ CBC تحت "YELLOW SUBMARINE" مع IV مكوَّن كله من ASCII 0 (\x00\x00\x00 وما إلى ذلك)

لا تغش.

لا تستخدم شيفرة CBC من OpenSSL لتنفيذ وضع CBC، ولا حتى للتحقق من نتائجك. ما الفائدة من فعل هذا كله إن لم تتعلم منه؟

11. أوراكل كشف (detection oracle) بين ECB وCBC

الآن وقد أصبح لديك ECB وCBC يعملان:

اكتب دالة لتوليد مفتاح AES عشوائي؛ هذا مجرد 16 بايت عشوائيًا.

اكتب دالة تشفّر بيانات تحت مفتاح مجهول --- أي دالة تولّد مفتاحًا عشوائيًا وتشفّر به.

ينبغي أن تبدو الدالة هكذا:

encryption_oracle(your-input)
=> [MEANINGLESS JIBBER JABBER]

تحت الغطاء، اجعل الدالة تلحق 5-10 بايتات (يُختار العدد عشوائيًا) قبل النص الصريح و5-10 بايتات بعد النص الصريح.

الآن، اجعل الدالة تختار التشفير بـ ECB نصف الوقت، وبـ CBC في النصف الآخر (استخدم متجهات تهيئة عشوائية في كل مرة لـ CBC). استخدم rand(2) لتقرير أيّهما تستخدم.

اكشف وضع شيفرة الكتل الذي تستخدمه الدالة في كل مرة. ينبغي أن ينتهي بك الأمر إلى قطعة شيفرة، عند توجيهها إلى صندوق أسود قد يشفّر بـ ECB أو CBC، تخبرك أيّهما يحدث.

12. فك تشفير ECB بايتًا بايتًا (بسيط)

انسخ دالة الأوراكل إلى دالة جديدة تشفّر المخازن بـ ECB باستخدام مفتاح ثابت لكن مجهول (على سبيل المثال، عيّن مفتاحًا عشوائيًا واحدًا مرة واحدة إلى متغير عام).

الآن خذ الدالة نفسها واجعلها تلحق بالنص الصريح، قبل التشفير، السلسلة التالية:

Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkg
aGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBq
dXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUg
YnkK

تنبيه بخصوص الحرق.

لا تفك ترميز هذه السلسلة الآن. لا تفعل ذلك.

فك ترميز base64 للسلسلة قبل إلحاقها. لا تفك ترميز base64 للسلسلة يدويًا؛ اجعل شيفرتك تفعل ذلك. المغزى ألا تعرف محتوياتها.

ما لديك الآن دالة تنتج:

AES-128-ECB(your-string || unknown-string, random-key)

وتبيّن أنك تستطيع فك تشفير "unknown-string" بنداءات متكررة إلى دالة الأوراكل!

إليك الطريقة التقريبية:

li { padding-top: 1em; }

  1. مرّر بايتات متطابقة من نصك إلى الدالة بايتًا واحدًا في كل مرة --- ابدأ ببايت واحد ("A")، ثم "AA"، ثم "AAA" وهكذا. اكتشف حجم كتلة الشيفرة. أنت تعرفه، لكن نفّذ هذه الخطوة على أي حال.
  2. اكشف أن الدالة تستخدم ECB. أنت تعرف ذلك مسبقًا، لكن نفّذ هذه الخطوة على أي حال.
  3. بمعرفة حجم الكتلة، صمّم كتلة إدخال أقصر ببايت واحد بالضبط (على سبيل المثال، إن كان حجم الكتلة 8 بايتات، فاجعلها "AAAAAAA"). فكّر فيما ستضعه دالة الأوراكل في موضع البايت الأخير ذلك.
  4. أنشئ قاموسًا بكل بايت أخير ممكن بتمرير سلاسل مختلفة إلى الأوراكل؛ على سبيل المثال، "AAAAAAAA"، "AAAAAAAB"، "AAAAAAAC"، مع تذكّر الكتلة الأولى من كل نداء.
  5. طابق ناتج الإدخال الأقصر ببايت واحد مع أحد مدخلات قاموسك. بذلك تكون قد اكتشفت البايت الأول من unknown-string.
  6. كرّر للبايت التالي.

تهانينا.

هذا أول تحدٍّ نقدمه لك سيتيح حلّه كسر تشفير حقيقي. كثيرون يعرفون أنك حين تشفّر شيئًا بـ ECB يمكنك رؤية البطاريق من خلاله. لكن عددًا أقل منهم يستطيع فك تشفير محتويات تلك النصوص المشفَّرة، والآن أصبحت قادرًا على ذلك. إن كان لتجربتنا قيمة إرشادية، فهذا الهجوم سيمنحك تنفيذ شيفرة في اختبارات الأمان نحو مرة كل سنة.

13. القص واللصق في ECB

اكتب روتينًا لتحليل k=v، كما في كوكيز منظّم. ينبغي أن يأخذ الروتين:

foo=bar&baz=qux&zap=zazzle

... وينتج:

{
  foo: 'bar',
  baz: 'qux',
  zap: 'zazzle'
}

(كما تعرف، الكائن؛ لا يهمني إن حوّلته إلى JSON).

الآن اكتب دالة ترمّز ملفًا شخصيًا للمستخدم (user profile) بهذه الصيغة، بالنظر إلى عنوان بريد إلكتروني. ينبغي أن يكون لديك شيء مثل:

profile_for("foo@bar.com")

... وينبغي أن ينتج:

{
  email: 'foo@bar.com',
  uid: 10,
  role: 'user'
}

... مُرمَّزًا هكذا:

email=foo@bar.com&uid=10&role=user

ينبغي ألا تسمح دالة "profile_for" الخاصة بك بترميز المحارف الوصفية (metacharacters) (& و=). احذفها، أو هجّنها، أو أي شيء تريده، لكن لا تدع الناس يضبطون عنوان بريدهم على "foo@bar.com&role=admin".

الآن، دالتان سهلتان إضافيتان. ولّد مفتاح AES عشوائي، ثم:

  1. شفّر ملف المستخدم المُرمَّز تحت المفتاح؛ «أَتِح» ذلك لـ«المهاجم».
  2. فك تشفير ملف المستخدم المُرمَّز وحلّله.

باستخدام مدخلات المستخدم إلى profile_for() فقط (كأوراكل لتوليد نصوص مشفَّرة «صحيحة») والنصوص المشفَّرة نفسها، أنشئ ملفًا شخصيًا بدور admin.

14. فك تشفير ECB بايتًا بايتًا (أصعب)

خذ دالة الأوراكل من التمرين رقم 12. الآن ولّد عددًا عشوائيًا من البايتات العشوائية وألحق هذه السلسلة بكل نص صريح. أنت الآن تفعل:

AES-128-ECB(random-prefix || attacker-controlled || target-bytes, random-key)

الهدف نفسه: فك تشفير target-bytes.

توقف وفكّر لحظة.

ما الأصعب في هذا مقارنة بالتحدي رقم 12؟ كيف تتغلب على تلك العقبة؟ التلميح هو: أنت تستخدم كل الأدوات التي تملكها بالفعل؛ لا حاجة إلى رياضيات مجنونة.

فكّر في «STIMULUS» و«RESPONSE».

15. التحقق من صحة حشو PKCS#7

اكتب دالة تأخذ نصًا صريحًا، وتحدد ما إذا كان لديه حشو PKCS#7 صحيح، وتزيل الحشو.

السلسلة:

"ICE ICE BABY\x04\x04\x04\x04"

... لديها حشو صحيح، وتنتج النتيجة "ICE ICE BABY".

السلسلة:

"ICE ICE BABY\x05\x05\x05\x05"

... ليس لديها حشو صحيح، وكذلك:

"ICE ICE BABY\x01\x02\x03\x04"

إن كنت تكتب بلغة فيها استثناءات، مثل Python أو Ruby، فاجعل دالتك تطرح استثناءً عند الحشو الفاسد.

يعرف مهووسو التشفير إلى أين نتجه بهذا. تحمّلنا قليلًا.

16. هجمات قلب البتّات في CBC

ولّد مفتاح AES عشوائي.

اجمع شيفرة الحشو وشيفرة CBC لكتابة دالتين.

ينبغي أن تأخذ الدالة الأولى سلسلة إدخال اعتباطية، وتضيف في مقدمتها السلسلة:

"comment1=cooking%20MCs;userdata="

.. وتلحق في نهايتها السلسلة:

";comment2=%20like%20a%20pound%20of%20bacon"

ينبغي أن تهجّن الدالة المحرفين ";" و"=".

ثم ينبغي أن تحشو الدالة الإدخال إلى طول كتلة AES البالغ 16 بايت وتشفّره تحت مفتاح AES العشوائي.

ينبغي أن تفك الدالة الثانية تشفير السلسلة وتبحث عن المحارف ";admin=true;" (أو، على نحو مكافئ، فك التشفير، وتقسيم السلسلة على ";"، وتحويل كل سلسلة ناتجة إلى زوجين، والبحث عن الزوج "admin").

أعِد true أو false بناءً على وجود السلسلة.

إن كنت كتبت الدالة الأولى بشكل صحيح، فلا ينبغي أن يكون ممكنًا تمرير إدخال مستخدم إليها يولّد السلسلة التي تبحث عنها الدالة الثانية. سيتعين علينا كسر التشفير لفعل ذلك.

بدلًا من ذلك، عدّل النص المشفَّر (دون معرفة مفتاح AES) لتحقيق ذلك.

أنت تعتمد على حقيقة أن خطأ بتّة واحدة في كتلة نص مشفَّر في وضع CBC:

  • يخلط محتوى الكتلة التي يحدث فيها الخطأ بالكامل
  • ينتج خطأ (أو تعديلًا) البتّة الواحدة نفسه في كتلة النص المشفَّر التالية.

توقف وفكّر لحظة.

قبل أن تنفّذ هذا الهجوم، أجب عن هذا السؤال: لماذا يمتلك وضع CBC هذه الخاصية؟