क्या आपको Base64 फ़ॉर्मेट के साथ काम करना होता है? तो यह साइट आपके लिए एकदम सही है! अपने डेटा को एन्कोड या डिकोड करने के लिए, हमारे बेहद आसान ऑनलाइन टूल का इस्तेमाल करें।

JavaScript/Node.js में Base64 डिकोडिंग: एक सम्पूर्ण गाइड

आपके ऐप्लिकेशन को एक Base64 स्ट्रिंग मिली है। यह किसी आने वाली रिक्वेस्ट का Authorization हेडर हो, JSON पेलोड के अंदर कोई फ़ील्ड, data URL में छिपा कोई इमेज, या किसी कॉन्फ़िग फ़ाइल में चिपकाया गया कोई सर्टिफिकेट हो - इनमें से कुछ भी हो सकता है। और ये सब एक ही चीज़ हैं: ASCII कोस्ट्यूम पहने हुए राव बाइट्स। यह लेख JavaScript और Node.js में उसी कोस्ट्यूम को उतारने के बारे में है, और उसे उतारते वक़्त एक भी बाइट न खोने के बारे में।

फ़ॉर्मेट के बारे में एक जल्दी बात: Base64 एक टेक्स्ट एन्कोडिंग है जो हर तीन इनपुट बाइट्स को चार प्रिंटेबल चरों पर मैप करती है। इस साइट का होम पेज वर्णमाला, गणित और पैडिंग की पूरी-पूरी विस्तार से व्याख्या करता है, इसलिए यहाँ यह बात एक वाक्य तक ही रहती है। एक नतीजा जेब में रखने लायक़ है: एन्कोडेड डेटा उन बाइट्स से लगभग 33 फ़ीसदी बड़ा होता है जिन्हें वह साथ रखता है, यानी डिकोडिंग एक सिकुड़ने वाली प्रक्रिया है, और इस लेख में कोई भी बात कोई भी रहस्य न बढ़ाती है न घटाती है। आप पैकेट खोल रहे हैं, सील नहीं तोड़ रहे।

अच्छी खबर: आपको कुछ इंस्टॉल नहीं करना। ब्राउज़र दो दशकों से atob() साथ में दे रहे हैं, Node.js में Buffer क्लास है जिसमें बिल्ट-इन base64 मोड है, और आधुनिक रनटाइम अब Uint8Array.fromBase64() भी देते हैं - ES2026 स्पेसिफिकेशन से आया एक सख़्त और कॉन्फ़िगर करने लायक़ नया चेहरा। कला इसमें है कि काम के लिए सही टूल चुना जाए, और यही ठीक-ठीक जाना जाए कि हर टूल क्या-क्या माफ़ करता है, क्योंकि सर्वर पर आप अजनबियों का डेटा डिकोड करते हैं, और चीज़ें टेढ़ी वही जगह जाती हैं जहाँ माफ़ी दी जाती है।

डिकोडर कैसे चुनें

डिकोडिंग के काम का बड़ा हिस्सा सिर्फ़ तीन APIs ही संभाल लेते हैं। इनका मिज़ाज अलग-अलग है, और पूरा मामला यही फ़र्क है:

डिकोडर कहाँ उपलब्ध मिज़ाज
Buffer.from(string, 'base64') Node.js (हर वर्ज़न जो मायने रखता है) मदारी: अज्ञात चर छुड़ जाता है, पहला = मिलते ही रुक जाती है, कभी त्रुटि नहीं फेंकती
atob(string) सारे ब्राउज़र, Node.js 16 और उससे आगे सख़्त: गलत इनपुट पर InvalidCharacterError फेंकती है, ASCII खाली जगह छुड़ जाती है, पैडिंग की कमी माफ़ कर देती है
Uint8Array.fromBase64(string) Chrome 140+, Firefox 133+, Safari 18.2+, Node.js 25+ कॉन्फ़िगर करने लायक़: वर्णमाला आप चुनते हैं, और आख़िरी चंक कितना सख़्त होना चाहिए यह भी आप तय करते हैं

तीनों वही क्लासिक पेलोड बिल्कुल एक ही तरीके से खोलते हैं:

// Node.js का वर्कहॉर्स
const { Buffer } = require('node:buffer');
console.log(Buffer.from('aGVsbG8gd29ybGQ=', 'base64').toString('utf8')); // "hello world"
// पुराना जोड़ी (हर ब्राउज़र, Node.js 16+)
console.log(atob('aGVsbG8gd29ybGQ=')); // "hello world", बिनरी स्ट्रिंग के रूप में
// आधुनिक ES2026 तरीका (Chrome 140+, Node.js 25+)
console.log(new TextDecoder().decode(Uint8Array.fromBase64('aGVsbG8gd29ybGQ='))); // "hello world"

atob() पर भरोसा करने से पहले एक चेतावनी: यह स्ट्रिंग लौटाता है, लेकिन बिनरी स्ट्रिंग, जिसमें हर चर एक राव बाइट को 0 से 255 के कोड पॉइंट के तौर पर साथ रखता है। इसे प्रिंट करना कोई बड़ी बात नहीं। लेकिन उसे JSON, डेटाबेस या कुकी में सहेजने पर वे राव बाइट वैल्यूज़ भी साथ-साथ चल पड़ती हैं, इसलिए डिकोड होते ही उसे असली बाइट्स या असली टेक्स्ट में बदल दीजिए।

मदारी डिकोडर और वह क्या-क्या निगल जाता है

Node का Buffer एक माफ़ी देने वाला पाठक है, और यह एक दोधारी तलवार है। ऐसे डेटा के लिए यह अद्भुत है जिसने कठिन सफ़र किए: लाइन ब्रेक्स वाला MIME ईमेल, हाथ से कॉपी की गई स्ट्रिंग्स, बख्खरे हुए स्पेस वाला लॉग आउटपुट। परंतु ऐसी डेटा के लिए यह खतरनाक है जिसे आपने खुद नहीं बनाया, क्योंकि यह कभी शिकायत नहीं करता। यहाँ वाक़ई क्या होता है:

