Base64 एनकोडर और डिकोडर

दोनों दिशाएँ, Unicode-सुरक्षित, और ज़रूरत पड़ने पर URL-सेफ़ अल्फ़ाबेट।

यह किस काम के लिए है

Base64 एक समस्या हल करता है: आपके पास मनमाने बाइट हैं, और ऐसा रास्ता है जो सिर्फ़ छपने योग्य टेक्स्ट ढोएगा। ईमेल हेडर, JSON के स्ट्रिंग फ़ील्ड, URL, HTML एट्रिब्यूट, और बहुत सारा पुराना ढाँचा — कच्ची बाइनरी पर ये सब लड़खड़ा जाते हैं। Base64 हर तीन बाइट को 64 चिह्नों वाली ऐसी वर्णमाला के चार अक्षरों में बदल देता है जो इन सबसे बचकर निकल आती है।

इसका पूरा मक़सद बस इतना है। यह कंप्रेशन नहीं है — आउटपुट एक-तिहाई बड़ा होता है। यह एन्क्रिप्शन नहीं है — इसमें कोई कुंजी है ही नहीं। यह एक ट्रांसपोर्ट एन्कोडिंग है, और लोग इसे जो भी गुण ग़लती से देते हैं वे सब «मैं इसे पढ़ नहीं सकता» को «यह सुरक्षित है» समझ लेने से आते हैं।

यह आपको कहाँ मिलेगा

  • JWT में। तीनों हिस्से, URL-सेफ़ वर्णमाला में और पैडिंग हटाकर।
  • HTTP बेसिक ऑथ में। Authorization: Basic दरअसल user:password का Base64 ही है — और ठीक इसीलिए सादे HTTP पर बेसिक ऑथ पासवर्ड को खुले में भेजने के बराबर है।
  • डेटा URI में। data:image/png;base64,... किसी फ़ाइल को सीधे HTML या CSS में जड़ देता है।
  • PEM सर्टिफ़िकेट और कुंजियों में। -----BEGIN----- और -----END----- के बीच की पंक्तियाँ Base64 में एन्कोड किया गया DER हैं।
  • ईमेल अटैचमेंट में। SMTP टेक्स्ट प्रोटोकॉल है; MIME जो कुछ टेक्स्ट नहीं है उसे ढोने के लिए Base64 इस्तेमाल करता है।

यूनिकोड का जाल

ब्राउज़र आपको btoa और atob देता है, और वे एक जाल हैं। दोनों स्ट्रिंग के रूप में दर्शाए गए बाइट पर काम करते हैं, इसलिए U+00FF से ऊपर के किसी भी अक्षर पर btoa त्रुटि फेंक देता है। किसी इमोजी पर आज़माइए और आपको 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)URL-सेफ़ (§5)
इंडेक्स 62+-
इंडेक्स 63/_
पैडिंग=, अनिवार्यआम तौर पर हटा दी जाती है
कहाँ इस्तेमालMIME, PEM, बेसिक ऑथJWT, URL, फ़ाइल नाम

मुसीबत पैडिंग से ही होती है। उसका इकलौता काम लंबाई को चार का गुणज बनाना है, और डिकोडर हमेशा हिसाब लगा सकता है कि कितनी कम है — इसलिए URL-सेफ़ रूप उसे हटा देता है। फिर सख़्त डिकोडर नतीजे को ठुकरा देते हैं, और इसीलिए किसी दूसरे टूल में चिपकाया गया JWT हिस्सा इतनी बार नाकाम होता है। यहाँ का डिकोडर पैडिंग ख़ुद वापस जोड़ लेता है।

इसका ग़लत इस्तेमाल कहाँ होता है

चूँकि Base64 एक नज़र में पढ़ा नहीं जाता, इसे राज़ की तरह इस्तेमाल कर लिया जाता है। ख़ास तौर पर इन तरीक़ों का नाम लेना ज़रूरी है:

  • पासवर्ड सहेजने के लिए Base64। यह भेस बदले हुए प्लेनटेक्स्ट है। पासवर्ड को धीमे, एकतरफ़ा हैश चाहिए — bcrypt, scrypt, Argon2।
  • JWT पेलोड के Base64 को निजी मान लेना। पेलोड को टोकन रखने वाला हर कोई पढ़ सकता है, वह उपयोगकर्ता भी जिसे वह जारी हुआ। उसमें कभी ऐसा कुछ मत डालिए जो आप उसे दिखाना न चाहें।
  • फ़िल्टर से डेटा चुपके निकालने के लिए Base64। यह थोड़ी देर काम करता है, और कोई भी ढंग का स्कैनर उसे वैसे भी डिकोड कर लेता है।

तीनों पर लागू होने वाला नियम: अगर किसी चीज़ की सुरक्षा इस पर टिकी है कि पढ़ने वाला उसे डिकोड नहीं करेगा, तो उसमें सुरक्षा है ही नहीं।

