ترميز وفك ترميز Base64

في الاتجاهين، بدعم كامل لليونيكود، مع أبجدية آمنة للروابط عند الحاجة.

ما الغرض منه

يحلّ Base64 مشكلة واحدة: لديك بايتات عشوائية، وقناة لا تحمل إلا نصاً قابلاً للطباعة. فترويسات البريد، وحقول النصوص في JSON، والروابط، وسمات HTML، وقدر كبير من البنية التحتية القديمة، كلها تنهار أمام البيانات الثنائية الخام. ويُسقط Base64 كل ثلاثة بايتات على أربعة محارف من أبجدية من 64 رمزاً تنجو من ذلك كله.

هذا هو الغرض بأكمله. فهو ليس ضغطاً — إذ يزيد الناتج بمقدار الثلث. وليس تشفيراً — إذ لا مفتاح فيه. إنه ترميز نقل، وكل خاصية ينسبها الناس إليه خطأً إنما تنبع من الخلط بين «لا أستطيع قراءة هذا» و«هذا محمي».

أين ستصادفه

  • رموز JWT. المقاطع الثلاثة جميعاً، بالأبجدية الآمنة في الروابط ومن دون حشو.
  • المصادقة الأساسية في HTTP. فـ Authorization: Basic ليس إلا user:password مرمّزاً بـ Base64 — ولهذا بالضبط تعادل المصادقة الأساسية فوق HTTP العادي إرسالَ كلمة المرور في العلن.
  • روابط البيانات. فـ data:image/png;base64,... تضمّن ملفاً مباشرةً في HTML أو CSS.
  • شهادات PEM ومفاتيحها. فالأسطر بين -----BEGIN----- و-----END----- هي DER مرمّز بـ Base64.
  • مرفقات البريد. فـ SMTP بروتوكول نصي؛ ويستعمل MIME صيغة Base64 لحمل كل ما ليس نصاً.

فخّ اليونيكود

يمنحك المتصفح btoa وatob، وهما فخّ. فكلتاهما تعملان على بايتات ممثَّلة كنص، لذا تُطلق btoa خطأً عند أي محرف فوق U+00FF. جرّبها على رمز تعبيري فتحصل على InvalidCharacterError.

والعلاج هو التحويل إلى بايتات UTF-8 أولاً، وهو ما تفعله هذه الصفحة:

// Encode any string safely
const bytes = new TextEncoder().encode(text);
const b64 = btoa(String.fromCharCode(...bytes));

// Decode back
const binary = atob(b64);
const out = new TextDecoder().decode(
  Uint8Array.from(binary, c => c.charCodeAt(0))
);

وثمة تحفظ على سطر الترميز: فنشر مصفوفة كبيرة داخل String.fromCharCode يفيض بمكدّس الاستدعاء عند نحو مئة ألف عنصر. فمع البيانات الكبيرة كرّر على دفعات — وهذا ما تفعله هذه الصفحة، ولهذا تتعامل مع لصقة بحجم عدة ميغابايتات دون أن تنهار.

الأبجديتان

القياسية (RFC 4648 §4)الآمنة في الروابط (§5)
الموضع 62+-
الموضع 63/_
الحشو=، إلزامييُحذف عادةً
يستعملها‏MIME وPEM والمصادقة الأساسيةرموز JWT والروابط وأسماء الملفات

والحشو هو ما يسبب المتاعب. فمهمته الوحيدة جعل الطول من مضاعفات الأربعة، ويستطيع فاكّ الترميز دائماً حساب المفقود منه — لذا تحذفه الصيغة الآمنة في الروابط. فترفض فاكّات الترميز الصارمة النتيجة عندئذٍ، ولهذا كثيراً ما يفشل مقطع JWT حين يُلصق في أداة أخرى. وفاكّ الترميز هنا يعيد الحشو بنفسه.

أين يُساء استعماله

لأن Base64 غير مقروء للوهلة الأولى، يُستعمل وكأنه سرّ. ويستحق الأمر تسمية الأنماط بعينها:

  • استعمال Base64 لتخزين كلمات المرور. هذا نص صريح في زيّ آخر. فكلمات المرور تحتاج تجزئة بطيئة أحادية الاتجاه — bcrypt أو scrypt أو Argon2.
  • التعامل مع Base64 في حمولة JWT وكأنها خاصة. فالحمولة يقرؤها كل من يحمل الرمز، بمن فيهم المستخدم نفسه. فلا تضع فيها أبداً ما لا تريد أن تريه إياه.
  • استعمال Base64 لتمرير بيانات خلسةً عبر مرشّح. ينجح ذلك لبرهة، وكل فاحص يستحق اسمه يفكّ ترميزه على أي حال.