इनपुट Buffer.from(input, 'base64') क्या करता है
'!!!' खाली Buffer लौटता है। सारा कचरा छुड़ जाता है, कुछ भी डिकोड नहीं होता, कोई त्रुटि नहीं।
'aGVsbG8== garbage' "hello" लौटता है। पहला = डिकोडिंग खत्म कर देता है; बाकी नज़रअंदाज़ हो जाता है।
'aG!VsbG8' "hello" लौटता है। '!' छुड़ जाता है, त्रुटि नहीं।
'aGVs=bG8' "hel" लौटता है। स्ट्रिंग के बीच का = नाटक जल्दी ही रोक देता है।
'aGVsbG8====' "hello" लौटता है। अतिरिक्त आख़िरी पैडिंग नज़रअंदाज़ हो जाती है।
'=aGVsbG8' खाली Buffer लौटता है। डेटा से पहले की पैडिंग का कोई मतलब नहीं।

विश्वसनीय नहीं माने जाने वाले इनपुट के लिए जवाब एक वेलिडेटर है, और Base64 का व्याकरण इतना छोटा है कि वह एक ही रेगुलर एक्सप्रेशन में समा जाता है:

const STRICT = /^([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==)$/;
function decodeStrict (base64) {
  if (!STRICT.test(base64)) {
    throw new TypeError('Not a valid base64 string');
  }
  return Buffer.from(base64, 'base64');
}
console.log(decodeStrict('aGVsbG8gd29ybGQ=').toString('utf8')); // "hello world"
try {
  decodeStrict('aGVs!bG8');
} catch (error) {
  console.log(error.message); // "Not a valid base64 string"
}

यह रेगुलर एक्सप्रेशन आकृति की जाँच करता है: चार-चार के ग्रुप्स और सही पैडिंग। एक ऐसा नियम है जो वह जाँच नहीं सकता, वह है RFC 4648 का कैनोनिकल-एन्कोडिंग नियम, जो कहता है कि आख़िरी ग्रुप के प्रयोग न होने वाले पैड बिट्स शून्य होने चाहिए। Uint8Array.fromBase64() का सख़्त मोड यह जाँच करता है, इसलिए Node.js 25 या किसी भी आधुनिक ब्राउज़र पर आप रेगुलर एक्सप्रेशन को बिल्कुल छोड़ सकते हैं और प्लेटफ़ॉर्म को ही ऑडिट करने दीजिए:

console.log(new TextDecoder().decode(Uint8Array.fromBase64('aGVsbG8'))); // "hello", loose मोड पैडिंग की कमी माफ़ कर देता है
try {
  Uint8Array.fromBase64('QQB=', { lastChunkHandling: 'strict' });
} catch (error) {
  console.log(error.name); // "SyntaxError", पैड बिट्स शून्य नहीं हैं
}

lastChunkHandling ऑप्शन में तीन सेटिंग्स हैं जो जानने लायक़ हैं। "loose" (डिफ़ॉल्ट) खाली जगह छुड़ जाता है, पैडिंग की कमी मान लेता है और बचे हुए पैड बिट्स को नज़रअंदाज़ करता है। "strict" एक पूरा, पैड किया हुआ आख़िरी ग्रुप मांगता है जिसमें सभी पैड बिट्स शून्य हों। और "stop-before-partial" सिर्फ़ पूरे चार-चर ग्रुप्स ही डिकोड करता है और आख़िरी टुकड़ा आपके लिए छोड़ देता है जिसे आपको आगे बढ़ाना होता है - यही वह हिस्सा है जो स्ट्रीमिंग डिकोडिंग को सुगम बनाता है, जैसा आप इस लेख के आगे देखेंगे।

राव बाइट्स से टेक्स्ट तक: चरसेट का फैसला

Base64 की डिकोडिंग आपको बाइट्स थमाती है। बाइट्स टेक्स्ट तभी बनेंगे जब आप एक चरसेट चुनें, और वह चुनाव आपका ही करना है, आमतौर पर इस बात पर निर्भर करते हुए कि भेजने वाले ने क्या वादा किया। Node का डिफ़ॉल्ट वही है जो आपको ज़्यादातर वक़्त चाहिए होता है:

const { Buffer } = require('node:buffer');
const bytes = Buffer.from('w6k=', 'base64'); // दो बाइट्स C3 A9
console.log(bytes.toString('utf8'));   // "é", दो बाइट्स मिलकर एक चर बनते हैं
console.log(bytes.toString('latin1')); // "é", वही बाइट्स एक-एक चर के रूप में पढ़े गए

UTF-8 के साथ एक झिझक है: जब कोई बाइट क्रम वैध UTF-8 नहीं होता, तो Node त्रुटि नहीं फेंकता। वह Unicode रिपलेसमेंट चर (U+FFFD, जिसमें प्रश्न चिह्न वाला हीरा है) लगा देता है और आगे बढ़ जाता है, यानी खराब पेलोड आपके पाइपलाइन से होकर आपके डेटाबेस तक आराम से पहुँच सकता है। प्लेटफ़ॉर्म का असली टेक्स्ट डिकोडर, TextDecoder (Node.js और हर ब्राउज़र में एक ग्लोबल), fatal ऑप्शन देता है जो खराबी को एक ऐसे TypeError में बदल देता है जो आप कैच कर सकते हैं:

const stray = new Uint8Array([0xe9]); // एक अकेला बाइट, वैध UTF-8 नहीं
console.log(new TextDecoder().decode(stray)); // रिपलेसमेंट चर, कोई त्रुटि नहीं
try {
  new TextDecoder('utf-8', { fatal: true }).decode(stray);
} catch (error) {
  console.log(error.name); // "TypeError"
}

पुराने सिस्टम कभी नहीं मरते, और TextDecoder उन्हें पढ़ना अभी भी जानता है। यह WHATWG Encoding Standard की पूरी लेबल टेबल मान लेता है, इसलिए 1990 के दशक के किसी Windows ऐप, किसी जपानी मेनफ्रेम या पुराने FTP मिरर से आया Base64 पेलोड अभी भी 'windows-1250', 'shift_jis', 'euc-kr' या 'gb18030' जैसे लेबल से डिकोड किया जा सकता है, सभी केस-इंसंसीटिव। एक लेबल के लिए चेतावनी ज़रूरी है, क्योंकि उसने असली डीबगिंग का वक़्त खाया है: स्पेसिफिकेशन 'iso-8859-1', 'latin1', और तो 'us-ascii' को Windows-1252 डिकोडर का एलियस बना देता है। बाइट 0x80, जो असली Latin-1 में एक कंट्रोल चर है, यूरो चिह्न के रूप में बाहर आता है:

console.log(new TextDecoder('iso-8859-1').decode(new Uint8Array([0x80]))); // "€", वह Latin-1 नहीं जो आपने माँगा था
// असली बाइट-दर-बाइट Latin-1 पढ़ाई के लिए Buffer वाले पक्ष का इस्तेमाल करें:
console.log(Buffer.from('gA==', 'base64').toString('latin1')); // राव 0x80 कंट्रोल चर

अगर आपको वाक़ई वह राव मैपिंग चाहिए, तो Buffer की 'latin1' एन्कोडिंग (जिसकी पुरानी एलियस 'binary', Node दस्तावेज़ीकरण की अपनी भाषा में, एक बहुत भटकाव देने वाला नाम है) बाइट N को कोड पॉइंट N पर मैप करती है, बिना Windows के मोड़ के। हर आधुनिक चीज़ के लिए, UTF-8 और fatal: true ही सुरक्षित जोड़ी है।

JWT खोलना

एक JavaScript सर्विस द्वारा डिकोड किए जाने वाला सबसे आम Base64 पेलोड JSON Web Token है, वह xxxxx.yyyyy.zzzzz स्ट्रिंग जो आधे वेब के Authorization हेडर में सवार रहती है। RFC 7515 के अनुसार, कम्पैक्ट JWS तीन डॉट से अलग किए हुए हिस्सों का बना होता है, और पहले दो JSON ऑब्जेक्ट्स हैं जिन्हें बिना पैडिंग के base64url से एन्कोड किया गया है। Node.js में इन्हें पढ़ने में कोई शर्त-शरायत नहीं, क्योंकि base64url मोड एक फर्स्ट-क्लास एन्कोडिंग है:

const { Buffer } = require('node:buffer');
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQWRhIn0.JMjpmDdNzQZpTuUO1H33GJsj7nWhBu-qxkPD0GL2uaA';
const [head, body, signature] = token.split('.');
console.log(JSON.parse(Buffer.from(head, 'base64url').toString('utf8'))); // { alg: 'HS256', typ: 'JWT' }
console.log(JSON.parse(Buffer.from(body, 'base64url').toString('utf8'))); // { sub: '123', name: 'Ada' }

इतना कई बार कहा जा चुका है, फिर भी दोहराने का है: डिकोडिंग सत्यापन नहीं है। हेडर और पेलोड सिर्फ़ सजाए हुए हैं, एन्क्रिप्ट नहीं, और टोकन जो भी पकड़े हुए है वह दोनों पढ़ सकता है। जिस हिस्से की जाँच आपको ज़रूर करनी है वह तीसरा है, सिग्नेचर। एक क्लासिक HMAC-SHA256 टोकन के लिए पूरी जाँच बिल्ट-इन crypto मोड्यूल की कुछ ही लाइनों में हो जाती है, और एक ही सूक्ष्म हिस्सा timingSafeEqual से तुलना करना है, ताकि कोई अटैकर आपके बाइट-दर-बाइट तुलना का समय नाप न सके:

const crypto = require('node:crypto');
const expected = crypto.createHmac('sha256', 'topsecret').update(head + '.' + body).digest();
const actual = Buffer.from(signature, 'base64url');
console.log(crypto.timingSafeEqual(expected, actual)); // true
console.log(crypto.timingSafeEqual(crypto.createHmac('sha256', 'wrong-secret').update(head + '.' + body).digest(), actual)); // false

एक असली सर्विस में आप आमतौर पर यह खुद हाथ से नहीं लिखेंगे। jose पैकेज (सिफ़र डिपेंडेंसी, Node.js, ब्राउज़र और एज रनटाइम्स में चलता है) और लंबे समय से मौजूद jsonwebtoken पैकेज (Node.js) यह नाच संभाल लेते हैं, RSA और ECDSA एल्गोरिथम परिवार संभालते हैं, और exp, aud और iss क्लेम्स लागू करते हैं। जो भी लाइब्रेरी चुनिए, नीचे की Base64 पाइपिंग वही दो कॉल हैं जो आपने अभी देखीं।

HTTP: हेडर, क्वेरी स्ट्रिंग और कुकीज़

वायर के तीन कोने Base64 से भरपूर हैं। सबसे पुराना है HTTP Basic ऑथेंटिकेशन, जो RFC 7617 में परिभाषित है: क्लाइंट Authorization: Basic भेजता है, साथ में user-id:password का Base64। सर्वर पर यह बस एक टुकड़ा काटना और एक बार डिकोड करना है, इसके साथ प्रोटोकॉल का एक छोटा सा नियम: सिर्फ़ पहली कोलन ही यूज़रनेम और पासवर्ड को अलग करती है, इसलिए पासवर्ड में क़ानूनी रूप से और कोलन हो सकती हैं, परंतु यूज़रनेम में नहीं:

const { Buffer } = require('node:buffer');
const header = 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==';
const credentials = Buffer.from(header.slice(6), 'base64').toString('utf8');
const [user, ...rest] = credentials.split(':');
console.log(user, rest.join(':')); // "Aladdin" "open sesame"

और याद रखिए कि Basic ऑथेंटिकेशन वाक़ई क्या है: छिपापन, सुरक्षा नहीं। क्रेडेंशियल्स वायर पर कोस्ट्यूम में पार होते हैं, यही वजह है कि यह विधि सिर्फ़ HTTPS पर ही स्वीकार्य है। दूसरा कोना है क्वेरी स्ट्रिंग, और उसमें Base64 के इलाक़े की सबसे घिनौनी लैंडमाइन छिपी है:

const params = new URLSearchParams('token=aGVs+bG8=');
console.log(params.get('token')); // "aGVs bG8=", + स्पेस बन गया

आपका Base64 अपने आप खराब नहीं हुआ। URL लेयर ने फ़ॉर्म-एन्कोडिंग नियमों की ओर से, बहुत सुलू-तरीके से, यह किया, जो + को स्पेस मानते हैं। यही वजह है कि क्वेरी स्ट्रिंग में रहने वाले टोकन URL-सुरक्षित वर्णमाला का इस्तेमाल करते हैं, जिसका कवर नीचे के सेक्शन में है। तीसरा कोना है कुकी: कुकीज़ सिर्फ़ ASCII की होती हैं, इसलिए उसमें सहेजा गया कोई भी नॉन-ASCII मान लगभग निश्चित रूप से Base64 है, और JSON ब्लॉब को Base64 करके कुकी में डालने वाला पुराना पैटर्न हैरत-अंगेज़ क़दर से ज़्यादा प्रोडक्शन सिस्टम में ज़िंदा है। डिकोड वही है जो आप पहले से जानते हैं; सिर्फ़ पहले आकृति की जाँच कर लीजिए, क्योंकि कुकी ऐसी जगह है जहाँ यूज़र, या ब्राउज़र एक्सटेंशन, आपको कचरा थमा सकता है।

फ़ाइलें, इमेज और Data URLs

Node का फ़ाइल सिस्टम Base64 को सीधे ही बोलता है, इसलिए पूरी फ़ाइल एक ही लाइन में JSON की सीमा पार कर सकती है:

const fs = require('node:fs');
const base64 = fs.readFileSync('./photo.png', 'base64');
console.log(base64.length); // फ़ाइल, लगभग 33 फ़ीसदी भारी
const bytes = Buffer.from(base64, 'base64');
fs.writeFileSync('./photo.copy.png', bytes);

दूसरा फ़ाइल-आकार का पेलोड है data URL, वह data:image/png;base64,... स्ट्रिंग जिसे फ्रंट-एंड इनलाइन इमेज के लिए बहुत पसंद करते हैं। हर रनटाइम में रेसिपी वही है: पहले कॉमा पर काटें, उसके पहले की मेटाडेटा पार्स करें, बाकी का डिकोड करें। यहाँ एक असली एक-पिक्सेल PNG दोबारा ज़िंदा होता हुआ:

const dataUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const comma = dataUrl.indexOf(',');
const meta = dataUrl.slice(5, comma);
const bytes = Buffer.from(dataUrl.slice(comma + 1), 'base64');
console.log(meta); // "image/png;base64"
console.log(bytes.subarray(0, 8).toString('hex')); // "89504e470d0a1a0a", PNG सिग्नेचर

सिग्नेचर की जाँच एक सस्ती आदत है। PNG के पहले आठ बाइट्स हमेशा 89 50 4E 47 0D 0A 1A 0A होते हैं, और JPEG FF D8 FF से शुरू होती है। अगर क्लाइंट से आया "base64 इमेज" उसी मैजिक बाइट्स से शुरू नहीं होता जो वह वादा करता है, तो उसे किसी महँगे काम में लगाने से पहले ही आप समझ जाते हैं।

URL-सुरक्षित Base64: टोकन के लिए वर्णमाला

क्लासिक Base64 अपने दो ख़ास चरों के रूप में + और / इस्तेमाल करता है (RFC 4648, सेक्शन 4), और दोनों ही URLs में ख़तरा हैं: फ़ॉर्म-डिकोडिंग के दौरान + स्पेस बन जाता है, और / पथ सेपरेटर है। सेक्शन 5 की URL- और फ़ाइल-नाम-सुरक्षित वैरिएंट, जिसे सब base64url कहते हैं, इन्हें - और _ से बदल देती है, और जब संदर्भ से लंबाई पता हो तो आख़िरी = पैडिंग को बिल्कुल छोड़ भी सकती है। यही बिल्कुल वह कॉम्बिनेशन है जो JWTs, OAuth टोकन और डीप लिंक्स को चाहिए, इसलिए base64url वह वर्णमाला है जो बाहर की दुनिया में आपको सबसे ज़्यादा मिलेगी।

Node का Buffer इस पूरे मामले को बेमोहर बना देता है। दोनों ही 'base64' और 'base64url' डिकोडिंग मोड्स चारों ख़ास चरों को स्वीकार करते हैं और इन्हें वही मान पर मैप करते हैं, इसलिए JWT का हिस्सा, OAuth टोकन, और क्लासिक Base64 ब्लॉब तीनों बिना किसी चर-बदलने की शरायत के डिकोड हो जाते हैं:

const { Buffer } = require('node:buffer');
console.log(Buffer.from('aGVs-bG8', 'base64').toString('hex'));    // "68656cf9b1bc"
console.log(Buffer.from('aGVs+bG8', 'base64url').toString('hex')); // "68656cf9b1bc", वही बिल्कुल छह बाइट्स

ES2026 API जानबूझकर चुटकी लेता है, और एक्सप्लिसिट डायल के साथ वही लचीलापन देता है। alphabet ऑप्शन "base64" (डिफ़ॉल्ट, + और /) और "base64url" (- और _) के बीच चयन करता है, और ग़लत वर्णमाला का चर डालने पर SyntaxError आता है, चुपचाप क्रॉस-अल्फाबेट डिकोड नहीं:

console.log(Uint8Array.fromBase64('aGVs-bG8', { alphabet: 'base64url' }).length); // 6
try {
  Uint8Array.fromBase64('aGVs-bG8'); // डिफ़ॉल्ट वर्णमाला क्लासिक वाली है
} catch (error) {
  console.log(error.name); // "SyntaxError", - क्लासिक चर नहीं है
}

ऐसे ब्राउज़र में जहाँ अभी नए मेथड्स नहीं हैं, देतूर एक छोटी सी बदली है - स्ट्रिंग को atob() को सौंपने से पहले, जो सिर्फ़ क्लासिक वर्णमाला जानता है। अगर भेजने वाले ने पैडिंग छोड़ दी हो, जो टोकन-स्टाइल पेलोड्स में आम बात है, तो उसे वापस जोड़ना भी पड़ता है:

function decodeBase64Url (value) {
  const classic = value.replace(/-/g, '+').replace(/_/g, '/');
  const padded = classic + '='.repeat((4 - (classic.length % 4)) % 4);
  const binary = atob(padded);
  const bytes = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i++) {
    bytes[i] = binary.charCodeAt(i);
  }
  return bytes;
}
console.log(new TextDecoder().decode(decodeBase64Url('aGVsbG8gd29ybGQ'))); // "hello world"