संबंधित

JWT डिकोडर टोकन को अलग करके दोनों हिस्से एक ही क़दम में पार्स कर देता है। अगर बात बाइट एन्कोड करने की नहीं बल्कि URL में जाने वाले टेक्स्ट को एस्केप करने की है, तो पर्सेंट-एन्कोडिंग वह अलग चीज़ है जो शायद आपको चाहिए। और अगर डिकोड करने पर JSON निकला, तो फ़ॉर्मैटर उसे सजा देगा।

Base64 से जुड़े सवाल

क्या Base64 एन्क्रिप्शन है?

नहीं, और इसे दो टूक कहना ज़रूरी है क्योंकि यह भ्रम असली हादसे करा चुका है। Base64 एक उलटा जा सकने वाला एन्कोडिंग है — न कोई कुंजी, न कोई राज़। जिसके पास स्ट्रिंग है उसके पास डेटा है, और उसे डिकोड करने में कोड की एक पंक्ति लगती है। यह इसलिए बना है कि बाइट्स ऐसे रास्तों से गुज़र सकें जो सिर्फ़ छपने योग्य टेक्स्ट ढोते हैं, कुछ छिपाने के लिए नहीं। पासवर्ड को Base64 में सहेजना उसे प्लेनटेक्स्ट में सहेजना ही है, बस एक क़दम और जोड़कर।

इतने सारे Base64 टूल इमोजी या मात्रा वाले अक्षरों पर क्यों टूट जाते हैं?

क्योंकि ब्राउज़र का btoa फ़ंक्शन अक्षरों पर नहीं, बाइट्स पर काम करता है और U+00FF से ऊपर किसी भी चीज़ पर त्रुटि फेंक देता है। जो टूल उसे सीधे बुलाते हैं वे é पर, 日本語 पर और हर इमोजी पर नाकाम रहते हैं। यह पेज पहले UTF-8 बाइट्स में एन्कोड करता है और उसी तरह वापस डिकोड करता है, इसलिए जो भी टेक्स्ट आप टाइप कर सकते हैं उसका पूरा चक्कर साफ़ रहता है।

URL-सेफ़ Base64 क्या है?

मानक वर्णमाला में + और / आते हैं, और URL में दोनों के अपने अर्थ हैं; = वाली पैडिंग का भी। RFC 4648 में तय URL-सेफ़ रूप + की जगह - और / की जगह _ रखता है, और आम तौर पर पैडिंग हटा देता है। JWT अपने तीनों हिस्सों में यही इस्तेमाल करता है, इसीलिए किसी मानक डिकोडर में चिपकाया गया JWT हिस्सा अक्सर तब तक नाकाम रहता है जब तक पैडिंग वापस न जोड़ी जाए। यहाँ का डिकोडर दोनों वर्णमालाएँ स्वीकार करता है, पैडिंग के साथ भी और बिना भी।

एन्कोड की गई स्ट्रिंग मेरे डाले हुए से बड़ी क्यों है?

हमेशा लगभग एक-तिहाई बड़ी। Base64 इनपुट के हर तीन बाइट को चार छपने योग्य अक्षरों में दिखाता है, इसलिए पैडिंग से पहले ही आउटपुट मूल का 4/3 हो जाता है। यही क़ीमत है सिर्फ़ उन अक्षरों का इस्तेमाल करने की जो ऐसे सिस्टमों से बचकर निकल आएँ जो मनमाने बाइट नहीं ढो सकते। इसीलिए CSS में तस्वीरों को data: URI के रूप में जड़ देने पर स्टाइलशीट उन तस्वीरों की फ़ाइलों से साफ़ तौर पर बड़ी हो जाती है।

मैंने कुछ डिकोड किया और बेमतलब अक्षर मिले। हुआ क्या?

सबसे संभव यह है कि वे बाइट्स टेक्स्ट हैं ही नहीं। Base64 किसी भी बाइनरी को एन्कोड करता है — कोई तस्वीर, कोई सर्टिफ़िकेट, कोई कंप्रेस्ड आर्काइव — और उन्हें स्ट्रिंग में डिकोड करने पर बकवास ही निकलेगा, क्योंकि वहाँ लौटाने लायक़ कोई टेक्स्ट है ही नहीं। यह पेज आपको टूटे-फूटे अक्षर दिखाने के बजाय यह बात साफ़ कह देता है: यह सख़्त UTF-8 जाँच के साथ डिकोड करता है, इसलिए अमान्य बाइट अनुक्रमों को चुपचाप प्रश्नचिह्नों से बदलने के बजाय बाइनरी बताकर रिपोर्ट किया जाता है।

अंतिम समीक्षा । कुछ पुराना पड़ा हुआ मिला? हमें बताइए.