C++ (Cpp) में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड
उल्टा सवाल है जिसके पास बड़ी शीर्षक-ख़बर है: आपके पास बाइट्स हैं - कोई प्रमाणपत्र, इमेज, रैंडम ब्लॉब, सिग्नेचर - और आपको उन्हें ऐसे कुछ से गुज़ारना है जो सिर्फ़ टेक्स्ट की भाषा समझता है: JSON फ़ील्ड, ईमेल हेडर, URL, एनवायरनमेंट वेरिएबल। इस साइट की होम पेज इस फॉर्मेट को गहराई से पढ़ाती है, इसलिए यह बस छोटा संस्करण है: तीन बाइट्स चार वर्णमाला चर बनते हैं, छोटी टेल को एक या दो = चिह्न मिलते हैं, और एन्कोडेड रूप मूल से लगभग 33 फ़ीसदी बड़ा होता है। एन्कोडिंग वह दिशा है जिसमें चीज़ें बढ़ती हैं, इसलिए इस लेख के हर बफ़र की साइज़ इसी के हिसाब से की गई है, और गणित एक लाइन में है - 4 * ((n + 2) / 3) - जो कोई भी एन्कोडर चुनिए, वही रहता है।
डिकोडिंग की तरफ़ की तरह, C++ स्वयं आपके लिए एक भी बाइट एन्कोड नहीं करेगा। स्टैंडर्ड लाइब्रेरी के पास base64 फ़ंक्शन उगाने के तीस साल रहे और उसने सारा वक़्त दूसरी चीज़ों पर लगा दिया, इसलिए हर C++ प्रोग्राम अपने एन्कोडर को चार बिल्कुल अलग व्यक्तित्वों की बेंच से लेकर आता है, साथ में करीब चालीस लाइनें खुद लिखने का विकल्प भी। एक वर्कहॉर्स है जो 1990 के दशक से TLS उठा रहा है, और जो पूछे-बिना पैड करता है, NUL से ख़त्म करता है, और लाइनें रैप करता है। एक तेज़ हेडर-ऑनली कोडेक है जो एक namespace में छिपा है जिसका लेबल उसके अपने लेखकों ने "detail" रखा है। एक 2002 का इटेरेटर है जिसने लगता है पैडिंग चर से कभी मिलना ही नहीं। और एक फ़ंक्शन है जो ऑपरेटिंग सिस्टम दशकों से शिप कर रहा है, और जो आपके टोकन के आख़िर में CRLF जोड़ देता है। और पाँचवाँ विकल्प आपका है। जब आपको पता चल जाएगा कि हर एक क्या जोड़ता है, क्या मना करता है, और चुपचाप क्या सलग्न कर देता है, एन्कोडिंग off-by-one बगों का स्रोत बनना बंद कर देगी। चलिए पैकिंग की ओर बढ़ते हैं।
मानक ने आज तक कोई पैकर शिप नहीं किया
C++98 के बाद का हर स्टैंडर्ड - और उनके आठ हो चुके हैं, C++26 तक - 64-चर वर्णमाला को देखकर आगे बढ़ गया है। <base64> नहीं है, std::base64 नहीं है, और न ही <string> या <vector> में कुछ भी है जो आपके बाइट्स को पैक करे। C++26 का तकनीकी काम ख़त्म हुआ और मार्च 2026 की ISO C++ मीटिंग, Croydon (UK) में, वोट से पार हुआ (114-12-3), और वह टेक्स्ट कोडेक के काम के लिए सच में <text_encoding> हेडर जोड़ता है; कमेटी की अगली मीटिंग, जून 2026 (Brno) और नवंबर 2026 (Búzios, Brazil), C++26 को दोबारा नहीं देखकर C++29 वर्किंग ड्राफ़्ट खोलती हैं। Base64 स्टैंडर्ड में नहीं है, और कमेटी पर अंगुली उठाना आसान नहीं है: टेक्स्ट एन्कोडिंग कैरेक्टर सेट के बारे में है, और base64 बाइट्स के बारे में, इसलिए नया हेडर कभी सही घर नहीं था। अमल में इकोसिस्टम ने काम कर लिया। OpenSSL के EVP base64 रूटीन हर OpenSSL रिलीज़ में रहे हैं, Boost लाइब्रेरी दो आज़ाद एन्कोडर उठाती हैं, Windows एक CryptoAPI फ़ंक्शन शिप करता है जिसके लिए काम की एक फ्लैग टेबल है, और चालीस-लाइन का एक स्निपेट 2008 से पूरी भाषा भर में कॉपी-पेस्ट हो रहा है। अगर आपका प्रोजेक्ट CMake-आधारित है, तो पूरा डिपेंडेंसी सेटअप तीन लाइन में है:
find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)
Boost की वह रिलीज़ जो नोट करनी है 1.92.0 है, अगस्त 2026 की, एक ऐसे प्रोजेक्ट की जो 1998 में स्थापित हुआ और 1999 की अपनी पहली रिलीज़ से लाइब्रेरी शिप करता आ रहा है। नीचे दिए दोनों Boost एन्कोडर हेडर-ऑनली हैं - लिंक करने के लिए कुछ भी नहीं है - जबकि OpenSSL -lcrypto चाहता है, जो अधिकांश C++ प्रोग्राम जिनमें TLS छूती है उनके बिनरी में पहले से मौजूद है।
सबसे पहले गणित: इस लेख के हर बफ़र की साइज़
Base64 बाइट्स को तीन-तीन के ग्रुप में बाँटता है, इसलिए आउटपुट लंबाई का एक आकार है जो जानते ही कभी आश्चर्य नहीं देता: हर 3 इनपुट बाइट्स पर 4 चर बाहर, और छोटी टेल को पूरे ग्रुप तक पैड कर दिया जाता है। n बाइट्स इनपुट की सटीक गिनती है:
4 * ((n + 2) / 3)
+2 छत का ट릭 है: इंटिजर डिवीजन नीचे गोल करता है, इसलिए पहले 2 जोड़ने से यह अगले तीन के गुणज तक ऊपर गोल हो जाता है। उसके बाद इस लेख में हर बफ़र साइज़ सूत्र में नंबर भरने का सवाल है। OpenSSL के वन-शॉट फ़ंक्शन को ऐसा बफ़र चाहिए जिसमें एन्कोडेड डेटा और वह NUL धारण हो सके जो वह आख़िर में जोड़ता है - मैन पेज यह करार 16 इनपुट बाइट्स के 24 एन्कोडेड बाइट्स + 1 NUL, कुल 25 बाइट्स के उदाहरण से दिखाती है, और फ़ंक्शन NUL के बिना लंबाई लौटाता है। उसकी स्ट्रीमिंग राह इनपुट को 48-बाइट ब्लॉक में प्रोसेस करती है, और मैन पेज आउटपुट की साइज़ हर ब्लॉक के लिए 65 बाइट्स करती है (64 चर + वह न्यूलाइन जो हर ब्लॉक हमेशा पैदा करता है) और NUL के लिए एक और बाइट। Boost.Beast का हेडर आपको सटीक सूत्र constexpr फ़ंक्शन के रूप में देता है। और आपका अपना कोड (n + 2) / 3 * 4 रिज़र्व करता है और तय मान लेता है। यहाँ वे नंबर हैं जो आप असल में देखेंगे:
| इनपुट | आउटपुट (पैडेड) | क्या नोट करें |
|---|---|---|
| 1 बाइट | 4 चर | सबसे छोटा पैडेड रूप: QQ== |
| 2 बाइट | 4 चर | तीन डेटा चर और एक पैड |
| 3 बाइट | 4 चर | एक पूरा ग्रुप, कोई पैडिंग नहीं |
| 48 बाइट | 64 चर | बिल्कुल एक OpenSSL स्ट्रीमिंग ब्लॉक |
| 500 बाइट | 668 चर | 64 पर रैप कीजिए तो 11 लाइनें, न्यूलाइन समेत 679 चर |
| 1 GB | लगभग 1.33 GB | टैक्स के लिए कॉलम, फ़ाइल, और वायर तीनों की बजट करें |
अगर पाठक पक्ष स्थिर-साइज़ कॉलम है, बफ़र है, या टेक्स्ट फ़ाइल में लाइन है, तो यह सूत्र पूरा डिज़ाइन डॉक्यूमेंट है। वह एक दिशा जिसमें यह आपको काट सकती है वह दूसरी है: डिकोड पक्ष को 3n/4 माइनस पैड चाहिए, और एन्कोड सूत्र से साइज़ किया गया डिकोड बफ़र क्लासिक ओवर-अलोकेशन है जो बड़कर मेमोरी-बग टिकट बन जाता है। सिकुड़ती दिशा की साइज़ करना sister गाइड की परेशानी है; यहाँ आप बस बढ़ते हैं।
यहाँ नक़्शा है, क्योंकि फ़र्क़ सब अतिरिक्त में हैं - पैडिंग, न्यूलाइन, NUL - कोर पैकिंग में नहीं, जो हर पंक्ति ने बराबरी से इम्प्लीमेंट किया है:
| एन्कोडर | कहाँ से आता है | पैडिंग | बजट के लिए अतिरिक्त बाइट्स | याद रखने वाला अजीबपन |
|---|---|---|---|---|
EVP_EncodeBlock |
<openssl/evp.h> लिंक -lcrypto |
हमेशा | 1 (बफ़र में NUL) | मैन पेज का 16-बाइट उदाहरण ही करार है |
EVP_EncodeUpdate + Final |
वही | हमेशा | 48-बाइट ब्लॉक पर 65 | 64 चर पर कठोर रैप, हर ब्लॉक न्यूलाइन पर ख़त्म |
Boost.Beast encode |
boost/beast/core/detail/base64.hpp, हेडर-ऑनली |
हमेशा | 0 | detail नाम के namespace में बसा है |
| Boost.Serialization इटेरेटर | boost/archive/iterators/base64_from_binary.hpp, हेडर-ऑनली |
कभी नहीं | 0 - 1 या 2 पैड आप खुद जोड़ते हैं | टूलबॉक्स का सबसे पुराना एन्कोडर, 2002 |
CryptBinaryToStringA |
wincrypt.h, crypt32.lib |
हमेशा | 2 (CRLF) जब तक NOCRLF न हो |
URL-सेफ़ फ्लैग है जो बाक़ी टूलबॉक्स में कहीं नहीं |
| आपकी अपनी चालीस लाइनें | कहीं नहीं: वह आपकी हैं | आपकी मर्ज़ी | आपकी मर्ज़ी | हर edge case आपकी सदा की ज़िम्मेदारी |
कोर अल्गोरिथम हर पंक्ति में बराबर है - यही 1987 के फ़ॉर्मेट की सांत्वना वाली चीज़ है। फ़र्क़ यह है कि हर इम्प्लीमेंटेशन पेलोड के चारों ओर क्या जोड़ता है, और इस लेख के लगभग हर फंदे में एक ऐसी जोड़ कुछ ऐसे उपभोक्ता से मिलती है जिसे इसकी उम्मीद नहीं थी।
OpenSSL: वह एन्कोडर जो आपकी TLS स्टैक पहले से लिंक करती है
अगर आपका प्रोग्राम TLS के लिए पहले से OpenSSL लिंक करता है, तो आपको कुछ भी जोड़ने की ज़रूरत नहीं है। वन-शॉट फ़ंक्शन एक कॉल है:
int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);
इसको स्रोत बाइट्स और लंबाई दे दीजिए और वह पैडेड, एक-लाइन एन्कोडिंग लिखता है। करार याद करने लायक़ है, क्योंकि मैन पेज उसे उदाहरण से बयान करती है: हर 3 इनपुट बाइट्स पर 4 आउटपुट बाइट्स; 3 से बँटने न वाली टेल को पैड कर दिया जाता है ताकि आउटपुट हमेशा 4 से बँटे; और ऊपर से एक NUL टर्मिनेटर चर जोड़ा जाता है। लिखा हुआ उदाहरण है 16 बाइट्स अंदर, 24 एन्कोडेड बाइट्स + 1 NUL, बफ़र में कुल 25 बाइट्स, और फ़ंक्शन 24 लौटाता है - NUL के बिना लंबाई। बफ़र की साइज़ इसी पर करें और रैपर कुछ लाइन में तय है:
#include <cstddef>
#include <cstdio>
#include <string>
#include <openssl/evp.h>
std::string openssl_encode(const std::string &in) {
std::string out;
out.resize(4 * ((in.size() + 2) / 3) + 1);
int n = EVP_EncodeBlock(reinterpret_cast<unsigned char *>(out.data()),
reinterpret_cast<const unsigned char *>(in.data()),
static_cast<int>(in.size()));
if (n < 0) return {};
out.resize(static_cast<size_t>(n));
return out;
}
int main() {
std::printf("%s\n", openssl_encode("Mane").c_str());
std::printf("%s\n", openssl_encode("M").c_str());
std::printf("%s\n", openssl_encode("").c_str());
}
ध्यान दीजिए कि std::string वह क्या कर रहा है जो C आपको ज़बरदस्ती कराता: वह बिल्कुल लौटी लंबाई तक बढ़ता है, इसलिए जो NUL OpenSSL ने जोड़ा वह बस ट्रैक की गई लंबाई के पार है और कभी पेलोड का हिस्सा नहीं बनता। "Mane" एन्कोड कीजिए तो TWFuZQ== मिलता है, क्लासिक चार-चर टेल अपने एक पैड के साथ; एक बाइट एन्कोड कीजिए तो दो-चर डेटा जोड़ी को दो-चर पैड कॉस्ट्यूम पहना मिलती है; कुछ भी न एन्कोड कीजिए तो ख़ाली स्ट्रिंग मिलती है, वह एक मामला जहाँ base64 एन्कोडर बिल्कुल identity फ़ंक्शन की तरह काम करता है। पूरे फ़ंक्शन में असली लॉजिक की एक लाइन resize है: यह "लिखे गए बाइट्स + NUL" को "बिल्कुल पेलोड" में बदल देती है।
ऐसे डेटा के लिए जो टुकड़ों में आता है - फ़ाइल, सॉकेट, ऐसा स्ट्रीम जिसे आप बफ़र नहीं करना चाहते - OpenSSL का एक कॉन्टेक्स्ट है जिसे आप खिलाना और ख़त्म करना हैं, और मैन पेज का ब्लॉक गणित असामान्य रूप से स्पष्ट है। बस पूरे 48-बाइट ब्लॉक तुरंत प्रोसेस होते हैं; कोई भी बचत कॉन्टेक्स्ट के अंदर रुकती है और किसी बाद की कॉल या अंतिम कॉल से छुट्टी पाती है। हर प्रोसेस किया ब्लॉक 64 चर + न्यूलाइन - 65 बाइट्स - लिखता है, और अंतिम कॉल आंशिक ब्लॉक संभालती है, इसीलिए उसकी लिखी छत 65 बाइट्स + NUL है। वह नतीजा जो कॉल से पहले जानना ज़रूरी है: यह API 64 चर पर रैप करता है। यह कॉन्फ़िगर नहीं होता। यही है स्ट्रीमिंग एन्कोडर।
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>
std::string openssl_encode_wrapped(const std::string &in) {
EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
EVP_EncodeInit(ctx);
std::string out;
out.reserve(4 * ((in.size() + 2) / 3) + in.size() / 48 + 2);
std::vector<unsigned char> buf(128);
int outl = 0;
for (size_t pos = 0; pos < in.size();) {
size_t take = std::min<size_t>(48, in.size() - pos);
EVP_EncodeUpdate(ctx, buf.data(), &outl,
reinterpret_cast<const unsigned char *>(in.data()) + pos,
static_cast<int>(take));
out.append(reinterpret_cast<const char *>(buf.data()), outl);
pos += take;
}
EVP_EncodeFinal(ctx, buf.data(), &outl);
out.append(reinterpret_cast<const char *>(buf.data()), outl);
EVP_ENCODE_CTX_free(ctx);
return out;
}
int main() {
std::string s = openssl_encode_wrapped(std::string(500, 'A'));
std::printf("500 bytes -> %zu chars\n", s.size());
int lines = 0;
size_t longest = 0, run = 0;
for (char c : s) {
if (c == '\n') { lines++; run = 0; }
else run++;
longest = std::max(longest, run);
}
std::printf("lines=%d longest=%zu lastchar=%c\n", lines, longest, s.back());
}
इसको A अक्षर के 500 बाइट्स डालिए और हिसाब बिल्कुल वही निकलता है जो मैन पेज ने वादा किया था: 668 एन्कोडेड चर, और चूँकि आउटपुट 64-चर लाइनों में कटा है, आपको 11 लाइनें मिलती हैं, कुल 679 चर, और बिल्कुल आख़िरी चर न्यूलाइन है। वह ट्रेलिंग न्यूलाइन ही वह है जो उपभोक्ताओं को तोड़ता है: नतीजे को JSON स्ट्रिंग में पेस्ट कीजिए तो जहाँ कोट होने का था वहाँ control चर मिलता है; उसे टोकन सेगमेंट की तरह इस्तेमाल कीजिए तो आपने नया सेगमेंट गढ़ लिया। अनुभव-नियम: एक-लाइन पेलोड्स (टोकन, हेडर, कॉन्फ़िग वैल्यूज़) के लिए ब्लॉक API, तब स्ट्रीमिंग API जब उपभोक्ता MIME-आकार का रैप आउटपुट चाहता हो, और शक पर while (out.back() == '\n') लूप से ट्रेलिंग न्यूलाइन हटा दें, इससे पहले कि पेलोड ऐसी बाउंड्री पार करे जो इसे उम्मीद नहीं करती।
Boost.Beast: detail:: namespace में बसा तेज़ पैकर
Boost की HTTP लाइब्रेरी base64 कोडेक को boost/beast/core/detail/base64.hpp नाम के आश्चर्यजनक पते पर शिप करती है। detail:: namespace Boost का यह कहने का तरीका है कि "यह हमारा अंदरूनी काम है", और मंटेनरों ने कोडेक को public API में उठाना मना कर दिया है। फिर भी हर कोई इस्तेमाल करता है: यह छोटी है, तेज़ है, हेडर-ऑनली है (BOOST_BEAST_HEADER_ONLY को include से पहले define कीजिए और लिंक करने के लिए कुछ भी नहीं), और यह वही कोडेक है जो Boost.Beast का अपना WebSocket हैंडशेक Sec-WebSocket-Accept की गणना के लिए इस्तेमाल करता है, यानी यह सालों से असली ट्रैफ़िक चबाती आ रही है।
एन्कोडिंग की तरफ़ API लगभग आहत कर देने वाली सी शांत है। एक constexpr हेल्पर आपको सटीक आउटपुट साइज़ देता है - 4 * ((n + 2) / 3), गणित वाले सेक्शन का वही सूत्र, अब एक कॉम्पाइलर के साथ जो जाँच करे - और encode फ़ंक्शन पैडेड नतीजा आपके बफ़र में लिखता है और बताता है कि उसने कितने चर इस्तेमाल किए। कोई एरर चैनल नहीं है, क्योंकि एन्कोडिंग नहीं फेल हो सकती: हर बाइट वैध इनपुट है, और आउटपुट लंबाई इनपुट लंबाई की शुद्ध फ़ंक्शन है। रैपर:
#define BOOST_BEAST_HEADER_ONLY
#include <boost/beast/core/detail/base64.hpp>
#include <cstddef>
#include <cstdio>
#include <string>
namespace b64 = boost::beast::detail::base64;
std::string beast_encode(const std::string &in) {
std::string out(b64::encoded_size(in.size()), '\0');
std::size_t n = b64::encode(out.data(), in.data(), in.size());
out.resize(n);
return out;
}
int main() {
std::printf("%s\n", beast_encode("Mane").c_str());
std::printf("%s\n", beast_encode("M").c_str());
}
"Mane" एन्कोड कीजिए तो TWFuZQ== मिलता है; अकेला M बाइट एन्कोड कीजिए तो TQ== मिलता है - वही बाइट्स जो OpenSSL रैपर ने पैदा किए, बिना किसी NUL की चिंता और बिना किसी लाइन के हटाने के। दो चीज़ें याद कर लीजिए। पहली, स्रोत: स्रोत पर 2016-2019 में Vinnie Falco का कॉपीराइट है, और एक फुटर कुछ हिस्सों को Rene Nyffenegger के 2004-2008 के स्निपेट को मानता है - वही फ़ॉल्क गाना जिसने C++ base64 की कहानी शुरू की, अब Boost के अंदर शिप हो रही है, आपके बिनरी में, पूरे वेब के लिए WebSocket हैंडशेक करती हुई। दूसरी, अमली बात: क्योंकि कोडेक पैड करती है और कभी रैप नहीं करती, यह सही टूल है सबके लिए जो एक लाइन में होना ज़रूरी है - टोकन, हेडर, API पेलोड्स - और encoded_size सूत्र आपको बिल्कुल सही बफ़र देता है, कभी अनुमान नहीं।
Boost.Serialization: वह इटेरेटर जो भूल गया कि पैडिंग भी होती है
C++ इकोसिस्टम का सबसे पुराना base64 कोई फ़ंक्शन नहीं बल्कि संयोज्य इटेरेटर एडैप्टरों का सेट है, जो Robert Ramey ने 2002 में Boost की serialization लाइब्रेरी के लिए लिखा। एन्कोडिंग दिशा दो-एडैप्टर की चेन है: एक चौड़ाई ट्रांसफ़ॉर्मर जो आपके raw बाइट्स को आठ-से-छह में दोबारा ग्रुप करता है, और एक इटेरेटर जो हर फिर-से-ग्रुप किया वैल्यू को वर्णमाला चर में बदलता है:
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <cstddef>
#include <cstdio>
#include <string>
namespace it = boost::archive::iterators;
std::string boost_iter_encode(const std::string &in) {
using enc =
it::base64_from_binary<it::transform_width<const char *, 6, 8>>;
std::string out(enc(in.data()), enc(in.data() + in.size()));
switch (in.size() % 3) {
case 1: out += "=="; break;
case 2: out += '='; break;
default: break;
}
return out;
}
int main() {
std::printf("%s\n", boost_iter_encode("Mane").c_str());
std::printf("%s\n", boost_iter_encode("M").c_str());
}
इटेरेटर कोर पैकिंग करता है और कुछ भी नहीं - कोई पैडिंग नहीं, NUL नहीं, न्यूलाइन नहीं, और एरर चैनल नहीं, क्योंकि कोर पैकिंग नहीं फेल हो सकती। "Mane" एन्कोड कीजिए और इटेरेटर छह चर, TWFuZQ, सीधा चेहरा रखकर देता है: चार बाइट्स की असली एन्कोडिंग आठ चर होती है, और 2002 के इटेरेटर को इसकी चिंता करने का सवाल ही नहीं उठा। इसीलिए switch स्टेटमेंट load-bearing है, सजावटी नहीं: ग्रुप से एक बाइट कम तो दो पैड, दो बाइट कम तो एक। वही चेन बिना switch के वह नतीजा है जो आपको तब मिलता है जब आप वह कदम भूल जाते हैं, और नतीजा एक स्ट्रिंग होती है जो सहिष्णु डिकोडर के नीचे बख़ूरी डिकोड होती है, सख़्त डिकोडर के नीचे फेल, और आपके API उपभोक्ता का एरर मैसेज रहस्य बन जाती है। (इसी इटेरेटर परिवार की डिकोडिंग तरफ़ वही है जो एक अतिरिक्त स्पेस देखकर एक्सेप्शन फेंकती है - और भी sister गाइड में।)
चालीस लाइनें, शून्य डिपेंडेंसी
Base64 इतनी छोटी है कि सही एन्कोडर की अपनी कीमत रखने का सम्मान है, और C++ में फ़ायदा किसी भी दूसरी भाषा से बढ़िया है: std::string बफ़र मैनजेमंट को आरामदायक बना देता है, सूत्र आपको साइज़ पहले से सटीक देता है, और हाथ-से-बना एन्कोडर वही है जिसका कोई ख़्याल बिल्कुल नहीं - NUL नहीं, न्यूलाइन नहीं, प्लेटफ़ॉर्म की आदतें नहीं - जो बिल्कुल वह है जो आप कॉन्फ़िग फ़ाइल या API बाउंड्री के नीचे चाहते हैं। यह संस्करण 3-बाइट ग्रुप में 64-चर टेबल के मुकाबले पैक करता है:
#include <cstddef>
#include <cstdio>
#include <string>
std::string base64_encode(const std::string &in) {
static const char *table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
out.reserve((in.size() + 2) / 3 * 4);
const unsigned char *p =
reinterpret_cast<const unsigned char *>(in.data());
size_t n = in.size();
for (size_t i = 0; i < n; i += 3) {
unsigned v = p[i] << 16;
if (i + 1 < n) v |= p[i + 1] << 8;
if (i + 2 < n) v |= p[i + 2];
out.push_back(table[(v >> 18) & 63]);
out.push_back(table[(v >> 12) & 63]);
out.push_back(i + 1 < n ? table[(v >> 6) & 63] : '=');
out.push_back(i + 2 < n ? table[v & 63] : '=');
}
return out;
}
int main() {
std::printf("%s\n", base64_encode("Mane").c_str());
std::printf("%s\n", base64_encode("M").c_str());
std::printf("%s\n", base64_encode("M\312\277").c_str());
}
हिस्सों से गुज़रते हैं। reserve लाइन गणित वाला सेक्शन है: (n + 2) / 3 * 4 चर, सटीक, इसलिए लूप के बीच में कोई री-अलोकेशन नहीं। const unsigned char * पर reinterpret_cast कोई रस्म नहीं है - उन प्लेटफ़ॉर्म पर जहाँ char signed है, 127 से ऊपर बाइट वरना ऋणात्मक नंबर होता, और जैसे ही वह टेबल इंडेक्स छूता आपको lab coat पहने undefined behavior मिलता। हर इटरेशन अधिकतम तीन बाइट्स को 24-bit वैल्यू में खींचता है, चार 6-bit स्लाइस टेबल में दबाता है, और टेल पर जो बाइट नहीं थी उसके जगह = निकालता है - i + 1 < n और i + 2 < n गार्ड ही सारी पैडिंग लॉजिक हैं। इसको "Mane" डालिए तो TWFuZQ== मिलता है। अकेला M डालिए तो TQ==। 127 से ऊपर बाइट्स डालिए - उदाहरण की तीसरी लाइन का 0xCA 0xBF जोड़ा - और आउटपुट शुद्ध ASCII (Tcq/) रहता है, क्योंकि 127 से ऊपर बाइट बस एक बाइट है, और टेबल को इसका मतलब कैसा है इसकी परवाह नहीं। चालीस लाइनें, कोई डिपेंडेंसी नहीं, और हर edge case वह लाइन है जो आपने लिखी - और यही पूरा मक़सद है।
Windows CryptoAPI: ऑपरेटिंग सिस्टम का बिल्ट-इन पैकर
Windows पर base64 एन्कोडर ऑपरेटिंग सिस्टम के खुद के अंदर है, इस लेख के अधिकांश फ्रेमवर्क्स से पुराना: wincrypt.h से CryptBinaryToStringA, crypt32.lib में, CryptoAPI का हिस्सा जो दशकों से Windows के साथ शिप हो रहा है। यह बाइट एरे को फ़ॉर्मैटेड स्ट्रिंग में बदलता है, और उसकी फ्लैग टेबल फ़ॉर्मेट के पूरे इतिहास के मेनू की तरह पढ़ी जाती है:
| फ्लैग | वैल्यू | आपको क्या मिलता है |
|---|---|---|
CRYPT_STRING_BASE64HEADER |
0x0 |
प्रमाणपत्र BEGIN/END हेडर लाइनों में रैप किया base64 |
CRYPT_STRING_BASE64 |
0x1 |
सादा base64, कोई हेडर नहीं |
CRYPT_STRING_BASE64URI |
0xD |
URL-सेफ़ वर्णमाला: + बनता है -, / बनता है _, RFC 4648 सेक्शन 5 के अनुसार |
CRYPT_STRING_NOCRLF |
0x40000000 |
आख़िर में न्यूलाइन जोड़ा नहीं जाता |
CRYPT_STRING_NOCR |
0x80000000 |
डिफ़ॉल्ट CRLF की जगह सादा LF |
पहली बात जो जाननी है वह डिफ़ॉल्ट है: जब तक आप CRYPT_STRING_NOCRLF न पास करें, फ़ंक्शन आपके स्ट्रिंग के आख़िर में कैरिएज-रिटर्न/लाइन-फ़ीड जोड़ा जोड़ देता है - लिखा हुआ व्यवहार यह है कि हर नॉन-बाइनरी फ़ॉर्मेट को न्यूलाइन सीक्वेंस मिलती है - इसलिए एक-लाइन में फिट होना ज़रूरी base64 टोकन BASE64 | NOCRLF चाहता है, और वह जोड़ी ही रिवाज-सिद्ध कॉल है। दूसरी बात कॉलिंग कॉन्वेंशन है, जो क्लासिक Windows दो-कदम है: NULL बफ़र के साथ कॉल करके पूछिए कि कितनी जगह चाहिए (जवाब में NUL टर्मिनेटर शामिल है), अलोकेट कीजिए, दोबारा कॉल कीजिए, और NUL के बिना लंबाई वापस पढ़िए:
#include <windows.h>
#include <wincrypt.h>
#include <cstddef>
#include <string>
std::string win32_encode(const std::string &in,
DWORD flags = CRYPT_STRING_BASE64) {
DWORD need = 0;
if (!CryptBinaryToStringA(reinterpret_cast<const BYTE *>(in.data()),
static_cast<DWORD>(in.size()),
flags | CRYPT_STRING_NOCRLF,
nullptr, &need))
return {};
std::string out(need, '\0');
DWORD got = 0;
if (!CryptBinaryToStringA(reinterpret_cast<const BYTE *>(in.data()),
static_cast<DWORD>(in.size()),
flags | CRYPT_STRING_NOCRLF,
out.data(), &got))
return {};
out.resize(got);
return out;
}
दो और नोट। URI फ्लैग इस पूरे लेख की एकमात्र बिल्ट-इन base64url है - Windows पर आप टोकन वर्णमाला सीधे एन्कोड कर सकते हैं, और नीचे वाले सेक्शन का ट्रांसकोड तरीका केवल दूसरे प्लेटफ़ॉर्म के लिए है। और CRYPT_STRING_BASE64HEADER एंट्री, जिसकी वैल्यू 0 है, वही फ्लैग भी है जो आपको तब मिलती है जब आप शून्य पास करते हैं, इसलिए ऐसी कॉल जिसका "मतलब" कोई फ्लैग नहीं था, चुपचाप पेलोड को प्रमाणपत्र हेडर लाइनों में रैप कर देती है - PEM-युग की फ्रेमिंग की आदत, .pem फ़ाइलें बनाने के लिए काम की और बाक़ी सबके लिए सरप्राइज़। crypt32.lib से लिंक कीजिए और फ़ंक्शन प्रोग्राम की बची हुई ज़िंदगी में आपका है।
Base64url: टोकन और URL की वर्णमाला
स्टैंडर्ड वर्णमाला में दो चर हैं जो URL में नहीं बचते: + क्वेरी स्ट्रिंग में स्पेस है, और / राह में डायरेक्टरी है। RFC 4648 सेक्शन 5 यह दो चर स्वैप से ठीक करता है - + बनता है - और / बनता है _ - और नतीजे के बारे में सीधा है: यह एन्कोडिंग "base64 एन्कोडिंग से वही माननी चाहिए नहीं"। यह JWTs, OAuth PKCE code challenges, YouTube वीडियो आइडेंटिफ़ायर, और अधिकांश API टोकन की वर्णमाला है, और यह रूटिन की तरह = पैडिंग भी गिरा देती है, क्योंकि टोकन में लंबाई ख़ुद-ब-ख़ुद मालूम होती है और पैड बस पर्सेंट-एस्केप्स बनने का इंतज़ार कर रहे होते हैं।
इस लेख के एन्कोडरों में से सिर्फ़ Windows फ्लैग वर्णमाला को बिल्ट-इन रूप में निकालता है - OpenSSL में URL-सेफ़ मोड नहीं है, और न किसी Boost रूप में - इसलिए अधिकांश प्लेटफ़ॉर्म पर नुस्खा है: स्टैंडर्ड एन्कोड कीजिए, दो चर स्वैप कीजिए, पैड गिरा दीजिए। यह एक दर्जन लाइन में तय है:
#include <cstddef>
#include <cstdio>
#include <string>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
std::string base64url_encode(const std::string &in, bool pad = false) {
std::string out = base64_encode(in);
for (char &c : out) {
if (c == '+') c = '-';
else if (c == '/') c = '_';
}
if (!pad)
while (!out.empty() && out.back() == '=')
out.pop_back();
return out;
}
int main() {
std::printf("%s\n", base64url_encode("M\312\277").c_str());
std::printf("%s\n", base64url_encode("M").c_str());
std::printf("%s\n", base64url_encode("M", true).c_str());
}
आउटपुट की पहली लाइन Tcq_ है, जहाँ स्टैंडर्ड वर्णमाला / लिखती; अगली दो लाइनें पैड स्विच को काम पर दिखाती हैं - डिफ़ॉल्ट में TQ बिना पैड, TQ== तब जब उपभोक्ता उसे वापस चाहता हो। वह pad एर्गुमेंट ही सोचने वाला है, क्योंकि उपभोक्ता आपस में अलग हैं: JWT सेगमेंट्स को पैड नहीं चाहिए, PKCE challenges को पैड नहीं चाहिए, लेकिन base64url वैल्यू जो ऐसी फ़ील्ड में पहुँचती है जहाँ डिकोडर लंबाई पर सख़्त है, उसे पैड वापस चाहिए भी सकते हैं, और स्विच bool है, पुनर्लेखन नहीं। और उल्टी दिशा में याद रखने वाला फेलियर मोड: स्टैंडर्ड-वर्णमाला पेलोड के अंदर - बस अवैध है, इसलिए दो वर्णमालाएँ बाइट स्तर पर एक-दूसरी की जगह नहीं बनतीं - ग़लत वर्णमाला से एन्कोड किया टोकन डिकोड नहीं होता, फेल होता है, और यही वह फेलियर है जो आप सुरक्षा बाउंड्री पर चाहते हैं।
लाइन रैपिंग: 64, 76, या कभी नहीं
रैप किए base64 के बाहर तीन लाइन लंबाइयाँ हैं, हर एक का अपना इतिहास। OpenSSL स्ट्रीमिंग एन्कोडर 64 चर पर कठोर है - PEM की आदत, जहाँ Privacy-Enhanced Mail का 1987 स्टैंडर्ड 64 पर रैप करता था। MIME, जब उसने 1993 में ईमेल के लिए एन्कोडिंग मानकीकृत की, 76 चर पर आ गया, और वह नंबर coreutils base64 कमांड का डिफ़ॉल्ट है (उसकी -w फ्लैग चौड़ाई सेट करती है, और -w 0 रैपिंग बंद ही कर देती है) और इकोसिस्टम के अधिकांश टूल्स का। RFC 4648 स्वयं किसी पक्ष का नहीं है: यह 76 को MIME की सीमा के रूप में उद्धृत करता है और इम्प्लीमेंटेशन को कहता है कि जब तक संदर्भित विनिर्देश न कहे तब तक रैप ही न करें। आप कौन-सी निकालें इस बात पर निर्भर है कि कौन खपता है, और उपभोक्ता - फ़ॉर्मेट नहीं - डिज़ाइन सीमा है।
रैपिंग एन्कोडेड स्ट्रिंग पर बाद-प्रोसेसिंग कदम है, कभी इनपुट कदम नहीं: 4-चर ग्रुप ही अर्थ की इकाई है, इसलिए चौड़ाई के किसी भी गुणज पर स्ट्रिंग कटना सुरक्षित काट है - हर लाइन बाउंड्री ग्रुपों के बीच गिरती है। C++ संस्करण एक लूप है:
#include <cstddef>
#include <cstdio>
#include <string>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
std::string wrap_lines(std::string s, size_t width = 76) {
std::string out;
for (size_t i = 0; i < s.size(); i += width)
out += s.substr(i, width) + "\r\n";
return out;
}
int main() {
std::string mime = wrap_lines(base64_encode(std::string(200, 'x')));
int lines = 0;
for (char c : mime)
if (c == '\n') lines++;
std::printf("mime: %d lines, %zu chars\n", lines, mime.size());
}
हिसाब: 200 बाइट्स एन्कोड होकर 268 चर बनते हैं, और 76 पर CRLF टर्मिनेटर के साथ रैप किया तो वह 4 लाइनें हैं - तीन पूरी लाइनें और 40-चर टेल - वायर पर 276 चर। स्निपेट का CRLF चयन ईमेल का चयन है; बाक़ी सब के लिए LF आधुनिक डिफ़ॉल्ट है, और वह एक नियम जो मसले की चीज़ नहीं है वह सुसंगति है - CRLF उम्मीद करने वाला डिकोडर सख़्त होने पर अकेले LF को डेटा चर के रूप में पढ़ लेगा। (MIME का नियम है कि डिकोडर को लाइन ब्रेक नज़रअंदाज़ करने होंगे, इसीलिए ईमेल को इस फ़र्क़ से कभी तकलीफ़ नहीं हुई।) तीसरी जानने लायक़ आदत: openssl base64 कमांड - जो enc प्रोग्राम trench coat में है, अपना नाम argv[0] में जाँचता है - -A के बिना 64 पर रैप करता है और -A के साथ एक लाइन निकालता है, और यह कमांड लाइन का वह एक टूल है जिसका व्यवहार आप हर चलाव पर जाँचते हैं, यादों पर भरोसा नहीं करते।
JSON और कॉन्फ़िग में बाइनरी
JSON स्ट्रिंग में एक छोटी लिस्ट है ऐसे चरों की जो raw रूप में नहीं रह सकते: कोट, बैकस्लैश, और 0x20 से नीचे control चर। प्रमाणपत्र, रैंडम key, सिग्नेचर - सब में बाइट्स भरे हैं जो raw स्ट्रिंग फ़ील्ड के अंदर सवारी करने पर एस्केप की क़तार बन जाते, और control चर कुछ पार्सरों को बिलकुल ख़ोकने पर मजबूर कर देते। Base64 ही उपाय है, और यह वह डिफ़ॉल्ट जवाब है जो हर कॉन्फ़िग फ़ॉर्मेट देता है जो बाइनरी उठाना मज़बूर है: वैल्यू शुद्ध वर्णमाला चरों की एक लाइन के रूप में सँवरी जाती है, और JSON लाइब्रेरी के कोटिंग नियमों के पास कुछ बचा ही नहीं रहता।
C++ पैटर्न ही पूरा इम्प्लीमेंटेशन है: बाइट्स पढ़ो (बाइनरी मोड में, ज़ाहिर है), एन्कोड करो, स्ट्रिंग सँवो। उपभोक्ता दूसरी तरफ़ डिकोड करता है। JSON-खास एक फंदा रैप स्ट्रिंग है: 76-चर रैप किया प्रमाणपत्र जो सीधा JSON फ़ाइल में पेस्ट हो वह असली control चरों से भरी स्ट्रिंग है, जो पार्स एरर है या चुपचाप कॉरप्शन, पार्सर के मूड पर निर्भर। अगर वैल्यू इंसानी आँखों के लिए रैप होनी ज़रूरी है, तो या तो एस्केप होनी चाहिए या एक लाइन होनी चाहिए - और मशीन-टू-मशीन कॉन्फ़िग के लिए एक लाइन ही जवाब है। दूसरा फंदा बिना लेबल वैल्यू है: 2014 की मैन पेज में base64 लिखा कॉन्फ़िग कॉलम आमतौर पर पैडेड स्टैंडर्ड वर्णमाला होता है, लेकिन API युग के टोकन बिना-पैड URL-सेफ़ हैं, और डिकोडिंग गाइड का चार-चर टेस्ट - क्या इसमें + या / है, - या _, आख़िर में =? - ही पूरा निदान है।
Data URIs: वे फ़ाइलें जो पेज में पेस्ट हो जाती हैं
data URI वह URL है जिसका पेलोड पते में ही खड़ा है: data:, वैकल्पिक मीडिया टाइप, वैकल्पिक ;base64 मार्कर, कॉमा, और डेटा स्वयं - RFC 2397 का पूरा स्कीम। ब्राउज़र इमेजेस, फ़ॉन्ट्स, और छोटे स्क्रिप्ट्स को बिना अतिरिक्त रिक्वेस्ट के सीधे HTML और CSS में इम्बेड करने के लिए इन्हें इस्तेमाल करते हैं, और अगर नेटवर्क बंद करके भी पेज काम करता रहे तो data URI मज़बूत शकिया है। C++ की तरफ़ एन्कोडिंग का काम स्ट्रिंग जोड़ना है, जो एक कॉन्स्टेंट के साथ स्ट्रिंग-जोड़ है:
#include <cstdio>
#include <string>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
std::string make_data_uri(const std::string &mime_type,
const std::string &binary) {
return "data:" + mime_type + ";base64," + base64_encode(binary);
}
int main() {
std::printf("%s\n", make_data_uri("text/plain", "hi").c_str());
}
फंदे सब विवरणों में हैं। ;base64 मार्कर बिल्कुल सात चरों का है, और यही लंबाई है जिसे off-by-one बग नख़रे लेते हैं: छह जाँचने वाला पार्सर वही है जो data:text/plain;base4,... स्वीकार कर लेता है और सीधा चेहरा रखकर कचरा डिकोड करता है। और base64 data URI का पेलोड एक लाइन है - न्यूलाइन URI ग्रामर का हिस्सा नहीं हैं, इसलिए अगर आपका एन्कोडर इमेज को 76 पर रैप कर दे (और MIME-आकार एन्कोडर डिफ़ॉल्ट में करेंगे), तो URI ब्राउज़र तक पहुँचने से पहले टूट चुका है। इस उपभोक्ता का नियम: एन्कोड कीजिए, रैप न कीजिए, और मीडिया टाइप सटीक रखिए - JPEG पर ग़लत image/png वह झूठ है जो बस दो बजे रात को टूटे थंबनेल के रूप में सामने आता है।
टोकन: JWTs, PKCE और API कीज
इंटरनेट पर सबसे बड़े दाँव वाला base64 टोकन में है। JSON Web Token तीन base64url सेगमेंट हैं जो डॉट्स से जुड़े हैं: एक हेडर JSON, एक claims JSON, और header.claims स्ट्रिंग पर गिना गया सिग्नेचर। C++ में बिल्ट-इन JWT टाइप नहीं है, लेकिन एक बनाना ऊपर वाला base64url एन्कोडर + एक HMAC कॉल है, क्योंकि पूरा टोकन base64url होता है जब तक वह सिग्नेचर न हो:
#include <cstddef>
#include <cstdio>
#include <string>
#include <openssl/evp.h>
#include <openssl/hmac.h>
/* पिछले सेक्शन्स से base64_encode और base64url_encode */
std::string jwt_hmac256(const std::string &signing_input,
const std::string &secret) {
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int len = 0;
HMAC(EVP_sha256(), secret.data(), static_cast<int>(secret.size()),
reinterpret_cast<const unsigned char *>(signing_input.data()),
signing_input.size(), digest, &len);
return std::string(reinterpret_cast<const char *>(digest), len);
}
int main() {
const std::string header_json = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
const std::string claims_json =
"{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}";
std::string head = base64url_encode(header_json);
std::string claims = base64url_encode(claims_json);
std::string signing_input = head + "." + claims;
std::string sig = base64url_encode(jwt_hmac256(signing_input, "secret"));
std::printf("token: %s\n", (signing_input + "." + sig).c_str());
}
उदाहरण चलाइए और जो टोकन निकलेगा वह पाठ्यपुस्तक-सटीक HS256 टोकन होगा: हेडर डिकोड होकर {"alg":"HS256","typ":"JWT"}, claims subject, name, और issued-at टाइमस्टैम्प, और सिग्नेचर दो एन्कोडेड सेगमेंट्स पर HMAC-SHA256 का base64url। तीन विवरण पूरा डिज़ाइन उठाते हैं। साइनिंग इनपुट एन्कोडेड सेगमेंट्स हैं, raw JSON नहीं - JSON पर साइन कीजिए तो आपने ग़लत बाइट्स पर साइन किया। सेगमेंट्स बिना-पैड base64url हैं - पैड URL के बीच में बैठ जाते, और वर्णमाला का पूरा मक़सद था टोकन को एक साफ़ स्ट्रिंग रखना। और HS256 का मतलब साझा secret है, जो सर्वर-टू-सर्वर अल्गोरिथम है: क्लाइंट के कोड में बसा secret secret नहीं, और उसका साइन किया टोकन क्रेडेंशियल नहीं। (OAuth का PKCE फ़्लो वही वर्णमाला एक कदम दूर इस्तेमाल करता है: एक रैंडम वैरिफ़ायर, SHA-256 से हैश, बिना पैड base64url करके code challenge में - base64url सेक्शन का एन्कोडर ही पूरा क्लाइंट-साइड इम्प्लीमेंटेशन है।)
HTTP Basic Auth
HTTP में सबसे पुराना base64 क्रेडेंशियल हेडर है: Authorization: Basic जिसके बाद user:password का base64, एक स्कीम इतनी पुरानी कि वह JSON से पहले की है। इसे बनाना स्ट्रिंग-जोड़ है:
#include <cstdio>
#include <string>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
std::string basic_auth_header(const std::string &user,
const std::string &pass) {
return "Basic " + base64_encode(user + ":" + pass);
}
int main() {
std::printf("%s\n", basic_auth_header("user", "password").c_str());
}
आउटपुट वह स्ट्रिंग है जो आपने शायद किसी पकड़े गए रिक्वेस्ट में देखी है: Basic dXNlcjpwYXNzd29yZA==। दो C++ नोट। user + ":" + pass स्ट्रिंग-जोड़ वही जगह है जहाँ कॉलन वाला password दूसरी तरफ़ के सरल पार्सर को घुमा देता - पार्सिंग नियम "पहले कॉलन पर बँटो" है, इसीलिए बनाने वाले पक्ष के पास दोनों फ़ील्ड में कुछ भी रखने का अधिकार है। और अगर क्रेडेंशियल ASCII नहीं हैं, तो स्कीम का सुरक्षित पढ़ना user-id और password को base64 से पहले UTF-8 मानना है, जो C++ में इसका मतलब है कि आपका std::string काम पहले से कर रहा है - बशर्ते आपने उसे UTF-8 बाइट्स से भरा हो, locale के तय किए कुछ से नहीं। सुरक्षा नोट इस स्कीम के हर ज़िक्र के साथ चलता है: Basic auth छिपाई है, सुरक्षा नहीं। हेडर बिना छिपाई सफर करता है, जो कोई नेटवर्क पढ़ सकता है वही पढ़ लेता है, इसलिए यह बस TLS के पीछे मंज़ूर है, और तब भी मशीन-टू-मशीन कॉल के लिए, इंसानों के लिए नहीं। (Boost.Beast का कोडेक - detail:: namespace वाला - Boost के WebSocket इम्प्लीमेंटेशन के अंदर यही हेडर काम करता है, SHA-1 डायजेस्ट को base64 करता है जो Sec-WebSocket-Accept key बनता है, और यही चुपचाप सबूत है कि पैटर्न 2017 से यह कर रहा है।)
ईमेल: seven-bit के नियम, Base64 का जवाब
ईमेल वही जगह है जहाँ base64 ने अपनी आदतें सीखीं, और आदतें आज भी load-bearing हैं। SMTP, अपने मूल रूप में, seven-bit ASCII उठाने के लिए बना था, इसलिए बाइनरी को सफ़र से पहले प्रिंट करने योग्य टेक्स्ट में री-लिखना पड़ता था। Privacy-Enhanced Mail ने 1987 में 64-चर लाइनों और आख़िर में चिपकाए RSA-MD2/MD5 message-integrity check के साथ किया, और MIME, जब उसने 1993 में ईमेल के लिए एन्कोडिंग मानकीकृत की, सीमा को 76 चर तक ढीला किया और नियम जोड़ा कि मानक-अनुकूल डिकोडर को बस लाइन ब्रेक नज़रअंदाज़ करने होंगे। ईमेल एटैचमेंट आज भी base64 है, 76 पर रैप किया हुआ, और सटीक गणित 4/3 गुणा 78/76 तक जाता है - मूल साइज़ का लगभग 137 फ़ीसदी, साथ में लगभग 814 बाइट्स हेडर।
C++ की तरफ़ ऊपर वाला एन्कोडर + रैप फ़ंक्शन है - एक लाइन में एन्कोड, 76 पर CRLF से रैप, ख़त्म। दो ईमेल-खास विवरण: आख़िरी लाइन ट्रेलिंग न्यूलाइन ले सकती है या नहीं (डिकोडरों को नज़रअंदाज़ करना ज़रूरी है, इसलिए दोनों क़ानूनी हैं और दोनों आम हैं), और रैप किया वैल्यू JSON वैल्यू नहीं है, एनवायरनमेंट वेरिएबल नहीं, टोकन नहीं - यह MIME बॉडी में बसने वाला ब्लॉब है, और उसे कहीं और ले जाना वही जगह है जहाँ रैप आदत बंद और बग शुरू होता है। उल्टी दिशा - 76 पर रैप किया हुआ एटैचमेंट आ रहा है - sister गाइड की ज़मीन है, जहाँ चार C++ डिकोडर लाइन ब्रेक पर चार अलग तरह से मतभेद रखते हैं।
फ़ाइलें, स्ट्रीम और दो-गीगाबाइट की छत
फ़ाइल एन्कोड करना डिकोडिंग गाइड के फ़ाइल काम का परछाई-रूप है: बाइनरी मोड में खोलो (Windows पर टेक्स्ट-मोड पढ़ने से CRLF जोड़े एकल न्यूलाइन में बदल जाते और एन्कोडर को दिखने से पहले ही आपका डेटा बदल जाता), बाइट्स पढ़ो, एन्कोड करो, बाइनरी में लिखो। छोटी-फ़ाइल संस्करण एक-फ़ंक्शन का काम है:
#include <cstdio>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
std::string encode_file(const std::string &path) {
std::ifstream in(path, std::ios::binary);
if (!in) return {};
std::vector<unsigned char> bytes{std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>()};
return base64_encode(
std::string(reinterpret_cast<const char *>(bytes.data()), bytes.size()));
}
int main() {
std::string b64 = encode_file("/etc/hostname");
std::printf("file -> %zu chars\n", b64.size());
}
छत ही सेक्शन शीर्षक का C++-खास तथ्य है: EVP API में हर लंबाई पैरामीटर int है। इसलिए एक EVP_EncodeBlock कॉल अधिकतम लगभग 2 GB इनपुट एन्कोड कर सकती है, और उस कॉल का आउटपुट बफ़र - 1.33 गुणा बड़ा - int में बिल्कुल नहीं फिट होता। छत से नीचे, ब्लॉक API मेमोरी में फिट होने वाली फ़ाइलों के लिए ठीक है। उसके ऊपर, या ऐसी फ़ाइल के लिए जो आप मेमोरी में नहीं रखना चाहते, आप चंक-बनाकर एन्कोड करते हैं - और चंक-बनाई का नियम लूप पर base64-खास एकमात्र बंधन है: चंक 3 बाइट्स के गुणज होने चाहिए, क्योंकि ग्रुपिंग तीन-तीन की है और ग्रुप के बीच चंक बाउंड्री आउटपुट बदल देती है। 3072 - तीन 1024-बाइट चंक - आरामदायक चंक साइज़ है, और लूप बन जाता है:
#include <algorithm>
#include <cstddef>
#include <string>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
std::string encode_streamed(const std::string &data) {
std::string out;
for (size_t pos = 0; pos < data.size();) {
size_t take = std::min<size_t>(3072, data.size() - pos);
out += base64_encode(data.substr(pos, take));
pos += take;
}
return out;
}
हर चंक आज़ादी से एन्कोड होता है और जोड़ वन-शॉट नतीजे से बराबर है - यही गुण चंक-बनाई को बिल्कुल सुरक्षित बनाता है, और यह सीधा 3-बाइट ग्रुपिंग से निकलता है। (एन्कोडर सेक्शन का OpenSSL स्ट्रीमिंग कॉन्टेक्स्ट वही काम करता है और 64-चर लाइन रैपिंग फ्री में जोड़ देता है, जो सही टूल है जब उपभोक्ता MIME आकार चाहता हो।) और आउटपुट पक्ष की बजट इनपुट पक्ष जैसी ही है: 10 GB फ़ाइल 13.3 GB स्ट्रिंग बन जाती है, इसलिए बफ़र - या वह फ़ाइल जो आप लिख रहे हैं - गणित वाले सेक्शन के सूत्र से साइज़ होती है, और int छत कहती है कि 2 GB से ऊपर चंक वाली राह सुविधा नहीं - वह एकमात्र राह है।
एनवायरनमेंट वेरिएबल और कमांड लाइन
एनवायरनमेंट वेरिएबल की JSON स्ट्रिंग की ही परेशानी है और जवाब और ख़राब: वे NUL बाइट्स बिल्कुल नहीं उठा सकते, और control चर भी उनके मित्र नहीं। स्टैंडर्ड ट릭 पेलोड को base64 करके शेल में बचाना है, और C++ में एन्कोडिंग दिशा एक-लाइनर है:
#include <cstdio>
#include <cstdlib>
#include <string>
/* "चालीस लाइनें, शून्य डिपेंडेंसी" सेक्शन से base64_encode */
int main() {
setenv("MY_PAYLOAD", base64_encode("hello, env").c_str(), 1);
std::printf("env: %s\n", getenv("MY_PAYLOAD"));
}
जो वैल्यू एनवायरनमेंट में उतरेगी वह है aGVsbG8sIGVudg==: शुद्ध वर्णमाला, शेल के लिए सुरक्षित, .env फ़ाइल के लिए सुरक्षित, CI डैशबोर्ड के लिए सुरक्षित, और हर मशीन पर डिकोड योग्य जहाँ base64 डिकोडर है। कमांड लाइन स्वयं डिकोडिंग पक्ष की वही दो-टूल कहानी है, एन्कोडिंग-दिशा फ्लैग्स के साथ: coreutils का base64 (या uutils री-इम्प्लीमेंटेशन जो नए डिस्ट्रो शिप करते हैं; base64 --version से जाँचें) डिफ़ॉल्ट में 76 पर रैप करता है, और -w 0 एक लाइन देता है; openssl base64 - enc प्रोग्राम जो अपना नाम argv[0] में जाँचता है और base64 मोड में बदल लेता है - 64 पर रैप करता है और एक लाइन के लिए -A लेता है:
# एक लाइन, टोकन और कॉन्फ़िग के लिए
base64 -w 0 < payload.bin > payload.b64
openssl base64 -A < payload.bin > payload.b64
# लपेटी हुई, ईमेल और टेक्स्ट फ़ाइलों के लिए
base64 < payload.bin > payload-76.b64
openssl base64 < payload.bin > payload-64.b64
न कोई base64url बिल्ट-इन बोलता है, इसलिए शेल में बने टोकन URL में जाने से पहले ट्रांसकोड ट्रीटमेंट पाता है। और कमांड लाइन वही जगह है जहाँ एन्कोडिंग पक्ष की चुप-फेलियर आदत सबसे ख़तरनाक है: एन्कोडर जो तब रैप कर दे जब आपके उपभोक्ता को एक लाइन की उम्मीद हो, वह एरर नहीं करेगा, बस न्यूलाइन वाली स्ट्रिंग पैदा करेगा - जो बिल्कुल वह फेलियर है जिसे आप अब प्रोडक्शन में ढूँढ रहे हैं। कुछ भी जो महत्व रखता हो, अपने प्रोग्राम में एन्कोड कीजिए, जहाँ बफ़र सूत्र से साइज़ होती है और लाइन का आकार वह वैरिएबल है जो आप control करते हैं।
फंदे: C++ संस्करण
- वह NUL जिसका आपने ऑर्डर नहीं दिया।
EVP_EncodeBlockपेलोड के बाद NUL टर्मिनेटर जोड़ता है। मैन पेज का उदाहरण: 16 बाइट्स अंदर, 24 एन्कोडेड + NUL, बफ़र में 25, लौटे 24। अतिरिक्त बाइट के लिए साइज़ करें और लौटी वैल्यू पर resize करें, वरना आपका टोकन ज़ीरो बाइट पर ख़त्म होगा। - कठोर 64। OpenSSL स्ट्रीमिंग API 64 चर पर रैप करता है, हर ब्लॉक न्यूलाइन पर ख़त्म होता है, और इसे बदलने की कोई फ्लैग नहीं है। एक-लाइन उपभोक्ता में रैप एन्कोडर आउटपुट control-चर बग है।
- 48-बाइट ब्लॉक।
EVP_EncodeUpdateबस पूरे 48-बाइट इनपुट ब्लॉक के लिए आउटपुट निकालता है; बचतEVP_EncodeFinalतक कॉन्टेक्स्ट में बैठती है। हर ब्लॉक के लिए 65 आउटपुट बाइट्स + NUL की बजट करें, और*outlको "मेरे पेलोड के बाइट्स" की तरह न पढ़ें - यह वे बाइट्स हैं जो यह कॉल लिखी, और छोटी पहली कॉल के लिए वह शून्य है। - इटेरेटर के ग़ायब पैड। Boost.Serialization चेन कभी
=नहीं निकालती। 2002 का इटेरेटर "Mane" एन्कोड करके छह चर देता है। पैड आप खुद जोड़ें, वरना आपका सख़्त उपभोक्ता स्ट्रिंग मना कर लेगा। - वह CRLF जिसकी आपने माँग नहीं की।
CryptBinaryToStringACRYPT_STRING_NOCRLFपास करने तक CR/LF जोड़ा जोड़ता है। डिफ़ॉल्ट फ्लैग्स से बना base64 टोकन जितना होना चाहिए उससे दो चर लंबा है, और आख़िरी से एक पहले का चर कैरिएज रिटर्न है। - NULL कॉल NUL को गिनती है। Windows की साइज़ प्रोब NUL टर्मिनेटर समेत ज़रूरी लंबाई लौटाती है; असली कॉल लंबाई बिना उसकी लौटाती है। दोनों को उलझाना क्लासिक off-by-one है, और यह बफ़र के पार एक बाइट लिखता है या आख़िरी चर गँवा देता है।
- रैप बाद में, बीच में नहीं। लाइन रैपिंग एन्कोडेड स्ट्रिंग पर बाद-प्रोसेसिंग कदम है। चौड़ाई के गुणजों पर काटें - हमेशा सुरक्षित, क्योंकि हर 4-चर ग्रुप स्वयं-पर्याप्त है - और raw बाइट्स कभी न रैप करें, लाइन ब्रेक का घर वहाँ नहीं है।
- तीन-तीन के चंक। अगर बड़ा पेलोड टुकड़ों में एन्कोड करते हैं, तो टुकड़ों की बाउंड्री 3-बाइट ग्रुप पर गिरनी चाहिए, वरना ग्रुपिंग - और आउटपुट - बदल जाती है। 3072 दोस्ताना चंक है; 3071 बग।
- int, size_t नहीं। हर EVP लंबाई पैरामीटर
intहै। एक-कॉल की छत लगभग 2 GB इनपुट है, और उस इनपुट का आउटपुटintमें बिल्कुल नहीं फिट होता। छत से ऊपर चंक वाली या स्ट्रीमिंग राह पसंद नहीं, ज़रूरत है। - Signed char। अगर आप unsigned cast के बिना
char *से पैक करते हैं, तो 127 से ऊपर बाइट उन प्लेटफ़ॉर्म पर ऋणात्मक नंबर है जहाँcharsigned है, और इससे टेबल इंडेक्स करना undefined behavior है।const unsigned char *कोई रस्म नहीं है। - रैप JSON स्ट्रिंग। 76-चर रैप किया वैल्यू जो JSON फ़ाइल में पेस्ट हो वह असली control चरों की स्ट्रिंग है। या तो यह एक लाइन है, या एस्केप है, या JSON में ही नहीं है।
- पैड करार हैं। कुछ उपभोक्ता पैडिंग चाहते हैं (MIME, अधिकांश डिकोडर), कुछ नहीं (JWT, PKCE, URL में टोकन), और कुछ सख़्त ग़ायब या अन-कैनोनिकल पैड को सीधे मना कर देते हैं। पैड सजावट नहीं है; वह फ़ॉर्मेट करार का हिस्सा है।
- दो वर्णमालाएँ। स्टैंडर्ड-वर्णमाला पेलोड में
-या_अवैध है, और URL-सेफ़ में+या/। वर्णमालाएँ बाइट स्तर पर एक-दूसरी की जगह नहीं बनतीं - मंज़िल के लिए सही वर्णमाला से एन्कोड कीजिए, और ट्रांसकोड जान-बूझकर कीजिए। - std::string और strlen।
std::stringज़ीरो बाइट्स खुशी से उठाता है, लेकिन जैसे ही आप C string को legacy API में देते हैं,strlenपहले NUL पर रुक जाता है। पॉइंटर और लंबाई पास करें, कभी अकेला पॉइंटर नहीं। - बजट। आउटपुट इनपुट का 4/3 है: अगर इनपुट 1.5 GB है तो आउटपुट 2 GB - जो
intछत भी है। पाठक बफ़र, कॉलम, और वायर की साइज़ सूत्र से करें, अनुमान से नहीं।
C++ को अपना Base64 कैसे मिला
फ़ॉर्मेट का इतिहास भाषा के आधुनिक युग से पुराना है, और C++ की कहानी वही है कि भाषा बार-बार इसे शिप नहीं करती। जिस एन्कोडिंग को अब MIME base64 कहते हैं उसका पहला मानकीकृत इस्तेमाल Privacy-Enhanced Mail प्रोटोकॉल था, जो 1987 में 64-चर लाइनों और आख़िर में चिपकाए RSA-MD2/MD5 message-integrity check के साथ प्रस्तावित हुआ; "base64" नाम स्वयं 1993 में आया, जब MIME स्टैंडर्ड्स ने उसे नाम दिया। C++ 1998 में C++98 के रूप में आया - MIME के पाँच साल बाद - और भाषा के डेवलपर्स की पहुँच का पहला base64 कोड Rene Nyffenegger का 2004-2008 का C जोड़ा था, जिसे 4 दिसंबर 2008 का एक Stack Overflow सवाल पूरे वेब पर फैला दिया। उस कहानी का सबसे प्यारा हिस्सा: एक जवाब ने Nyffenegger के अपने पेज की लिंक की और इम्प्लीमेंटेशन उठाकर लाया, लाइसेंस हेडर समेत, और एक टॉप-वोटेड जवाब ने उनके हल का बाक़ी क्षेत्र के मुकाबले बेंचमार्क की। फ़ॉल्क गाने का लाइसेंस हेडर है - संगीतकार स्वयं कभी कमेंट्स में हाज़िर नहीं हुए।
फिर इकोसिस्टम ने वही किया जो इकोसिस्टम करते हैं। 2002 में Robert Ramey के Boost.Serialization ने इटेरेटर एडैप्टर शिप किए - C++ टूलबॉक्स का सबसे पुराना base64, डिकोडिंग दिशा में सख़्त और एन्कोडिंग दिशा में फ़ामस बिना-पैड, एक साल पहले कि RFC 3548 ने उन वर्णमाला नियमों को पक्का किया जो वह पहले से लागू कर रहा था। 2017 में Boost 1.66 ने Beast लाया, और उसके साथ वह हेडर-ऑनली कोडेक जो आज भी शिप हो रही है, अपने फुटर में Nyffenegger के श्रेय के साथ। OpenSSL का EVP_EncodeBlock और साथी हर OpenSSL रिलीज़ में रहे हैं, इसलिए वर्कहॉर्स टूलबॉक्स में उतना पुराना है जितना भाषा "क्या इसे स्टैंडर्ड में होना चाहिए" पर बहस कर रही है। Windows पर कहानी बस यह है कि ऑपरेटिंग सिस्टम ने इसे शिप कर दिया: एक फ़ंक्शन, एक फ्लैग टेबल, कोई स्टैंडर्ड बिल्कुल नहीं। इस बीच स्टैंडर्ड स्वयं C++11, C++14, C++17, C++20, C++23 (2024 में प्रकाशित), और अब C++26 गया, और इनमें से हर एक ने 64-चर वर्णमाला को देखकर आगे निकल गया। C++26 का तकनीकी सामग्री ख़त्म हुई और मार्च 2026 की Croydon (UK) में ISO C++ मीटिंग में वोट से पार हुई (114-12-3), और वह टेक्स्ट कोडेक के काम के लिए नया <text_encoding> हेडर सच में जोड़ता है; कमेटी की अगली मीटिंग, जून 2026 (Brno) और नवंबर 2026 (Búzios, Brazil), C++26 को दोबारा नहीं देखकर C++29 वर्किंग ड्राफ़्ट खोलती हैं। Base64 स्टैंडर्ड में नहीं है। आठ स्टैंडर्ड, तीन दशक, टेक्स्ट एन्कोडिंग का एक हेडर - और कमेटी के पास अब base64 जोड़ने के हर संभव बहाने थे और उसने सारे छोड़ दिए। C++ में base64 का अमली इतिहास है, और बना हुआ है, उसकी लाइब्रेरियों का इतिहास: एक EVP जोड़ा, दो Boost रूप, एक Windows फ्लैग, और चालीस-लाइन स्निपेट जो आपका है।
जानने लायक़ अजीब बातें
- OpenSSL स्ट्रीमिंग एन्कोडर का 48-बाइट ब्लॉक वह नंबर है जो किसी RFC में नहीं आता। यह 16 base64 ग्रुप हैं, इस चयन से कि आउटपुट लाइन बिल्कुल 64 चर हो - PEM की आदत - और यह उन आख़िरी जगहों में से है जहाँ 1987 आज भी 2026 में load-bearing काम कर रहा है।
- Boost.Beast का
encoded_sizeगणित वाला सेक्शनconstexprफ़ंक्शन के रूप में है:4 * ((n + 2) / 3), कंपाइल-टाइम पर गिना जाता है जब आप इसे constant दें। स्टैंडर्ड लाइब्रेरी को यह एक-लाइनर कभी नहीं मिला; Boost ने इसेdetail::namespace में शिप किया। - सबसे छोटा पैडेड base64 चार चर हैं,
QQ==: एक बाइट दो-चर कॉस्ट्यूम में। सबसे छोटा बिना-पैड दो चर हैं,QQ। पैड की गिनती भी संदेश है: दो पैड का मतलब आख़िरी ग्रुप में एक बाइट थी, एक पैड का मतलब दो, और बिना पैड का मतलब तीन - पाठक टेल से अकेले इनपुट लंबाई बहाल कर सकता है। - MIME का ओवरहेड गणित सटीक है: 4/3 गुणा 78/76, इसीलिए ईमेल एटैचमेंट मूल साइज़ का लगभग 137 फ़ीसदी पहुँचता है, साथ में लगभग 814 बाइट्स हेडर। इस लेख का हर एन्कोडर वही टैक्स देता है; रैप चौड़ाई बस यह बदलती है कि वह कैसे बिल होता है।
- आम libstdc++ या MSVC पर,
std::stringछोटे पेलोड्स को अलोकेशन करने के बजाय small-string optimization से स्टैक बफ़र में उठाता है। 9-बाइट इनपुट एन्कोड होकर 12 चर बनता है और हीप को छूता भी नहीं। आपके टोकन का base64 रूप सचमुच स्टैक फ्रेम में बस सकता है, जो वह फ्री लंच है जिसे स्टैंडर्ड लाइब्रेरी प्रचार नहीं करती। - शेल में जो
openssl base64कमांड आप हाथ बढ़ाते हैं वह बस कमांड ही नहीं है। वहencप्रोग्राम है जो अपना नामargv[0]में जाँचता है और व्यक्तित्व बदल लेता है। स्ट्रिंग तुलना से एलियस, जो C++ की चीज़ें करने की शैली है, C में। - YouTube वीडियो आइडेंटिफ़ायर base64url हैं: ग्यारह चर, बिना पैडिंग, URL के करीब कहीं भी
+या/नहीं। धरती का सबसे ज़्यादा देखा गया एन्कोडिंग फ़ॉर्मेट उस "URL और फ़ाइल-नाम सेफ़" रूप पर चलता है जिसे RFC 4648 ने एक ऐसे सेक्शन में जोड़ा जो एक पेज में फिट होता है। - चार A -
AAAA- तीन ज़ीरो बाइट्स एन्कोड करते हैं, क्योंकि A वर्णमाला का ज़ीरो है। अगर आपने कभी एक चर से बिलकुल बना base64 ब्लॉब देखा है, अब आपको पता है वह क्या कह रहा था: कुछ नहीं। - वही फ़ंक्शन जोड़ा 2008 के Stack Overflow सवाल के जवाबों में, Boost.Beast के स्रोत में श्रेय-फुटर के साथ, और अगिनती प्राइवेट कोडबेस के हेडर फ़ाइलों में दिखता है। किसी C++ डेवलपर से पूछिए कि उसका base64 कहाँ से आया तो सबसे ईमानदार जवाब है "मैं नहीं जानता, और इंटरनेट को भी नहीं"।
दूसरी दिशा
जिस सब की आपने बस पैकिंग की वह दूसरी तरफ़ वही टूलबॉक्स अनपैक करेगा, और अनपैकिंग पक्ष के पास अपने अलग सेट की आदतें हैं: वन-शॉट OpenSSL फ़ंक्शन जो अपनी टेल को ज़ीरो से भरता है, 2025 का बगफिक्स जिसने पैडेड इनपुट पर स्ट्रीमिंग डिकोडर के लौटाने को बदल दिया, Boost.Beast डिकोड जो एक बिखरा चर पर रुकता है और एक शब्द नहीं बोलता, इटेरेटर जो एक स्पेस पर फेंक देता है, और चालीस-लाइन सख़्त डिकोडर जो वही बाइट बताता है जो चोट पहुँचाती थी। पूरी अनपैकिंग कहानी - चारों डिकोडरों के मूड, base64url ट्रांसकोडिंग, फ़ाइलें, MIME की 76-चर आदत, और दो कमांड-लाइन टूल जो चुपचाप फेल होते हैं - sister साइट की C++ डिकोडिंग गाइड में बसी है। जाइए पढ़िए, फिर लौटकर कुछ बड़ा पैक कीजिए। यही पूरा खेल है: कोई स्टैंडर्ड लाइब्रेरी नहीं, चार वेंडर जिनके न्यूलाइन और NUL पर चार अलग ख़्याल हैं, एक सूत्र जो लेख के हर बफ़र की साइज़ करता है, और एक 33 फ़ीसदी टैक्स जो हर पाठक रिफंड पाता है। पैकिंग मज़े की।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: C++ (Cpp) में Base64 डिकोडिंग: एक सम्पूर्ण गाइड