Base64 बाहर की दुनिया में: पेलोड कहाँ छुपते हैं

JavaScript की दुनिया में Base64 बाइट्स की डाक सेवा है। यह कहाँ-कहाँ सामने आता है, एक भ्रमण - हर ठहराव के लिए डिकोड की रेसिपी के साथ:

  • JSON API फ़ील्ड्स, ज़्यादातर मामलों में सबसे आम वाहन: अवाटार्स, थंबनेल्स, जेनेरेटेड डॉक्यूमेंट्स और अपलोड्स साधारण JSON के अंदर Base64 स्ट्रिंग्स के रूप में आते हैं, क्योंकि JSON में "ये बाइट्स हैं" कहने का कोई शब्द ही नहीं है। इस फ़ील्ड के साथ कुछ और करने से पहले उसे डिकोड कर लीजिए।
  • एनवायरनमेंट वैरिएबल्स और कॉन्फ़िग फ़ाइलें: कई सीक्रेट मैनेजर्स, CI सिस्टम, और npm CLI खुद आपको Base64 ब्लॉब्स थमाते हैं (पुरानी npm वर्ज़न रजिस्ट्री क्रेडेंशियल को .npmrc में user:password का Base64 बनाकर रखती थी; आधुनिक npm _authToken में राव बियरर टोकन लिखती है)। स्टार्टअप पर एक बार डिकोड करें, और प्लेनटेक्स्ट को मेमोरी में उतनी देर ही रखें जितनी ज़रूरत हो।
  • Kubernetes और क्लस्टर टूलिंग: k8s सीक्रेट्स को API में और etcd में Base64-एन्कोडेड रखना तो बिल्कुल मशहूर बात है, और आधिकारिक दस्तावेज़ बार-बार दोहराते हैं कि यह एन्कोडिंग है, एन्क्रिप्शन नहीं। आपका डिकोड कोड उस परिणाम को सीक्रेट के तौर पर ही माने, सुरक्षा के प्रमाण के तौर पर नहीं।
  • डेटाबेस: JSON कॉलम (Postgres jsonb, MongoDB डॉक्यूमेंट्स, Redis) में सहेजे हुए किसी भी बाइनरी चीज़ का Base64 स्ट्रिंग होना बहुत आम है। रीड पथ पर उसे Buffer या Uint8Array में डिकोड करें, और डेटाबेस को सिर्फ़ टेक्स्ट की ही रहने दीजिए।
  • ईमेल: 76-चर लाइन रैप वाला MIME Base64 वही तरीका है जिससे अटैचमेंट्स और बाइनरी हेडर्स SMTP पार करते हैं - एक ऐसा प्रोटोकॉल जो शुरू में सिर्फ़ 7-bit का था। Node का डिकोडर लाइन ब्रेक्स आपके लिए छुड़ जाता है, इसलिए पूरा बॉडी एक ही कॉल में, बिना किसी क्लीनअप के, डिकोड हो जाता है।
  • CI और CD पाइपलाइन्स: बिल्ड सिस्टम्स और सीक्रेट इंजेक्टर टोकन को Base64 एनवायरनमेंट वैल्यूज़ के रूप में भेजते हैं; पाइपलाइन स्क्रिप्ट में डिकोड करें, और कभी भी डिकोड किया हुआ मान लॉग में इको न करें।
  • डिरेक्टरी और SAML डेटा: LDIF फ़ाइलें बाइनरी एट्रिब्यूट्स (सोचिए: सर्टिफिकेट्स) को Base64 में सहेजती हैं, और SAML रिस्पॉन्सेज अक्सर deflate करके, फिर HTTP की सीमा पार करने से पहले Base64-एन्कोड किए जाते हैं।
  • वर्कर थ्रेड्स और एज रन्टाइम्स: Base64 स्ट्रिंग्स worker_threads की सीमा को साधारण स्ट्रक्चर्ड-क्लोनेबल स्ट्रिंग्स के रूप में पार करती हैं, इसलिए भारी डिकोड किसी वर्कर पर रह सकता है, जबकि मुख्य थ्रेड का इवेंट लूप खाली रहता है।

