Go में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड
कभी-कभी आपके Go प्रोग्राम को बाइनरी डेटा किसी ऐसी दुनिया को सौंपनी पड़ती है जो सिर्फ़ टेक्स्ट स्वीकार करती है: एक JSON फील्ड जो स्ट्रिंग बनी रहनी चाहिए, एक URL जो एकल टोकन बनी रहना चाहिए, एक ईमेल एटैचमेंट जो उन सर्वरों पर से गुज़र रहा है जिनकी यादों में अभी भी 7-बिट के दिन हैं, और एक इमेज जो HTML के अंदर रहना चाहती है ताकि पेज को एक रिक्वेस्ट की बचत हो। base64 बस इसी काम का courier है, और इस साइट का होम पेज फ़ॉर्मेट को गहराई से समझा चुका है, इसलिए यह आर्टिकल सीधे पैकिंग की कला पर जाता है: Go में ऐसी base64 स्ट्रिंग्स बनाना जिन्हें पृथ्वी का हर डिकोडर बिना लड़ाई के खोल सके।
अच्छी ख़बर पहले: एन्कोडर इस कहानी का आसान आधा हिस्सा है। एक मेथड, कोई error return नहीं, कोई failure mode नहीं, और पहली स्थिर रिलीज़ से लेकर हर Go रिलीज़ पर byte-identical आउटपुट। सारा ड्रामा उसी मेथड के चारों ओर है: उस चैनल के लिए सही वर्णमाला चुनना जिससे स्ट्रिंग सफ़र करेगी, वह Close कॉल जो आप भूल जाएँ तो आपके आख़िरी दो बाइट्स को ख़ामोशी निगल जाता है, फ़ॉर्मेट की वह साइज़ टैक्स जो वह साथ रखता है, और यह कि Go, Python, Java और Node की तरह, अपने आउटपुट को कभी 76 चरों पर wrap नहीं करता। पहले फ़ंक्शन से मिलिए, फिर फ़सेलों से।
गिरने के बिना पैकिंग
Go में एन्कोडिंग के काम का 90 फ़ीसद हिस्सा Encoding टाइप पर एक ही मेथड है, और पैकेज के हर एन्कोडिंग entry point (Encode, AppendEncode) की तरह, इसका कोई error return नहीं है:
func (enc *Encoding) EncodeToString(src []byte) string
उसे बाइट्स दें, वह स्ट्रिंग देगा, और यही पूरा अनुबंध है:
package main
import (
"encoding/base64"
"fmt"
)
func main() {
packed := base64.StdEncoding.EncodeToString([]byte("Man"))
fmt.Println(packed) // TWFu
}
error मान इसीलिए नहीं है क्योंकि गलती होने को कुछ ही है: कोई भी बाइट वैध इनपुट है, वर्णमाला हमेशा उसे cover करती है, और आउटपुट हमेशा शुद्ध ASCII होता है। तीन गुण याद करने लायक़ हैं, क्योंकि वे आने वाले सवालों का आधा हिस्सा जवाब दे देते हैं। पहला, आउटपुट की लंबाई इनपुट लंबाई का एक शुद्ध अंकगणितीय फ़ंक्शन है, और पैकेज उस सूत्र को भी मेथड के रूप में दे देता है: EncodedLen(n) पैडिंग वाली एन्कोडिंग के लिए (n+2)/3*4 लौटाता है, इसलिए 3 इनपुट बाइट्स 4 चर बनते हैं, 6 बनते हैं 8, और इसी तरह। दूसरा, फ़ॉर्मेट की एक साइज़ टैक्स है: हर तीन बाइट्स डेटा चार चरों के रूप में लौटते हैं, यानी वह पढ़ी-समझी करीब 33 फ़ीसद expansion, जो आपके बैंडविड्थ बिल और storage quota में सामने आती है। तीसरा, मेथड deterministic है: वही बाइट्स हमेशा वही स्ट्रिंग बनाते हैं, किसी भी मशीन पर, Go के किसी भी वर्ज़न में, हमेशा। यही determinism base64 को रहस्य की जगह एक serialization फ़ॉर्मेट बनाता है।
इनपुट साइड पर एक Go-विशेष नोट: मेथड []byte लेता है, string नहीं, और []byte(...) रूपांतरण हर call site पर explicit होता है - Go कभी आपकी तरफ़ से स्ट्रिंग को slice में बदलता नहीं है - और यह स्ट्रिंग के बाइट्स की एक स्वतंत्र कॉपी बनाता है। compiler उस कॉपी को हटा सकता है जब slice सिर्फ़ पढ़ा जाए और escape न करे, इसलिए लागत ज़्यादातर नापनीय रहती है; पर अगर slice store या return हो जाए, तो runtime एक असली O(n) कॉपी चुकाता है। Go प्रोग्राम में टेक्स्ट रिवाज से UTF-8 होता है, इसलिए जब आप स्ट्रिंग को एन्कोड करते हैं तो आप उसकी UTF-8 बाइट्स को एन्कोड कर रहे हैं, और दूसरी तरफ़ हर आधुनिक डिकोडर बस यही उम्मीद करता है। इसका विस्तार Text, Bytes, and Unicode सेक्शन में है।
Go इसे कैसे शिप करता है
इस आर्टिकल की तरह सब कुछ, एन्कोडर भी स्टैंडर्ड लाइब्रेरी पैकेज encoding/base64 से आता है, जो भाषा की पहली रिलीज़ से शिप हो रहा है, और उसकी source फ़ाइल अभी भी अपना 2009 कॉपीराइट हेडर पहने हुई है। कोई fetch करने को module नहीं, कोई flip करने को feature flag नहीं, और कोई platform की अजीबो-गरीबी नहीं: अगर go version चलता है, तो go doc encoding/base64 पूरा API आपके लिए प्रिंट कर देता है।
लिखने के समय नवीनतम रिलीज़ Go 1.27.1 है, जो 1 सितंबर 2026 को आया, और दूसरी सपोर्टेड ट्रैक Go 1.26 लाइन है (वर्तमान में 1.26.8)। Go को go.dev/dl की आधिकारिक टारबॉल से इंस्टॉल करें, अपनी डिस्ट्रीब्यूशन के पैकेज मैनेजर से (sudo apt install golang-go), या वर्ज़न घुमाने के शौक़ीनों के लिए golang.org/dl रैपर से। दोनों सपोर्टेड लाइनों पर base64 API बिल्कुल एक जैसा है, और नीचे की टेबल उसकी पूरी कहानी है कि कभी क्या बदला - ऐसे केंद्रीय पैकेज के लिए यह सूची छोटी ही है:
| रिलीज़ | साल | encoding/base64 में क्या बदला |
|---|---|---|
| Go 1.0 | 2012 | पैकेज पहले दिन से स्थिर; source कॉपीराइट 2009 |
| Go 1.5 | 2015 | RawStdEncoding और RawURLEncoding बिना-पैडिंग आउटपुट के लिए जुड़े |
| Go 1.8 | 2017 | Strict() कैनोनिकल डिकोडिंग के लिए जुड़ा (डिकोडर साइड) |
| Go 1.22 | 2024 | AppendEncode और AppendDecode जुड़े; WithPadding अब ग़लत arguments रिजेक्ट करता है |
| Go 1.27.1 | 2026 | वर्तमान रिलीज़; API अपरिवर्तित, व्यवहार Go 1 promise से byte-stable |
उस इतिहास का अमली नतीजा: 2015 में इस API के लिए लिखा कोड आज भी वैसे ही compile होता है और वैसे ही काम करता है, और जो स्ट्रिंग्स आपका प्रोग्राम 2026 में एन्कोड करता है, वे किसी भी Go रिलीज़ पर, चाहे पुरानी हो या आने वाली, सही से डिकोड होंगी। एक serialization फ़ॉर्मेट के लिए यही ख़ामोश superpower है।
गंतव्य के लिए वर्णमाला चुनना
एन्कोडिंग में एक ही असली फैसला है, और वह सफ़र का सवाल है: यह स्ट्रिंग कहाँ जाएगी? Go आपको चार तैयार एन्कोडर देता है, और हर एक किसी अलग चैनल के लिए तैयार किया गया है:
| एन्कोडर | वर्णमाला | पैडिंग | स्ट्रिंग कहीं से सफ़र करे, वहाँ भेजें |
|---|---|---|---|
StdEncoding |
A-Z a-z 0-9 + / |
= |
JSON बॉडीज़, ईमेल MIME parts, data URLs, HTTP Basic auth, PEM, ज़्यादातर APIs |
URLEncoding |
A-Z a-z 0-9 - _ |
= |
URL के paths और queries, फ़ाइल-नाम, और हर जगह जहाँ + या / को escape की ज़रूरत पड़ती है |
RawStdEncoding |
A-Z a-z 0-9 + / |
कोई नहीं | कॉम्पैक्ट स्टैंडर्ड-वर्णमाला स्ट्रिंग्स जहाँ पैडिंग दिखाई नहीं देनी चाहिए |
RawURLEncoding |
A-Z a-z 0-9 - _ |
कोई नहीं | JWT सेगमेंट्स, कॉम्पैक्ट पहचान-चिह्न, URLs में embed किए टोकन |
variants के पीछे का तर्क फ़ॉर्मेट के अपने पीछे का तर्क है। स्टैंडर्ड वर्णमाला वह है जो MIME और ज़्यादातर APIs उम्मीद करते हैं, इसलिए यह डिफ़ॉल्ट है और सुरक्षित जवाब है जब कोई ने कुछ नहीं बताया हो। URL-safe वर्णमाला इसलिए मौजूद है क्योंकि + और / URL में reserve किए गए चर हैं: query स्ट्रिंग में प्लस को अक्सर स्पेस पढ़ा जाता है, और स्लैश नया path सेगमेंट शुरू करता है, इसलिए URL में स्टैंडर्ड base64 या तो टूट जाता है या उन चरों को percent-escape की ज़रूरत पड़ती है जिनमें +, / या = है - किसी आम टोकन का कुछ फ़ीसद हिस्सा। - और _ से उन्हें बदलना, जो paths, queries और फ़ाइल-नाम में बिना escape वैध हैं, वह ठीक वह सुधार है जिसे RFC 4648 ने standardized किया। Raw variants आख़िरी इक्वल्स चिह्न पूरी तरह छोड़ देते हैं, और यह तब मायने रखता है जहाँ पैडिंग या तो मना हो या बस कभी इस्तेमाल न हुई हो, जैसे JWT सेगमेंट्स में। वह नियम जो आपको ज़्यादातर डीबगिंग से बचाता है: जो एन्कोडर आप चुनते हैं और जो डिकोडर दूसरी तरफ़ इस्तेमाल होती है, वे एक ही अनुबंध हैं, और वह अनुबंध गंतव्य लिखता है, आप नहीं।
अगर जिस सिस्टम से आप बात कर रहे हैं ने कोई निजी 64-चरों वाली वर्णमाला तय की है, तो base64.NewEncoding("...64 chars...") उसके लिए आपको एन्कोडर बना देता है, और WithPadding(rune) पैडिंग चर बदलने या NoPadding से उसे बंद करने देता है। दोनों फ़ंक्शन ग़लत arguments पर पैनिक करते हैं (ग़लत वर्णमाला लंबाई, दुबारा वाला चर, वर्णमाला में न्यूलाइन, वर्णमाला से टकराता पैडिंग चर), इसलिए अपने custom एन्कोडर एक बार, startup पर, बनाएं - कभी भी hot path में नहीं।
Close की फ़सेल
यह इस पैकेज की सबसे मशहूर फ़सेल है, और यह तभी सामने आती है जब आप स्ट्रिंग की जगह स्ट्रीम एन्कोड कर रहे हों। NewEncoder किसी भी io.Writer को base64-एन्कोडिंग writer में wrap करता है, और क्योंकि base64 तीन इनपुट बाइट्स के ब्लॉक्स में काम करके चार आउटपुट चर बनाता है, एन्कोडर को आपके आख़िरी एक या दो बाइट्स को बफ़र करना पड़ता है, यह देखने की उम्मीद में कि और आ रहे हैं या नहीं। वे बस तभी flush होते हैं जब आप उसे बंद करते हैं:
package main
import (
"bytes"
"encoding/base64"
"fmt"
)
func main() {
var buf bytes.Buffer
enc := base64.NewEncoder(base64.StdEncoding, &buf)
enc.Write([]byte("hello"))
fmt.Println(buf.String()) // aGVs -- "lo" कहाँ गया?
buf.Reset()
enc = base64.NewEncoder(base64.StdEncoding, &buf)
enc.Write([]byte("hello"))
enc.Close()
fmt.Println(buf.String()) // aGVsbG8= -- "hello" की पूरी एन्कोडिंग
}
पहला प्रिंट-आउट पूरा सबक है: Close के बिना, एन्कोडर ने सिर्फ़ पहला पूरा ब्लॉक निकाला - "hello" के तीन बाइट्स "aGVs" बने - और बची हुई दो बाइट्स आंतरिक बफ़र में बस गायब हो गईं। दूसरा प्रिंट-आउट, Close के बाद, सही और पूरा स्ट्रिंग है। सुधार कोई तकनीक नहीं, एक आदत है: एन्कोडर बनाने के ही पल उसका cleanup भी बना लें:
enc := base64.NewEncoder(base64.StdEncoding, w)
defer enc.Close() // प्रोडक्शन कोड में लौटे एरर की जाँच करना न भूलें
दो बातों से यह फ़सेल दिखने से तेज़ है। पहली, Close असली काम करता है: यह पेंडिंग आधा ब्लॉक flush करता है और यह फेल भी हो सकता है, क्योंकि यह नीचे वाले writer में लिखता है, इसलिए आइडियमैटिक वर्ज़न उसका एरर जाँचता है, खास़ तब जब गंतव्य नेटवर्क या डिस्क हो। दूसरी, दस्तावेज़ीकरण कहता है कि Close के बाद Write कॉल करना एरर है, पर runtime उस वाक्य को enforce नहीं करता। अगर आप बंद करने के बाद फिर लिखें, तो एन्कोडर ख़ामोशी से एक नया ब्लॉक शुरू करके उसे append कर देता है, और बनती है एक ऐसी स्ट्रिंग जिसके बीच में पैडिंग हो - अवैध base64, जो ज़्यादातर डिकोडर भ्रमित करने वाले ऑफ़सेट के साथ रिजेक्ट कर देंगे। वह अनुबंध पालने का काम आपका है।
लाइन व्रैपिंग, Go की तरह
आपने जो भी बड़ी base64 इम्प्लेमेंटेशन इस्तेमाल की है, हर एक अपना आउटपुट wrap करती है: MIME को ज़्यादा से ज़्यादा 76 चरों की लाइन्स चाहिए, PEM 64 इस्तेमाल करता है, और दुनिया भर के ईमेल क्लाइंट कभी-कभार CRLF डाल देते हैं। Go का एन्कोडर इनमें से कुछ नहीं करता। वह एक लगातार लाइन निकालता है, चाहे पेलोड कितना भी बड़ा हो, और पैकेज के जन्म से यही करता आ रहा है। एक मेगाबाइट डेटा का आउटपुट एक ही लाइन है - एक मेगाबाइट-और-एक-तीहाई लंबी, शुरुआत से अंत तक, बिना किसी ब्रेक के।
यह एक जानबूझकर किया गया फैसला है, कोई भूल नहीं। फ़ॉर्मेट लाइन-ब्रेक्स के साथ भी वैसे ही काम करता है और बिना भी, Go का अपना डिकोडर इनपुट में कहीं भी उन्हें skip करता है, और ऐसे एन्कोडर जो ख़ामोशी से आपके डेटा में CRLF डाल दें, उनसे चौंकने वाले प्रोग्राम हैं जो स्ट्रिंग को डेटाबेस कॉलम में store करते हैं या equality के लिए compare करते हैं। कीमत यह है कि जब चैनल को wrap चाहिए हो तो आपको खुद करना पड़ता है, और वह एक छोटा सा helper है:
package main
import (
"bytes"
"encoding/base64"
"fmt"
)
func wrapAt(s string, width int) string {
var out bytes.Buffer
for i := 0; i < len(s); {
end := i + width
if end > len(s) {
end = len(s)
}
out.WriteString(s[i:end])
out.WriteByte('\n')
i = end
}
return out.String()
}
func main() {
raw := base64.StdEncoding.EncodeToString(bytes.Repeat([]byte{0x42}, 100))
fmt.Print(wrapAt(raw, 76))
}
सफ़र की दिशा पर एक नोट: Go का डिकोडर कहीं भी न्यूलाइन्स को नज़रअंदाज़ करता है, इसलिए wrapped इनपुट किसी भी पुल की Go तरफ़ पर बिल्कुल सही डिकोड होता है। दूसरी दिशा में सावधानी चाहिए: अगर आप wrapped आउटपुट किसी उपभोक्ता को भेजें जो breaks की उम्मीद नहीं करता (एक JSON फील्ड, एक URL, एक टोकन), तो पहले उन्हें strip करें, क्योंकि वह उपभोक्ता न्यूलाइन को corrupt चर की तरह ट्रीट कर सकता है। पता रखें कि आपका चैनल किस रिवाज में ज़िंदा है, और आउटपुट उसे जानबूझकर दें।
ईमेल और MIME के लिए पैकिंग
ईमेल base64 का सबसे पुराना घर है। मूल SMTP प्रोटोकॉल 7-bit ASCII carry करने के लिए बना था, इसलिए एटैचमेंट्स को भेजने से पहले base64-एन्कोड किया जाता था और पहुँचते ही डिकोड, और MIME स्टैंडर्ड (RFC 2045) ने इस रिवाज को औपचारिक बना दिया: हेडर Content-Transfer-Encoding: base64 एक part को चिह्नित करता है, और बॉडी को ज़्यादा से ज़्यादा 76 चरों की लाइन्स में तोड़ना चाहिए, बीच में CRLF रखकर।
Go का net/smtp पैकेज जो बाइट्स आप दें वह भेजता है, और MIME parts आपकी तरफ़ से नहीं बनाएगा, इसलिए जो भी प्रोग्राम मेल बनाता है, वहाँ base64 का हिस्सा ऐसा दिखता है:
package main
import (
"bytes"
"encoding/base64"
"fmt"
)
func main() {
body := []byte("hi from Go")
var part bytes.Buffer
part.WriteString("Content-Transfer-Encoding: base64\r\n")
part.WriteString("Content-Type: text/plain; charset=utf-8\r\n\r\n")
encoded := base64.StdEncoding.EncodeToString(body)
for i := 0; i < len(encoded); i += 76 {
end := i + 76
if end > len(encoded) {
end = len(encoded)
}
part.WriteString(encoded[i:end] + "\r\n")
}
fmt.Print(part.String())
}
तीन बातें नोट करने लायक़ हैं। यहाँ सही एन्कोडर स्टैंडर्ड वाला है, क्योंकि MIME मूल स्टैंडर्ड-वर्णमाला context है। लाइन-ब्रेक्स CRLF हैं, platform के native न्यूलाइन नहीं, क्योंकि यही RFC तय करता है और यही मेल parser उम्मीद करते हैं। और अगर आपका प्रोग्राम असली ईमेल बड़ी संख्या में भेजता है, तो एक maintained MIME लाइब्रेरी पूरा मैसेज आपके लिए बना देगी; इस उदाहरण का मक़सद base64 का आधा हिस्सा है, यानी वह हिस्सा जो इस पैकेज का है। वर्णमाला और लाइन-रिवाज सही रख लें, और MIME का बाकी सब कुछ किसी और की ज़िम्मेदारी है।
फ़ाइलें पैक करना
जो फ़ाइलें मेमोरी में समा जाती हैं, उनके लिए पैटर्न हर जगह की तरह वही दो लाइन्स हैं: पढ़ें, फिर EncodeToString। जो नहीं समातीं, उनके लिए streaming आपकी मेमोरी को सपाट रखता है, और तरीका है: एक फ़ाइल, एक एन्कोडर, एक कॉपी, और सही क्रम में दो closes:
in, err := os.Open("photo.jpg")
if err != nil {
panic(err)
}
defer in.Close()
out, err := os.Create("photo.b64")
if err != nil {
panic(err)
}
enc := base64.NewEncoder(base64.StdEncoding, out)
if _, err := io.Copy(enc, in); err != nil {
panic(err)
}
if err := enc.Close(); err != nil {
panic(err) // आख़िरी अधूरा ब्लॉक फ़्लश करता है
}
if err := out.Close(); err != nil {
panic(err)
}
closes का क्रम वही सूक्ष्म हिस्सा है, और यह Close फ़सेल की फ़ाइल-वर्ज़न है: एन्कोडर को फ़ाइल से पहले बंद करना होगा, क्योंकि फ़ाइल में आख़िरी आधा ब्लॉक लिखने वाला enc.Close ही है, और पहले फ़ाइल बंद करने से वह ब्लॉक ऐसे बफ़र में रह जाएगा जो ख़ाली हवा में लिखेगा। defer के साथ याद रखें कि deferred कॉल्स उल्टे क्रम में चलते हैं, इसलिए पहले out.Close register करके enc.Close दूसरा (या, ऊपर के उदाहरण की तरह, फ़ाइल को defer करने से पहले एन्कोडर को खुद ही बंद करके) ही क्रम को सुरक्षित बनाता है।
इस पैटर्न के चारों ओर प्लान करते समय साइज़ टैक्स को दिमाग़ में रखें: 10 मेगाबाइट की फ़ोटो करीब 13.3 मेगाबाइट टेक्स्ट बन जाती है, और 100 मेगाबाइट का आर्काइव डिस्क पर 133 मेगाबाइट की स्ट्रिंग। अगर गंतव्य की quota है, कोई limit है, या बाइट पर कीमत लगती है, तो गिनी जा रही आपकी फ़ाइल की base64 कॉपी है, मूल नहीं।
वेब के लिए पैकिंग: data URLs
ब्राउज़र खुशी-खुशी इमेज या फॉन्ट load करते हैं उस स्ट्रिंग से जो HTML या CSS के अंदर ही रहती है, और वह स्ट्रिंग data URL है: मीडिया टाइप, ;base64 फ़्लैग, एक comma, और पेलोड - सब कुछ एक ही URL में। Go में data URL helper नहीं है, पर बनाना बस स्ट्रिंग जोड़ना है, क्योंकि फ़ॉर्मेट वह अनुबंध है जिसे आप लिखा हुआ देख सकते हैं:
package main
import (
"fmt"
"os"
"encoding/base64"
)
func main() {
img, err := os.ReadFile("logo.png")
if err != nil {
panic(err)
}
url := "data:image/png;base64," + base64.StdEncoding.EncodeToString(img)
fmt.Println(url)
// data:image/png;base64,iVBORw0KGgo...
}
दो नियम data URLs को गड्ढों से बचाते हैं। हमेशा मीडिया टाइप लगाएं: व्याकरण में वह optional है (डिफ़ॉल्ट text/plain;charset=US-ASCII है), पर ब्राउज़र का आपके binary पेलोड की टाइप अंदाज़ा लगाना वह हालत नहीं जो आप चाहें। और data URLs को छोटे assets का trick समझें। RFC कहता है कि स्कीम सिर्फ़ छोटे मानों के लिए काम आती है, और 33 फ़ीसद expansion ही वह फ़र्क़ है जो एक तरफ़ 2 किलोबाइट का आइकॉन - जो एक रिक्वेस्ट बचाता है - और दूसरी तरफ़ 5 मेगाबाइट की फ़ोटो - जो हर पेज load को फूल देता है, बिना cache जिससे share हो, बिना किसी URL के जिसे किसी को दे सके। आइकॉन, favicons, छोटे sprites: हाँ। प्रोडक्ट फ़ोटोग्राफ़ी: नहीं।
HTTP के लिए पैकिंग
Go सर्विस में base64 तीन HTTP context में हावी होता है, और उनमें से दो के साथ built-in मदद मिलती है। पहला है JSON बॉडी, जो कामगार है: आप मारशलिंग से पहले मान को एन्कोड करते हैं, और फील्ड wire पर एक साधारण स्ट्रिंग ले जाती है:
package main
import (
"encoding/base64"
"encoding/json"
"fmt"
)
type avatar struct {
Data string `json:"data"`
}
func main() {
png := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
a := avatar{Data: base64.StdEncoding.EncodeToString(png)}
body, err := json.Marshal(a)
if err != nil {
panic(err)
}
fmt.Println(string(body))
// {"data":"iVBORw0KGgo="}
}
अगर कोई टाइप कई जगहों पर आता है, तो साफ़ Go कदम है उस पर MarshalJSON और UnmarshalJSON implement करना, ताकि base64 का स्टेप हर call site के लिए अदृश्य हो। दूसरा context है HTTP Basic ऑथेंटिकेशन, जहाँ स्टैंडर्ड लाइब्रेरी पूरा काम कर देती है: Request.SetBasicAuth(user, pass) Authorization हेडर आपके लिए बना देता है, RFC 2617 द्वारा तय user:pass जोड़ी पर स्टैंडर्ड एन्कोडर चलाकर। वहाँ का एक ही नियम है: कुछ-न-कुछ ख़ुद बनाकर न जाएं - Basic auth Basic prefix के साथ स्टैंडर्ड base64 है, और URL-safe वर्णमाला या ग़ायब पैडिंग चिह्न एक काम करने वाला login ऐसे 401 में बदल देगा जिसे कोई समझ न पाए।
तीसरा context है URLs, जहाँ स्ट्रिंग किसी path सेगमेंट या query पैरामेटर का पेलोड होती है। यहाँ स्टैंडर्ड वर्णमाला ख़राब चुनाव है, क्योंकि +, / और = तीनों URL के व्याकरण से टकराते हैं, और उनका हर एक इस्तेमाल percent-escape माँगता है। इसके बजाय URL-safe variant से एन्कोड करें, और टोकन URL से पूरा-तावा गुज़रता है। अगर उपभोक्ता फिर भी उसे percent-escape कर दे, तो कुछ टूटा नहीं, पर अगर न करे, तो आपने 404 की एक पूरी श्रेणी से बचाव कर लिया।
URL-safe आउटपुट
Go में URL-safe base64 को अपना अलग सेक्शन इसलिए मिलता है क्योंकि यह वह variant है जो आप स्टैंडर्ड वाले से ज़्यादा बार इस्तेमाल करेंगे, और क्योंकि Go इस स्विच को मुफ़्त करता है। RFC 4648 की वैकल्पिक वर्णमाला + की जगह - रखती है और / की जगह _, इसलिए आउटपुट को URL के paths, queries या फ़ाइल-नाम में escape की ज़रूरत नहीं, और यह लॉग लाइन में एक साफ़ टोकन की तरह पढ़ा जाता है। दो तैयार एन्कोडर हैं URLEncoding (पैडिंग वाला) और RawURLEncoding (बिना पैडिंग):
raw := []byte{0xfb, 0x0f, 0x67, 0x01}
fmt.Println(base64.StdEncoding.EncodeToString(raw)) // +w9nAQ==
fmt.Println(base64.URLEncoding.EncodeToString(raw)) // -w9nAQ==
fmt.Println(base64.RawURLEncoding.EncodeToString(raw)) // -w9nAQ
वही एक इनपुट, तीन आउटपुट: स्टैंडर्ड वर्ज़न को अपने प्लस चिह्न के लिए percent-escape चाहिए, URL-safe वर्ज़न एक टोकन है, और raw वर्ज़न पैडिंग भी छोड़ देती है। हर एक के लिए आम Go काम: opaque पहचान-चिह्न जो service बनाती है और फिर URLs, routes या फ़ाइल-नाम में store करती है; API टोकन जो क्लाइंट query स्ट्रिंग्स में paste करते हैं; और कुछ भी जो लॉग लाइन में आएगा जहाँ प्लस या स्लैश को syntax समझा जाने से बस एक चर दूर है।
वह अनुशासन जो इसे साफ़ रखता है, यह आर्टिकल में हर जगह का वही है: variant उपभोक्ता के साथ एक अनुबंध है। अगर दूसरी तरफ़ स्टैंडर्ड base64 की उम्मीद है और आप URL-safe भेजें, तो उसका डिकोडर पहले ही डैश पर फेल हो जाएगा, और एरर बिल्कुल सही स्ट्रिंग के आख़िर के पास एक बाइट ऑफ़सेट होगा, जो डीबग करने में एक स्पष्ट चीज़ नहीं है। शक हो तो पूछें कि दूसरी तरफ़ क्या उम्मीद है, उसकी वह spec पढ़ें जिसका इशारा करती है, और एन्कोडर गंतव्य से चुनें, आदत से नहीं।
JWT पैक करना
JSON Web टोकन आधुनिक APIs में base64 के सबसे ज़्यादा दिखने वाले उपभोक्ता हैं, और वे variant को बिल्कुल सटीक तय कर देते हैं: RFC 7515 के अनुसार JWS compact serialization तीन base64url सेगमेंट्स हैं, बिना पैडिंग के, डॉट्स से जुड़े हुए। हेडर, पेलोड, signature। इसका मतलब है कि हाथ से बने हर काम के लिए सही एन्कोडर RawURLEncoding है:
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
)
func main() {
secret := []byte("hmac-secret")
header, _ := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
payload, _ := json.Marshal(map[string]any{"sub": "1234567890"})
signingInput := base64.RawURLEncoding.EncodeToString(header) + "." +
base64.RawURLEncoding.EncodeToString(payload)
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(signingInput))
signature := base64.RawURLEncoding.EncodeToString(mac.Sum(nil))
fmt.Println(signingInput + "." + signature)
}
उस उदाहरण को इस बात का सबक पढ़ें कि फ़ॉर्मेट क्या है, इसे ship करने की सलाह नहीं: वह बिल्कुल साफ़ दिखाता है कि base64 कहाँ बैठी है (साइन करने से पहले दो बार, बाद में एक बार) और signature encoded सेगमेंट्स को कवर करती है, raw JSON को नहीं। प्रोडक्शन में sign और verify एक maintained लाइब्रेरी से करें, क्योंकि JWT के पास ग़लतियों की एक लंबी पूँछ है (expiry पर clock skew, algorithm confusion, ग़ायब audience check) जो base64 परत को दिखाई नहीं देती। Go की de facto लाइब्रेरी है github.com/golang-jwt/jwt/v5, इंस्टॉल go get github.com/golang-jwt/jwt/v5 से:
package main
import (
"fmt"
"log"
"time"
"github.com/golang-jwt/jwt/v5"
)
func main() {
secret := []byte("hmac-secret")
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "1234567890",
"exp": time.Now().Add(time.Hour).Unix(),
})
signed, err := token.SignedString(secret)
if err != nil {
log.Fatal("signing failed:", err)
}
fmt.Println(signed)
}
लाइब्रेरी हर सेगमेंट की base64url एन्कोडिंग अंदर ही कर लेती है, इसलिए आप encoding/base64 को बिल्कुल हाथ नहीं लगाते, जो सबसे अच्छा नतीजा है: पैडिंग या वर्णमाला की ग़लती के छुपने की जगह एक कम। और वह guard नोट करें जो वह मुफ़्त देता है: v5 ऐसे टोकन रिजेक्ट करता है जो alg=none दावा करते हैं, जब तक कि आप उसके UnsafeAllowNoneSignatureType कॉन्स्टेंट से खुद से opt-in न करें - यही वह सुरक्षा है जो आपको बिना सोचे चाहिए।
टेक्स्ट, बाइट्स, और Unicode
इस सवाल पर Go की राय किसी भी बड़ी भाषा की सबसे छोटी है, और यही वजह है कि यहाँ base64 इतना सुगम है: Go में string बाइट्स का read-only क्रम है, और आपके प्रोग्राम का टेक्स्ट UTF-8 है। कोई छिपा हुआ एन्कोडिंग layer नहीं, कोई "स्ट्रिंग असल में UTF-16 है" का चौंकाने वाला मोड़ नहीं, और कोई set करने को charset flag नहीं। जब आप EncodeToString([]byte(myText)) लिखते हैं, तो आप टेक्स्ट की UTF-8 बाइट्स को एन्कोड कर रहे हैं - ख़त्म बात:
s := "Café ☕"
packed := base64.StdEncoding.EncodeToString([]byte(s))
fmt.Println(packed) // Q2Fmw6kg4piV
आधुनिक टेक्स्ट के लिए, emoji और CJK समेत, वह एक ही लाइन पूरी कहानी है: base64 बाइट्स पर काम करता है, UTF-8 बस एक बाइट क्रम है, और दूसरी तरफ़ हर वह डिकोडर जो वही रिवाज मानता है, वही स्ट्रिंग लौटाएगा। []byte(...) रूपांतरण एक स्वतंत्र कॉपी है, जो compiler हटा देता है जब slice सिर्फ़ पढ़ा जाए और escape न करे - इसलिए अमल में उसकी लागत आप नाप भी नहीं पाएंगे।
एक ही ऐसा केस है जहाँ कहानी लंबी होती है: legacy डेटा - बाइट्स जो किसी Windows-1252, Shift JIS या ISO-8859-1 सिस्टम ने बनाए हैं और वे वैध UTF-8 नहीं हैं। अगर आप वे बाइट्स ऐसे ही base64-एन्कोड कर दें, तो आपने टूटे टेक्स्ट को अक्षरशः carry कर दिया, जो किसी का मक़सद नहीं था। सुधार है एन्कोड से पहले normalize करना, golang.org/x/text से, ताकि base64 स्ट्रिंग आपके प्रोग्राम से निकलते ही साफ़ UTF-8 साथ रखे:
import (
"golang.org/x/text/encoding/charmap"
"golang.org/x/text/transform"
)
legacy := []byte{0x43, 0x61, 0x66, 0xE9} // Windows-1252 में "Café"
utf8, _, err := transform.Bytes(charmap.Windows1252.NewDecoder(), legacy)
if err != nil {
panic(err)
}
packed := base64.StdEncoding.EncodeToString(utf8)
// Q2Fmw6k= -- वही "Café", अब साफ़ UTF-8 बाइट्स, सफ़र के लिए तैयार
उसी मॉड्यूल में charmap के साथ-साथ japanese, korean, simplifiedchinese और traditionalchinese भी हैं। अमली नियम: एक बार convert करें, उस सीमा पर जहाँ legacy बाइट्स आपके प्रोग्राम में घुसते हैं, और उसके बाद जो कुछ भी आप एन्कोड करेंगे वह UTF-8 ही होगा। दो बार convert न करें, अंदाज़ा न लगाएं, और कभी नॉन-UTF-8 पेलोड को ऐसे base64 स्ट्रिंग में न घुसने दें जिसे कोई आधुनिक उपभोक्ता डिकोड करके दिखाएगा।
एन्कोडर को मापना
एन्कोडर टेबल लुकअप है - इनपुट पर कोई branching नहीं, और आउटपुट स्ट्रिंग के अलावा कोई अलोकेशन नहीं - और आँकड़े यह दिखाते हैं। Go 1.26 चला रहे हालिया डेस्कटॉप CPU पर, 500 बाइट्स एन्कोड करने में दो अलोकेशन के साथ करीब तीन-दसियाँ माइक्रोसेकंड लगते हैं, यानी लगभग एक-और-आधा गिगाबाइट प्रति सेकंड के अंदाज़ में। एक मेगाबाइट डेटा एन्कोड हो जाता है एक मिलीसेकंड से भी बहुत कम समय में; एन्कोडर ज़्यादातर कुछ ऐसा नहीं है जो आप महसूस करें।
एक ही ऐसा lever है जिसे जानना ज़रूरी है: hot loops में अलोकेशन प्रोफ़ाइल। EncodeToString हर कॉल पर आउटपुट स्ट्रिंग अलोकेट करता है, जो 99 फ़ीसद केस के लिए सही trade है। अगर आप हर सेकंड हज़ारों चंक्स को बढ़ते बफ़र में एन्कोड कर रहे हैं, तो Go 1.22 में जुड़ा AppendEncode encoded बाइट्स उस slice में append करता है जिसे आप reuse करते हैं, और बफ़र सही साइज़ तक बढ़ जाने के बाद स्थिर स्टेट में कोई अलोकेशन नहीं करता:
var out []byte
for _, chunk := range chunks {
out = base64.StdEncoding.AppendEncode(out, chunk)
}
एक-दो बार के काम के लिए EncodeToString, tight लूप के लिए AppendEncode, और स्ट्रीम्स और फ़ाइलों के लिए NewEncoder। जो भी चुनें, याद रखें कि एन्कोडर के चारों ओर का नेटवर्क या डिस्क ज़्यादातर धीमा हिस्सा होता है, इसलिए वर्णमाला optimize करने से पहले पूरी path को profile करें।
सिक्योरिटी नोट्स
इस आर्टिकल का सबसे ज़रूरी सिक्योरिटी वाक्य: base64 एन्क्रिप्शन नहीं है, और "हम पहले base64 कर लेते हैं" कोई सिक्योरिटी उपाय नहीं है। वर्णमाला डेटा को text-safe बनाती है, secret नहीं, और ब्राउज़र के डेवलपर टूल्स वाले किसी भी इंसान को आपका base64 पल भर में पढ़ सकता है। गोपनीयता TLS और access control से आती है, और base64 का काम बाइट्स को टेक्स्ट-ऑनली चैनल पर बिना ख़राब किए पंहुचाना है। अपने डिज़ाइन में और अपने दस्तावेज़ीकरण में इन दोनों कामों को अलग रखें, और आप क्लासिक "पासवर्ड प्रोटेक्टेड है, देखिए, base64 है" वाली review टिप्पणी से बच जाएँगे।
दूसरा विचार है साइज़। क्योंकि फ़ॉर्मेट एक-तीहाई फूलता है, आपके सिस्टम की हर limit की एक base64 वर्ज़न है: 4 मेगाबाइट JSON स्वीकार करने वाला API तब करीब 3 मेगाबाइट मूल डेटा लेता है जब पेलोड base64 फील्ड हो, लंबाई की बजट वाली URL raw बाइट्स में छोटी हो जाती है जब टोकन URL-safe और बिना पैडिंग हो, और raw मान के लिए साइज़ किया गया डेटाबेस कॉलम encoded मान के लिए छोटा साबित हो सकता है। store, भेजने या limit लगाने से पहले EncodedLen से अंकगणित करें, और याद रखें कि expansion उस इनपुट पर है जिससे आप शुरू करते हैं, उस स्ट्रिंग पर नहीं जिससे ख़त्म होते हैं।
तीसरा, सोचें कि encoded स्ट्रिंग कहाँ देखी जा सकती है। base64 स्ट्रिंग्स लॉग-अनुकूल और स्क्रीन-अनुकूल होती हैं, जो फ़ीचर है - तब तक, जब तक कि 20 मेगाबाइट का एटैचमेंट 26 मेगाबाइट टेक्स्ट में base64 न हो जाए, जो आपका access log हर रिक्वेस्ट पर वफ़ादारी से दर्ज कर ले। लॉग करें लंबाई, पहली कुछ दर्ज़न चर, और पहचान-चिह्न - पेलोड नहीं - और आपके लॉग पढ़ने योग्य और आपकी डिस्क ज़िंदा रहेंगी। आख़िर में, URLs में URL-safe variant को पसंद करें, ताकि आपके टोकन के कोई भी चर percent-escape की तरह ख़र्च न हों, क्योंकि वह URL को फूलता है और कभी-कभार ऐसे gateway या proxy को उलझा देता है जिनकी query स्ट्रिंग में क्या ठीक है, उसकी सख़्त राय होती है।
मज़ेदार तथ्य और Go की अनोखियाँ
कुछ तथ्य जो इस पैकेज के अपने हैं, उन पलों के लिए जब आप code review में सही साबित करना चाहें:
EncodeToStringकामगार है, और पैकेज के हर एन्कोडिंग entry point (Encode,AppendEncode) की तरह इसका कोई error return नहीं - Go में एन्कोडिंग फेल नहीं हो सकती, जो एक दुर्लभ और ख़ामोश आज़ादी है: कोई भी बाइट वैध इनपुट है, और ग़लत स्ट्रिंग पाने का एक ही तरीका है - चैनल के लिए ग़लत वर्णमाला चुन लेना।EncodedLenशुद्ध अंकगणित है - पैडिंग वाली एन्कोडिंग के लिए(n+2)/3*4- बिना अलोकेशन और बिना लूप के। इसका होना इसलिए है ताकि आप बिना एक भी बाइट एन्कोड किए बफ़र और quota साइज़ कर सकें।- आंतरिक स्ट्रीम एन्कोडर में एक 3-बाइट इनपुट बफ़र और एक 1024-बाइट आउटपुट बफ़र छिपा है, इसीलिए
NewEncoderचंक्स में लिखता है, और इसीलिए आख़िरी आधा ब्लॉक सिर्फ़Closeसे बाहर निकल सकता है। बफ़र ही इस फ़सेल का कारण हैं। - दस्तावेज़ीकरण कहता है कि
Closeकॉल के बाद लिखना एरर है, पर runtime उस वाक्य को enforce नहीं करता। देर कीWriteस्वीकार हो जाती है, एक नया ब्लॉक append करती है, और पैडिंग-भरी स्ट्रिंग बनाती है: अवैध base64, बिना किसी झगड़े के बनी हुई, आस-पास कोई error मान नहीं। - Go का एन्कोडर ने कभी भी अपने आउटपुट को 76 चरों पर wrap नहीं किया - Python, Java और Node के एन्कोडरों की तरह, Go का एन्कोडर एक मेगाबाइट डेटा के लिए एक ही लाइन बनाता है। आपका MIME-wrapping helper आपका खुद का project है, और यही ईमेल base64 की लाइन-ब्रेक्स को याद रखने का अच्छा तरीका भी है - ये MIME का रिवाज है, base64 की ज़रूरत नहीं।
- अगस्त 2026 तक, pkg.go.dev पर 244,000 से ज़्यादा सार्वजनिक पैकेज
encoding/base64import करते हैं। आपका Go प्रोग्राम जो भी है, वह ज़्यादातर कहीं न कहीं base64 कर रहा है, चाहे आपको पता हो या नहीं। - Go 1 की compatibility promise इस पैकेज पर खास़ ताक़त से लागू होती है: उस प्रोग्राम की आउटपुट, जिसने 2013 में स्ट्रिंग एन्कोड की थी, आज Go 1.27 पर byte-identical है। Go में base64 स्ट्रिंग्स असल में अमर हैं।
जो ग़लतियाँ बार-बार सामने आती हैं
एन्कोडिंग की वे ग़लतियाँ जो Go codebases में बार-बार सामने आती हैं, लगभग उसी क्रम में जैसे वे आती हैं:
- स्ट्रीम एन्कोडर पर
Closeभूल जाना, और ऐसी स्ट्रिंग ship कर देना जिसके आख़िरी एक या दो बाइट्स ग़ायब हों। बग हर उस टेस्ट में बच जाता है जिसका इनपुट तीन का गुणज लंबाई का हो, और यही राह है जिससे वह प्रोडक्शन तक पहुँचता है। - एन्कोडर से पहले फ़ाइल बंद कर देना, जिससे आख़िरी आधा ब्लॉक ऐसे file handle में flush हो जाता है जो पहले ही चला गया हो। आउटपुट बिल्कुल उतने ही माप में truncated रह जाता है, और एरर सिर्फ़ अजीब साइज़ के इनपुट पर दिखता है।
- MIME या ईमेल आउटपुट में 76-चरों की लाइन-ब्रेक्स की उम्मीद करना, और चौंक जाना जब Go एक लंबी लाइन दे। wrap चैनल का रिवाज है, और Go में उसे लगाने का काम आपके कोड का है।
- URL के अंदर स्टैंडर्ड वर्णमाला इस्तेमाल कर लेना, और फिर एक दोपहर 404 और 400 के पीछे लगा देना जो असल में percent-encoding की समस्या हैं। अगर स्ट्रिंग URL में रहने वाली है, तो
URLEncodingयाRawURLEncodingसे शुरू करें। - पैडिंग निकाल देना जहाँ उपभोक्ता ने उसे मना किया हो: JWT सेगमेंट्स, कुछ टोकन फ़ॉर्मेट्स, कुछ सख़्त parser। raw variants बस इसीलिए मौजूद हैं, और दूसरी तरफ़ से एरर मैसेज अक्सर आपकी स्ट्रिंग के बिल्कुल आख़िर में एक बाइट ऑफ़सेट होता है।
- secret को base64 करके उसे सुरक्षा कह देना। वह सुरक्षा नहीं है। हेडर, टोकन, "encrypted" फील्ड: कोई भी आधा सेकंड में पढ़ सकता है। TLS इस्तेमाल करें, hash वहाँ जहाँ प्रोटोकॉल को hash चाहिए, और base64 को अपना एक ईमानदार काम करने दें।
- limits लगाने पर 33 फ़ीसद भूल जाना: बॉडी साइज़, कॉलम चौड़ाई, URL बजट, quota चेक्स। अंकगणित है
EncodedLenकी एक कॉल, और उसे छोड़ने की कीमत है प्रोडक्शन में 413 या truncated कॉलम। - UTF-8 न होने वाले टेक्स्ट को एन्कोड कर देना, जो टूट को अक्षरशः carry करता है। एन्कोड से पहले legacy charsets को
golang.org/x/textसे normalize करें, ताकि base64 स्ट्रिंग साफ़ बाइट्स साथ रखे। - बंद करने के बाद एन्कोडर में लिख देना - आदत से या retry लूप से। कोई एरर नहीं उठता, और आउटपुट ख़ामोशी से अवैध रह जाता है।
- मान ले लेना कि दूसरी तरफ़ का डिकोडर Go के जितना ही सहिष्णु है। Go कहीं भी न्यूलाइन्स skip करता है, पर दूसरी भाषाएँ और parser व्हाइटस्पेस और लाइन लंबाई के मामले में सख़्त होते हैं, इसलिए चैनल के रिवाज से मेल खाएं, Go runtime के मूड से नहीं।
उल्टी दिशा
यह कहानी की एन्कोडिंग तरफ़ थी: एक मेथड जो फेल नहीं हो सकती, चार एन्कोडर, जो उन चैनलों से मिले हुए हैं जिन पर उनकी स्ट्रिंग्स सफ़र करेंगी, एक स्ट्रीम एन्कोडर जिसमें एक अनिवार्य Close है, और एक फ़ॉर्मेट जो आपके डेटा को एक-तीहाई फुलाता है और कभी भी अपनी लाइन्स wrap नहीं करता। वर्णमाला गंतव्य से चुनें, अपने एन्कोडर बंद करें, साइज़ का अंकगणित आगे से कर लें, और Go में base64 वही ख़ामोश, निर्भरता-रहित टूल रहता है जो वह 2009 से आ रहा है।
और जब ट्रैफ़िक पलटता है, जब आपका प्रोग्राम इनमें से कोई स्ट्रिंग पाकर उसे खोलना पड़ता है, तो Go में Base64 डिकोडिंग पर संबंधित आर्टिकल उस तरफ़ को विस्तार से कवर करता है: डिकोडर की सहिष्णुता के नियम, वे एरर ऑफ़सेट्स जो बताते हैं कि इनपुट कहाँ गलत हुआ, सख़्त प्रोटोकॉल के लिए strict मोड, और वही चार एन्कोडिंग दूसरी दिशा से।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: Go में Base64 डिकोडिंग: एक सम्पूर्ण गाइड