والقاعدة التي تشمل الثلاثة: إن كان أمن شيء ما يعتمد على ألا يفكّ القارئ ترميزه، فلا أمن فيه أصلاً.

ذات صلة

فاكّ ترميز JWT يقسم الرمز ويحلّل المقطعين في خطوة واحدة. أما إن كان مرادك تهريب نص ذاهب إلى رابط لا ترميز بايتات، فإن الترميز المئوي هو الشيء المختلف الذي تريده على الأرجح. وإن تبيّن أن ما فككته ملف JSON، فسيرتّبه لك المنسّق.

أسئلة حول Base64

هل Base64 تشفير؟

لا، ويستحق الأمر صراحةً لأن هذا الخلط يتسبب في حوادث حقيقية. فـ Base64 ترميز عكسي بلا مفتاح وبلا سر — من يملك النص يملك البيانات، وفك الترميز سطر واحد من الشيفرة. وقد وُجد لنقل البايتات عبر قنوات لا تحمل إلا نصاً قابلاً للطباعة، لا لإخفاء شيء. وتخزين كلمة مرور مرمّزة بـ Base64 هو تخزينها كنص صريح مع خطوة إضافية.

لماذا تتعطل كثير من أدوات Base64 مع الرموز التعبيرية أو الحروف المشكولة؟

لأن دالة btoa في المتصفح تعمل على البايتات لا على المحارف، وتُطلق خطأً عند أي شيء فوق U+00FF. فالأدوات التي تستدعيها مباشرةً تفشل مع é ومع 日本語 ومع كل رمز تعبيري. أما هذه الصفحة فترمّز أولاً إلى بايتات UTF-8 وتفكّ الترميز بالطريقة نفسها، فتكون الرحلة ذهاباً وإياباً نظيفة لأي نص تستطيع كتابته.

ما هو Base64 الآمن في الروابط؟

الأبجدية القياسية تتضمن + و/‎ ولكليهما معنى داخل الرابط، وكذلك حرف الحشو =. والصيغة الآمنة في الروابط المعرَّفة في RFC 4648 تستبدل - بـ + و_ بـ /‎، وتحذف الحشو عادةً. وهي ما تستعمله رموز JWT في مقاطعها الثلاثة، ولهذا كثيراً ما يفشل لصق مقطع من JWT في فاكّ ترميز قياسي حتى يُعاد الحشو. وفاكّ الترميز هنا يقبل الأبجديتين، بحشو أو بغيره.

لماذا النص المرمَّز أكبر مما أدخلت؟

أكبر بنحو الثلث، دائماً. فـ Base64 يمثّل كل ثلاثة بايتات من المدخل بأربعة محارف قابلة للطباعة، فيصير الناتج ‎4/3 الحجم قبل الحشو. هذا ثمن الاقتصار على محارف تنجو من أنظمة لا تستطيع حمل بايتات عشوائية. ولهذا أيضاً يصبح ملف الأنماط أكبر بوضوح من ملفات الصور نفسها حين تُضمَّن الصور في CSS بصيغة data: URI.

فككت ترميز شيء فخرج كلام غير مفهوم. ماذا حدث؟

الأرجح أن هذه البايتات ليست نصاً أصلاً. فـ Base64 يرمّز بيانات ثنائية عشوائية — صورة أو شهادة أو أرشيفاً مضغوطاً — وتحويلها إلى نص ينتج هراءً لأنه لا نص هناك ليُستعاد. وهذه الصفحة تقول لك ذلك صراحةً بدل أن تعرض عليك محارف مشوّهة: فهي تفكّ الترميز بتحقق صارم من UTF-8، فتُبلَّغ تسلسلات البايتات غير الصالحة على أنها بيانات ثنائية بدل أن تُستبدل بعلامات استفهام في صمت.

آخر مراجعة . وجدت شيئاً لم يعد محدَّثاً؟ أخبِرنا.