उनमें से दो ठहरावों को नज़दीक से देखने का हक़ है, क्योंकि ये इंटरव्यूज़ में भी और प्रोडक्शन में भी सामने आते हैं:

const { Buffer } = require('node:buffer');
// एनवायरनमेंट वैरिएबल: सीक्रेट Base64-एन्कोडेड रूप में आता है
const token = Buffer.from(process.env.REGISTRY_TOKEN_B64, 'base64').toString('utf8');
// JSON API फ़ील्ड: कुछ और करने से पहले खोलें
const body = { attachment: 'iVBORw0KGgo...' };
const imageBytes = Buffer.from(body.attachment, 'base64');
console.log(imageBytes.subarray(0, 4).toString('hex')); // "89504e47", PNG सिग्नेचर फिर से
// MIME ईमेल: लाइन ब्रेक्स छुड़ जाते हैं, क्लीनाप की ज़रूरत नहीं
const mimeBody = 'SGVsbG8sIHdyYXBw\nZWQgYmFzZTY0IQ==';
console.log(Buffer.from(mimeBody, 'base64').toString('utf8')); // "Hello, wrapped base64!"

इस भ्रमण में पहचानने वाला उल्टा पैटर्न हर जगह वही है: वहाँ जहाँ राव बाइट्स पहले से ही मंज़ूर थे, वहाँ Base64। WebSocket फ्रेम, फाइल स्ट्रीम्स, Postgres bytea कॉलम - ये सब बाइट्स को जन्मजात संभालते हैं, इसलिए वहाँ Base64 राउंड ट्रिप शुद्ध अतिरिक्त बोझ है, 33 फ़ीसदी की साइज़ टैक्स जिसका कोई फ़ायदा नज़र नहीं आता। जहाँ नेटिव बाइनरी पथ मौजूद है, वही लीजिए।

टुकड़ों में डिकोडिंग: स्ट्रीम्स और बड़ा डेटा

Base64 के चार-चर ग्रुप्स तीन बाइट्स एन्कोड करते हैं, इसलिए चंक्स की एक स्ट्रीम ग्रुप को बीच में तोड़ सकती है। हर चंक डिकोड करके बस दुआ माँगने वाली नाइव अप्रोच रैंडम बाउंडरी पर आउटपुट खराब कर देती है। ES2026 API बिल्कुल इसी के लिए बना है: setFromBase64() एक पहले से एलोकेटेड अर्रे में लिखता है और बताता है कि इनपुट के कितने चर खपे, और "stop-before-partial" मोड आख़िरी पूरे ग्रुप पर ही रुकता है, टुकड़ा अगले चंक के लिए छोड़ देता है। यह पैटर्न TextDecoder के स्ट्रीम API की प्रतिकृति है:

const { Buffer } = require('node:buffer');
const chunks = ['aGVsbG8', 'gd29ybGQ='];
let leftover = '';
const parts = [];
for (const chunk of chunks) {
  const pending = leftover + chunk;
  const space = new Uint8Array(Math.ceil(pending.length * 3 / 4));
  const { read, written } = space.setFromBase64(pending, { lastChunkHandling: 'stop-before-partial' });
  parts.push(Buffer.from(space.buffer, space.byteOffset, written));
  leftover = pending.slice(read);
}
parts.push(Buffer.from(Uint8Array.fromBase64(leftover)));
console.log(Buffer.concat(parts).toString('utf8')); // "hello world"

ऐसे रनटाइम्स में जिनमें नए मेथड्स नहीं हैं (और Node की LTS लाइन को भी थोड़े वक़्त तक वे नहीं मिले), वही लूप एक छोटे यूजरलैंड डिकोडर के साथ काम करता है जो पार्शियल ग्रुप को ट्रैक करता है, या आप सिर्फ़ आने वाले चंक्स को बफ़र कर लेते हैं, जब तक कि आप ग्रुप बाउंडरी पर स्प्लिट कर न सकें। ज़रूरी बात कैरी है: कभी भी किसी टुकड़े को अकेला डिकोड न करें।

बड़े पेलोड्स दो और सीमाएँ आपके ध्यान में लाते हैं। पहला, स्ट्रिंग खुद: Node का Buffer.constants.MAX_STRING_LENGTH 536870888 चर है, लगभग 512 MiB का टेक्स्ट, जो लगभग 400 MB के बाइट्स में डिकोड होता है। उससे बड़ा "base64 फ़ाइल" स्ट्रीमिंग अप्रोच माँगता है, एक-एक readFileSync नहीं। दूसरा, मेमोरी: एन्कोडेड स्ट्रिंग JavaScript हेप में UTF-16 के रूप में रहती है, हर चर के लिए दो हेप बाइट्स, और डिकोड किया हुआ Buffer डेटा की दूसरी पूरी कॉपी है। बड़े पेलोड्स पर आप दोनों को एक पल भर के लिए साथ रखे रहते हैं, इसलिए एन्कोडेड रूप को उतनी देर ही रिफ़रेंस रखिए जितना कोड अनुमति दे, और फ़ाइल-आकार की किसी भी चीज़ के लिए स्ट्रीम्स को ही पसंद कीजिए।

टर्मिनल से

Node एक बिल्कुल काफ़ी कमांड-लाइन Base64 डिकोडर के तौर पर भी काम करता है, और यह तब काम आता है जब आप किसी रिक्वेस्ट को डिबग कर रहे हों या किसी कॉन्फ़िग मान की जाँच कर रहे हों:

# अर्ग्यूमेंट के रूप में पेश की गई क्लासिक Base64 स्ट्रिंग को डिकोड करें
node -e 'console.log(Buffer.from(process.argv[1], "base64").toString("utf8"))' "aGVsbG8gd29ybGQ="
# URL-सुरक्षित वैरिएंट, पैडिंग वैकल्पिक
node -e 'console.log(Buffer.from(process.argv[1], "base64url").toString("utf8"))' "aGVsbG8gd29ybGQ"
# stdin से डिकोड करें, पाइप्स के लिए ही तो बने हैं
echo -n "aGVsbG8gd29ybGQ=" | node -e 'let d="";process.stdin.on("data",c=>d+=c).on("end",()=>console.log(Buffer.from(d.trim(),"base64").toString("utf8")))'

तीनों hello world प्रिंट करते हैं। अगर मशीन पर coreutils का पुराना base64 कमांड भी है, तो base64 -d वही काम करता है, परंतु Node की वर्ज़न base64url जानती हैं, जो पुराने टूल को नहीं।

JavaScript लहज़े वाले फँदे

इनमें से हर एक किसी की खोई हुई दोपहर रहा है, JavaScript या Node.js में:

  • खामोश डिकोडर: Buffer.from('!!!', 'base64') खाली Buffer लौटता है, त्रुटि नहीं। आधा खराब इनपुट आधा खराब डेटा बनकर डिकोड होता है, बिना किसी चेतावनी के। विश्वसनीय न माने जाने वाले इनपुट को सख़्त रेगुलर एक्सप्रेशन (या सख़्त fromBase64 मोड) से वैलिडेट करें, और ग़ैर-खाली स्ट्रिंग से आया खाली Buffer को लाल छँडा मानिए।
  • गुम एन्कोडिंग अर्गयूमेंट: दूसरा अर्गयूमेंट न होने पर Buffer.from('aGVsbG8=') कुछ भी डिकोड नहीं करता। वह उन अक्षरों के UTF-8 बाइट्स से Buffer बनाता है, इसलिए आपका "डिकोडेड" डेटा वही अक्षर हैं, जो बाइट्स के तौर पर दोबारा पैक हो गए हैं। 'base64' अर्गयूमेंट ही पूरी चाल है।
  • बिनरी-स्ट्रिंग का कोस्ट्यूम: atob() का अयूटपूट तब तक टेक्स्ट नहीं, जब तक आप खुद कह न दें। उसे JSON रिस्पोंस, कुकी, या लॉग लाइन में डालना "चल जाता है", और यह हर नल बाइट को भी सहेजता रहता है, जिससे लॉग शिपर्स और सीरियलाईजर दोनों बराबर हैरान होते हैं। तुरंत charCodeAt() से उसे Uint8Array, या UTF-8 टेक्स्ट में बदल दीजिए।
  • क्वेरी स्ट्रिंग में पॉलूस: फॉर्म-डिकोडेड कुकी मान में + तब तक स्पेस हो चुका होता है जब तक URLSearchParams उसे आपको न सौंपे। URL में रहने वाली किसी भी चीज़ के लिए base64url को ही पसंद कीजिए, और क्लासिक Base64 टोकन को बिना एसकेप किए क्वेरी स्ट्रिंग में कभी पैस्ट न करें।
  • रिप्लेसमेंट चर: ग़ैर-वैध UTF-8 Buffer के UTF-8 मोड में चुपचाप हीरा-प्रश्नचिह्न बन जाता है, त्रुटि नहीं, इसलिए खराब पेलोड आपके पाइपलाइन से होकर डेटाबेस में पहुँच सकता है। जहाँ खराबी की आवाज़दार त्रुटि चाहिए, वहाँ TextDecoder के साथ fatal: true चालू कीजिए।
  • Windows की चुनौती: TextDecoder से 'iso-8859-1' या 'latin1' माँगने पर आपको Windows-1252 डिकोडर मिलता है, जहाँ बाइट 0x80 यूरो चिह्न बन जाता है। असली बाइट-दर-बाइट Latin-1 के लिए Buffer को toString('latin1') से पढ़िए। और याद रखिए कि 'binary' वही Latin-1 मैपिटां के लिए सिर्फ़ भटकाव देने वाली एलियस है।
  • साइज़ की सीमाएँ: 64-bit सिस्टम पर buffer.constants.MAX_LENGTH 9007199254740991 बाइट्स है (2 की घात 53, घटा एक), परंतु Base64 संभालने वाली स्ट्रिंग MAX_STRING_LENGTH के 536870888 चर से आगे बढ़ नहीं सकती। इसलिए एक स्ट्रिंग सिर्फ़ 400 MB से थोड़ा ज़्यादा डिकोडेड डेटा संभाल सकती है; उसके आगे, उसे स्टृम कीजिए।
  • मेमोरी का बिल: Base64 स्ट्रिंग का दाम हर चर पर दो हेप बाइट्स है (UTF-16), और डिकोड किया हुआ Buffer पूरी दूसरी कॉपी है। एक 100 MB की फाईल आपके प्रोसेस में, थोड़ी देर के लिए, लगभग 133 MB स्ट्रिंग और 100 MB Buffer बन जाती है। एन्कोडेड रूप के रिफरेंस रहे खिड़की को छोटा कीजिए।
  • सख़्त-दूर, मदारी-नज़दीक का मेल न होना: आपका Node डिकोडर वह माफ़ कर देता है जो कहीं और का सख़्त डिकोडर नहीं मानता (Python सक्रिप्ट, Go सर्विस, मॉबाइल अप)। अगर आपके सिस्टम की एक तरफ सख़्ती है और दूसरी मदारी, तो बग सिर्फ़ कुछ पेलोड लंबाईं पर सामने आता है, जो सबसे खराब तरह का बग है। सख़्ती पर प्रोटोकोल लेवल पर राय तय कीजिए, अपने सिर में नहीं।

JavaScript ने अपने डिकोडर कैसे पाले

ब्राउज़र की तरफ का इतिहास लंबा, सैफ़, और भरोसेमंद है। atob() और btoa() को 2011 की शुरुआत में HTML5 ड्राफ्ट में तय किया गया था (ब्राउज़रों ने उन्हें स्पेसिफिकेशन से पहले ही ले लिया था), और तब से वे हर बड़े ब्राउज़र में बसे हुए हैं, एक दशक से ज़्यादा तक व्यवहार में बिना बदले। वे भाषा स्टैंडर्ड में टाइप्ड अर्रे से पहले के हैं, यही वजह है कि वे "बिनरी स्ट्रिंग्स" में बोलते हैं, बाइट्स में नहीं।

Node.js ने अपना डिकोडर एक अलग टाइमलाइन पर पाला। Buffer क्लास वर्ज़न 0.1.103 में ग्लोबल बन गई, 2010 के गर्मियों में, Node 1.0 से करीब पाँच साल पहले, और उसके साथ 'base64' मोड शुरुआत से ही था। Node की ज़िंदगी के ज़्यादातर हिस्से में वह ही इलाक़े का एकमात्र डिकोडर था। फिर वेब-स्टैंडर्ड लहर आई: 2021 में Node 16 ने atob() और btoa() को ग्लोबल्स के रूप में जोड़ा, ताकि ब्राउज़र के लिए लिखा गया कोड बिना किसी पॉलीफिल के सर्वर पर भी चले, और दोनों को पहले दिन से ही Legacy तय किया। Node 25, जो 15 अक्टूबर 2025 को रिलीज़ हुआ, ने V8 को 14.1 तक अपग्रेड किया और ES2026 के मेथड्स, Uint8Array.fromBase64(), setFromBase64(), और उनके हेक्स बंधुओं को रनटाइम में पेश किया। इसी रास्ते में पुराना new Buffer() कॉन्स्ट्रक्टर डिप्रिकेटेड हो गया (Node 10 ने 2018 में चेतवनियाँ शुरू की), जगह में Buffer.from(), alloc() और allocUnsafe(), इसीलिए भी कि एक अखाली एलोकेशन वही मेमोरी रिसा सकती थी जो उस जगह पहले रखी थी।

ब्राउज़रों में वही लहर थोड़ी जल्दी उतरी: Firefox 133 और Safari 18.2 ने 2024 में नए मेथड्स पेश किए, और Chrome 140 (2 सितंबर 2025 को स्टेबल) ने सेट पूरा किया, तब तक फीचर को ब्राउज़र वेंडर के Baseline प्रोग्राम में Baseline Newly available घोषित कर दिया गया था। Bun, ऑल-इन-वन JavaScript रनटाइम, को वे वर्ज़न 1.1.22 में, अगस्त 2024 में, मिले। और अगर आप नया रनटाइम ज़रूरी नहीं मान सकते, तो core-js और es-shims प्रोजेक्ट का es-arraybuffer-base64 पैकेज इस सब के लिए पॉलीफिल्स देते हैं, जो ज़्यादातर फ्रेमवर्क की भी आंतरिक राह है।

जो फ़ॉर्मेट वे संभालते हैं, उसकी कुलाँच और पुरानी है। वर्णमाला सबसे पहले 1987 में Privacy-Enhanced Mail के लिए स्टैंडर्ड बनी (RFC 989), 1993 की रिवीजन (RFC 1421) ने वही वर्णमाला रखी, और MIME ने उसे 1996 में उठाया (RFC 2045), उस रिवीजन से करीब तीन साल बाद, अपने 76-चर लाइन रैप के साथ; 2003 में RFC 3548 ने base16, base32, और base64 को एक डॉक्यूमेंट में जोड़ा, और 2006 में RFC 4648 ने उसे फिर जारी किया, URL-सुरक्षित वर्णमाला को बनाए रखते हुए जो RFC 3548 ने जोड़ी थी - वही वर्णमाला जो एक दशक बाद हर JWT के अंदर आ गई। URL-सुरक्षित वैरिएंट एक अच्छी ट्रिविया है: यह किसी टोकन से मिलने से पहले, 2001 के एक मेलिंग-लिस्ट पोस्ट में, पीयर-टू-पीयर आइडेंटिफायर के बारे में, प्रस्तावित हुई थी।

आपके अगले स्टैंडअप के लिए मज़ेदार बातें

  • WebSocket RFC की उदाहरण कुंजी, dGhlIHNhbXBsZSBub25jZQ==, "the sample nonce" शब्दों में डिकोड होती है। स्टांडर्ड समिति ने अपने ही उदाहरण के अंदर एक झपकी छिपाई थी, और Node की atob() वह जोक एक ही कॉल में टूड़ देती है।
  • Buffer.from('!!!', 'base64') लंबाई शून्य का Buffer लौटता है। एक असली एलोकेशन जिसमें अंदर कुछ भी नहीं है। कुछ भी नहीं। यह Node का हाथ-झटकना होने की सबसे करीब की चीज़ है।
  • Node के Base64 डिकोडर एक ऐसे ढंग से द्विभाषी हैं जिसकी सपेसिफिकेशन ने कभी माँग नहीं की: +, -, / और _ - चारों 'base64' और 'base64url' मोड दोनों में स्वागत पाते हैं, हर जोड़ी वही मान मैप करती है।
  • atob() की Node दस्तावेज़ीकरण में यह वाक्य मौजूद है: "Use Buffer.from(data, 'base64') instead"। एक रनटाइम जो आपको अपना ही एक ग्लोबल बंद करने का कह रहा है, पूरी तरह आधिकारिक codemod (npx codemod@latest @nodejs/buffer-atob-btoa) के साथ, जो माइग्रेशन आपके लिए कर देता है।
  • छोटे Buffers एक साझा स्लैब से काटे जाते हैं: Buffer.poolSize 65536 बाइट्स है, और हर छोटी एलोकेशन उस पूल के टुकड़े दोबारा इस्तेमाल करती है। यही वजह है कि Buffer बनाना तेज़ है, और यही वजह है कि "अनसेफ़" एलोकेशन का मतलब जानना आपको ज़रूर चाहिए।
  • छोटा सा base64-js पैकेज, तीन फ़ंक्शन और सिफ़र डिपेंडेंसी, npm पर हर हफ़्ते 10 करोड़ से ज़्यादा डाउनलोड ले जाता है, ज़्यादातर हिस्सा दूसरे पैकेजों के अंदर छिपी डिपेंडेंसी के तौर पर। Base64 इस इकोसिस्टम में सबसे ज़्यादा छुपकर भेजा गया कोड है।
  • Uint8Array.fromBase64() में एक मोड है जिसका नाम "stop-before-partial" है, जो सिर्फ़ इसलिए मौजूद है ताकि आप एक स्ट्रीम को कभी भी चार-चर ग्रुप तोड़े बिना डिकोड कर सकें। उस चीज़ के नाम पर रखा मोड जिससे वह मना करती है - API की शायरी का दुर्लघ नमूना।
  • Unix पासवर्ड की दुनिया अपनी-अपनी Base64-स्वरूप वर्णमालाएँ इस्तेमाल करती है, बिना पैडिंग के, और हैरानी की बात यह है कि इनकी क्रमबद्धता एक जैसी नहीं है। क्लासिक crypt(3) की "hash64" वर्णमाला ./0-9A-Za-z है, परंतु bcrypt वही 64 अक्षर ./A-Za-z0-9 में घुमा देता है। आप JavaScript प्रोजेक्ट्स में यूज़र पासवर्ड्स के लिए सहेजे गए $2b$ हैश में bcrypt की वर्ज़न को मिलेंगे, और यही वजह है कि सुरक्षा के संदर्भ में "base64" के कई अलग वर्णमालाएँ हो सकती हैं, सिर्फ़ दो नहीं।

एक दिशा बाकी है

JavaScript और Node.js में Base64 की डिकोडिंग तीन ईमानदार टूल्स का स्टैक है: Buffer.from(string, 'base64'), वह मदारी वर्कहॉर्स जो दोनों वर्णमालाएँ मानता है और हर बख्खरा चर छुड़ जाता है, जिसे सख़्त रेगुलर एक्सप्रेशन से बखूबी सँभाला जा सकता है; TextDecoder, पुराने वेब की इजाद की गई किसी भी चरसेट में असली टेक्स्ट के लिए, fatal मोड के साथ जब खराबी का दम लगना चाहिए; और नया Uint8Array.fromBase64(), बाइट-पहले कोड के लिए जो सख़्त वर्णमालाएँ, सख़्त पैड बिट्स, और बिना किसी स्टंट के स्ट्रीमिंग चाहता है। चरसेट तय कीजिए, अजनबियों की भेजी चीज़ों की जाँच कीजिए, सिग्नेचर की तुलना timingSafeEqual से कीजिए, और ब्राउज़र/सर्वर के पार दोनों तरफ यह फ़ॉर्मेट रहस्य बनकर रहना बंद कर देगा।

और जब आप पैकेट खोलने की फ़रियाद ख़त्म करें, याद रखिए कि किसी ने उन्हें सील भी किया था। एन्कोडिंग की तरफ के अपने-अपने फँदे हैं: वह Unicode दीवार जो btoa() को वाक्य के बीच रुका देती है, MIME लाइन रैपिंग, base64url पैडिंग नियम, और नया Uint8Array.toBase64(), जिसका अपना omitPadding ऑप्शन है। वह कहानी, हर कदम के कोड उदाहरणों के साथ, हमारी बहन साइट के संबंधित Base64 एन्कोडिंग लेख में विस्तार से कवर की गई है। उसे अगला पढ़िए, क्योंकि वर्णमाला की उस तरफ के फँदे अलग और मज़ेदार हैं।

अंतिम अपडेट: 2026-09-07

संबंधित लेख: JavaScript/Node.js में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड