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

C++ (Cpp) में Base64 डिकोडिंग: एक सम्पूर्ण गाइड

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

बड़ी ख़बर यह है कि C++ खुद आपके लिए एक अक्षर भी डिकोड नहीं करेगा। स्टैंडर्ड लाइब्रेरी को base64 फ़ंक्शन पालने के तीस साल मिल चुके हैं, और उसने तीसों साल किसी और काम में लगा दिए हैं, इसलिए हर C++ प्रोग्राम अपना डिकोडर तीन बेहद अलग व्यक्तित्वों की मंडली से लेकर आता है, और साथ में लगभग चालीस लाइनें खुद लिखने का विकल्प भी मिलता है। एक वर्कहॉर्स है जो 1990 के दशक से इंटरनेट को उठाए चल रहा है, एक है जो काम आधे वाक्य में ही छोड़ देता है और इसका एक शब्द भी ज़िक्र नहीं करता, और एक इतना सख़्त कि एक अतिरिक्त स्पेस देखते ही एक्सेप्शन फेंक देता है। जैसे ही आपको पता चलता है कि हर एक क्या माफ़ करता है, क्या नहीं करता, और आपकी पीठ पीछे चुपचाप क्या करता है, डिकोडिंग रहस्यमय बगों का स्रोत बंद हो जाती है। तो चलते हैं, कुछ पैकेजेस खोलते हैं।

टूलबॉक्स: चार डिकोडर, चार व्यक्तित्व

यह है एक नज़र में पूरा नज़ारा। चारों स्टैंडर्ड वर्णमाला को संभाल लेते हैं; फ़र्क़ किनारों में है, और बग यहीं किनारों पर बसे हैं।

डिकोडर कहाँ से आता है एरर मॉडल वह अजीब बात जो याद रखनी है
OpenSSL EVP <openssl/evp.h>, लिंक -lcrypto ख़राब इनपुट पर -1 रिटर्न करता है वन-शॉट वर्ज़न टेल को ज़ीरो से भर देता है
Boost.Beast <boost/beast/core/detail/base64.hpp>, हेडर-ओनली कुछ नहीं: बस रुक जाता है किसी तरह का एरर चैनल नहीं
Boost.Serialization इटेरेटर <boost/archive/iterators/binary_from_base64.hpp>, हेडर-ओनली dataflow_exception फेंकता है = को असली ज़ीरो वैल्यू मानता है
आपकी अपनी चालीस लाइनें कहीं नहीं: यह आपकी हैं आपकी पसंद, बाइट-पोज़िशन तक हर एज केस का हक़दार आप ही, हमेशा के लिए

इंस्टॉलेशन में हर डिस्ट्रो के लिए बस एक पैकेज का नाम। OpenSSL के लिए: Debian और Ubuntu पर libssl-dev, Fedora और RHEL पर openssl-devel, Arch पर openssl, और macOS पर brew install openssl। Boost, जिसकी मौजूदा रिलीज़ 1.92.0 है - अगस्त 2026 की, ऐसे प्रोजेक्ट की जिसने 1998 से लाइब्रेरी बना रहा है: libboost-dev या boost-devel। नीचे दिए दोनों Boost डिकोडर हेडर-ओनली हैं, इसलिए लिंक करने के लिए बिल्कुल कुछ नहीं है। अगर आपका प्रोजेक्ट CMake पर टिका है, तो पूरा सेटअप बस तीन लाइनें हैं:

find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)

कोड से पहले एक वर्ज़न का नोट, क्योंकि इससे बदल जाता है कि आपका डिकोडर क्या रिटर्न करता है। OpenSSL 3.5, जो अप्रैल 2025 में लॉन्ग-टर्म-सपोर्ट लाइन के तौर पर रिलीज़ हुई, ने स्ट्रीमिंग डिकोडर का एक असली बग ठीक किया (थाड़ी में और विस्तार से), और अप्रैल 2026 की नई 4.0 फीचर रिलीज़ ने वह फिक्स अपने साथ लिया। अगर आपका बिल्ड पुरानी 3.0 या 3.3 पर पिन है, तो किसी टेल लंबाई पर भरोसा करने से पहले नीचे वाले OpenSSL सेक्शन का वर्ज़न वाला पैराग्राफ़ पढ़ लीजिए।

OpenSSL: वह डिकोडर जो शायद आप पहले से ही लिंक करते हैं

अगर आपका C++ प्रोग्राम TLS, हैशिंग या सर्टिफिकेट्स से छूता है, तो OpenSSL पहले से बिनरी में मौजूद है, और उसके EVP base64 रूटीन पूरे इस खेल के सबसे ज़्यादा परखे हुए डिकोडर हैं। वन-शॉट फ़ंक्शन एक ही कॉल है:

int EVP_DecodeBlock(unsigned char *t, const unsigned char *f, int n);

इसे base64 चरों का बफ़र और उसकी लंबाई सौंपें, तो वह डिकोड किए बाइट्स t में लिख देता है। वह शुरू के व्हिटस्पेस काटता है, आख़िर के व्हिटस्पेस, न्यूलाइन और कैरिएज रीटर्न काटता है, और फिर बिना किसी समझौते के नियम लगाता है: अंदर का कोई व्हिटस्पेस नहीं, और ट्रिम की गई लंबाई चार का गुणज होनी चाहिए। हर चार इनपुट चर बिल्कुल तीन आउटपुट बाइट्स देते हैं, और यही वह हिस्सा है जो लोगों को चौंकाता है: पैडिंग चर छह ज़ीरो बिट्स में डिकोड होते हैं, और मैन पेज शांत स्वर में यही लिखती है कि टेल पैडिंग का ख़्याल रखना कॉलर की ज़िम्मेदारी है। दूसरे शब्दों में, फ़ंक्शन आपके लिए गणित कर रहा है, और फिर आख़िर में चुपचाप दो बाइट्स तक के बोनस ज़ीरो बाइट्स जोड़ देता है। आइडीयमैटिक C++ रैपर बफ़र गणित को std::string के पीछे छुपा देता है:

#include <cstddef>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>

std::string openssl_decode(const std::string &b64) {
  std::vector<unsigned char> out(b64.size() * 3 / 4 + 4);
  int n = EVP_DecodeBlock(out.data(),
                          reinterpret_cast<const unsigned char *>(b64.data()),
                          static_cast<int>(b64.size()));
  if (n < 0) return {};
  size_t pads = 0;
  for (size_t i = b64.size(); i > 0 && b64[i - 1] == '='; --i) ++pads;
  return std::string(reinterpret_cast<const char *>(out.data()),
                     static_cast<size_t>(n) - pads);
}

int main() {
  std::string one = openssl_decode("TQ==");
  std::printf("%zu bytes: %02x\n", one.size(),
              static_cast<unsigned char>(one[0]));
  std::string word = openssl_decode("TWFuZQ==");
  std::printf("%zu bytes: %s\n", word.size(), word.c_str());
}

चलाइए, और ज़ीरो-फिल बिल्कुल वहाँ दिखेगा जहाँ डॉक्यूमेंटेशन ने वादा किया था: चार चरों की स्ट्रिंग TQ== डिकोड करने पर तीन बाइट्स मिलती हैं - M अक्षर और दो ज़ीरो - यह सब रैपर उनके हटाने से पहले। उसे TWFuZQ== दें, तो आपको "Mane" के साफ़ चार बाइट्स मिलेंगे। वर्णमाला से बाहर का कोई भी चर दें, तो खाली स्ट्रिंग वापस मिलेगी, क्योंकि फ़ंक्शन ने -1 से जवाब दिया था। नोटिस करें कि इस रैपर में std::string चुपचाप क्या कर रहा है: वह अपनी लंबाई खुद ट्रैक करता है और ज़ीरो बाइट्स को खुशी-खुशी पकड़े रहता है, इसलिए एक डिकोड की गई JPEG आपके टेक्स्ट टाइप में रह सकती है - तुलना भी, हैश भी, आगे पकड़कर चलाना भी। C में आप लंबाई वाले एक वैरिएबल के लिए शुक्रिया अदा कर रहे होते; यहाँ स्ट्रिंग बस काम करती है।

जो डेटा टुकड़ों में आता है, उसके लिए OpenSSL आपको एक कॉन्टेक्स्ट देता है जिसमें आप चंक्स ढालते हैं और नतीजे बाहर निकालते हैं, और तीन फ़ंक्शनों की रिटर्न-वैल्यू की भाषा बड़ी संक्षिप्त है:

कॉल रिटर्न इसका मतलब
EVP_DecodeUpdate -1 अवैध चर, या डेटा के बीच में कोई पैड चर
EVP_DecodeUpdate 1 और इनपुट की उम्मीद है
EVP_DecodeUpdate 0 डेटा का आख़िर: आख़िरी ग्रुप में पैडिंग थी, या सॉफ्ट इनपुट-अंत मार्कर आ गया
EVP_DecodeFinal 1 / -1 स्ट्रीम साफ़ ख़त्म हुई / बचे हुए चरों की संख्या चार का गुणज नहीं थी

दो व्यवहार स्ट्रीमिंग डिकोडर को टूलबॉक्स का सबसे बड़ा दिल वाला बनाते हैं। वह स्पेस, टैब, कैरिएज रीटर्न और न्यूलाइन को स्ट्रीम में कहीं भी छलांग खाने देता है, इसलिए 76-चर CRLF लाइनों वाली MIME ईमेल ब्लॉक बिल्कुल वैसे ही बहती है जैसे एक लाइन की स्ट्रिंग, और वह सही बाइट काउंट रिपोर्ट करता है: वही TQ==, जिसने वन-शॉट फ़ंक्शन को फँसाया, यहाँ बिल्कुल एक बाइट देता है, किसी गणित की ज़रूरत नहीं। वह इनपुट को 64 base64 चरों के चंक्स में चबाता है, 80-बाइट के अंदरूनी बफ़र से काम लेता है, और जो भी चारों के ग्रुप में नहीं फिट होता वही बफ़र कर लेता है, यही वजह है कि आप उसे किसी भी आकार के टुकड़ों में ढाल सकते हैं। रैपर:

#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>

std::string openssl_decode_stream(const std::string &b64) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  EVP_DecodeInit(ctx);
  std::string out;
  std::vector<unsigned char> chunk(1024);
  int outl = 0;
  for (size_t pos = 0; pos < b64.size(); pos += chunk.size()) {
    size_t take = std::min(chunk.size(), b64.size() - pos);
    int ret = EVP_DecodeUpdate(ctx, chunk.data(), &outl,
                               reinterpret_cast<const unsigned char *>(b64.data()) + pos,
                               static_cast<int>(take));
    if (ret < 0) {
      EVP_ENCODE_CTX_free(ctx);
      return {};
    }
    out.append(reinterpret_cast<const char *>(chunk.data()), outl);
    if (ret == 0) break;
  }
  unsigned char tail[3];
  int tail_l = 0;
  int fin = EVP_DecodeFinal(ctx, tail, &tail_l);
  EVP_ENCODE_CTX_free(ctx);
  if (fin != 1) return {};
  out.append(reinterpret_cast<const char *>(tail), tail_l);
  return out;
}

और अब टूलबॉक्स टेबल का वह वर्ज़न फ़ुटनोट, क्योंकि यही बिल्कुल वही बात है जो "हल हो चुका" टिकट को फिर से खोल देती है: 3.5 से पहले की हर OpenSSL रिलीज़ में स्ट्रीमिंग पथ में ब्लॉक डिकोडर से उसी ज़ीरो-फिल की आदत थी। इसे फ़रवरी 2025 में इश्यू 26677 के तौर पर रिपोर्ट किया गया; फिक्स कमिट 27 फ़रवरी 2025 को master पर लैंड की, और जुड़ा हुआ पुल रिक्वेस्ट उसी दिन बंद हुआ। आधिकारिक मैन पेज अब इसे अपनी history सेक्शन में नोट करती है: OpenSSL 3.5 से आगे, EVP_DecodeUpdate उतने बाइट्स देता है जितने डॉक्यूमेंटेशन हमेशा दावा करता रहा है, और पैडिंग को ज़ीरो बिट्स में डिकोड करना बंद कर देता है। अगर आपके कोडबेस में पुरानी OpenSSL पिन है और आपकी टेल लंबाईएँ एक-दो बाइट्स अटूट लग रही हैं, तो यह ही सबसे पहले जाँचने वाली चीज़ है। और PEM युग से मिले-जुले एक अजीबपन भी है: हाइफ़न - बिल्कुल वर्णमाला का चर नहीं है - यह सॉफ्ट इनपुट-अंत मार्कर है। अगर आपकी स्ट्रीम में चार के गुणज वैध चरों के बाद एक हो, तो डिकोडर 0 रिटर्न करता है और आपको रुकने को कहता है, यही वजह है कि एक base64url स्ट्रिंग, जिसकी - चरों की वाकई ज़रूरत हो, बस ऐसे ही डिकोड नहीं होगी - पहले नीचे वाले सेक्शन में ट्रांसकोड करें, और टाइम ट्रैवेलर गायब हो जाएगा।

Boost.Beast: वह तेज़ डिकोडर जो कभी शिकायत नहीं करता

अगर Boost पहले से प्रोजेक्ट में है, तो उसकी HTTP लाइब्रेरी एक base64 कोडेक को इस अजीब पते पर शिप करती है: boost/beast/core/detail/base64.hpp। detail:: नैमस्पेस Boost का यह कहने का तरीका है कि "यह हमारा अंदरूनी काम है", और मेइन्टेनर्स ने इसे public API बनाने से इनकार कर दिया है। बावजूद इसके हर कोई इसे इस्तेमाल करता है, क्योंकि वह छोटा है, तेज़ है, और हेडर-ओनली है: include से पहले BOOST_BEAST_HEADER_ONLY define करें, और लिंक करने के लिए कुछ नहीं। और हाँ, यह वही कोडेक भी है जो Boost.Beast का अपना WebSocket हैंडशेक Sec-WebSocket-Accept की गणना के लिए इस्तेमाल करता है, इसलिए यह सालों से असली ट्रैफ़िक चबा रहा है।

डिकोडिंग फ़ंक्शन का व्यक्तित्व ही चौंकाने वाली बात है। वह आपका आउटपुट बफ़र लेता है, इनपुट, और उसकी लंबाई, और एक जोड़ी रिटर्न करता है: कितने ऑक्टेट लिखे गए, और कितने चर पढ़े गए। वह पहले = पर रुक जाता है, और पहले अवैध चर पर - और दोनों मामलों में बिना आपको बताए। कोई एरर कोड नहीं, कोई एक्सेप्शन नहीं, कोई स्टेटस फ्लैग नहीं। ख़राब पेयलॉड, लाइन-रैप पेयलॉड, और कटी हुई टेल - तीनों सफल आंशिक नतीजा देते हैं:

इनपुट डिकोड किए बाइट्स पढ़े गए चर क्यों रुका
"TWFuZQ==" 4 बाइट्स "Mane" 6 पहले = पर रुका
"TWF!ZQ==" 2 बाइट्स "Ma" 3 ! पर रुका
"TWFu\nZQ==" 3 बाइट्स "Man" 4 \n पर रुका
"TWFuZQ" 4 बाइट्स "Mane" 6 टेल कटी हुई थी
"TQ==" 1 बाइट "M" 2 पैडिंग ठीक से संभाली गई

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

#define BOOST_BEAST_HEADER_ONLY
#include <boost/beast/core/detail/base64.hpp>
#include <cstddef>
#include <string>

namespace b64 = boost::beast::detail::base64;

std::string beast_decode(const std::string &in) {
  std::string out;
  out.resize(in.size() / 4 * 3 + 3); /* विषम लंबाइयों के लिए अतिरिक्त जगह */
  auto result = b64::decode(out.data(), in.data(), in.size());
  out.resize(result.first);
  size_t pads = 0;
  for (size_t i = in.size(); i > 0 && in[i - 1] == '='; --i) ++pads;
  if (result.second + pads != in.size() || in.size() % 4 != 0)
    return {}; /* यह जल्दी रुक गया, या टेल असंभव थी */
  return out;
}

दो बातें याद कर लीजिए। पहली, decoded_size(n) हेल्पर, जिसकी तरफ हेडर आपकी उँगली करती है, तभी एक वैध अपरी सीमा है जब n चार का गुणज हो - फ़ंक्शन की अपनी ही टिप्पणी यही कहती है - यही वजह है कि ऊपर का रैपर किसी भी लंबाई के लिए उसके पर भरोसा करने के बजाय कुछ बाइट्स की अतिरिक्त जगह जोड़ देता है। दूसरी, यह कि स्रोत फ़ाइलें Vinnie Falco की 2016-2019 कॉपीराइट वाली हैं, और एक फुटर में कुछ हिस्सों का श्रेय Rene Nyffenegger की 2004-2008 स्निपेट को दिया गया है। वह स्निपेट वही base64 जोड़ी है जो दो दशकों से अंग्रेज़ी-बोलने वाले इंटरनेट में कॉपी-पेस्ट होती रही है, और अब वह Boost के अंदर शिप हो रही है, आपकी बिनरी में, पूरे वेब के लिए HTTP Basic auth कर रही है।

Boost.Serialization: वह डिकोडर जो एक अतिरिक्त स्पेस देखते ही एक्सेप्शन फेंकता है

Boost की serialization लाइब्रेरी C++ एकोसिस्टम का सबसे पुराना base64 संभालती है: 2002 का संयोज्य इटेरेटर एडैप्टरों का एक सेट, Robert Ramey की रचना, जो "जो मिले, उसे उदारता से स्वीकारो" वाले सुझाव को व्यक्तिगत अपमान मानती है। डिकोडिंग की दिशा binary_from_base64.hpp में बसती है (हाँ, नाम आउटपुट की नज़र से है), और साथ में एक चौड़ाई ट्रांसफ़ॉर्मर जोड़ी जाती है जो छह-बिट वैल्यूज़ को आठ-बिट बाइट्स में दोबारा पैक करता है:

#include <boost/archive/iterators/binary_from_base64.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <cstddef>
#include <string>

namespace it = boost::archive::iterators;

std::string boost_decode(const std::string &in) {
  using dec =
      it::transform_width<it::binary_from_base64<const char *>, 8, 6>;
  std::string out(dec(in.data()), dec(in.data() + in.size()));
  size_t pads = 0;
  for (size_t i = in.size(); i > 0 && in[i - 1] == '='; --i) ++pads;
  out.resize(out.size() - pads);
  return out;
}

अंदरूनी इटेरेटर हर base64 चर को उसकी छह-बिट वैल्यू में बदलता है, और बाहरी इटेरेटर उन वैल्यूज़ को दोबारा बाइट्स में सजाता है। उसकी सख़्ती पूरी तरह की है: वर्णमाला से बाहर कोई भी चर - एक स्पेस समेत - इटेरेटर को boost::archive::iterators::dataflow_exception फेंकने पर मजबूर कर देता है, मैसेज के साथ "base64 चर सेट में न होने वाली वैल्यू को डिकोड करने का प्रयास"। यही वह "मना करो, जब तक कोई कुछ और न कह दे" व्यवहार है जिसे बाद में RFCs ने स्पष्ट बनाया - 2002 में इम्प्लीमेंट हुआ, एक पूरा साल पहले, जब RFC 3548 उसी नियम को कोड करके आया था। अमली नतीजा यह है कि MIME-रैप इनपुट को इस इटेरेटर को छूने से पहले उसके लाइन ब्रेक हटाने पड़ते हैं। दूसरा अजीबपन थोड़ा सूक्ष्म है: लुकअप टेबल में पैडिंग चर = को छोड़ा नहीं जाता - वह ज़ीरो वैल्यू के तौर पर रेंडर होता है। इसलिए TWFuZQ== डिकोड करने पर छह बाइट्स बनती हैं - 4d 61 6e 65 00 00 - क्योंकि दोनों पैड चरों ने असली (ज़ीरो) डेटा दिया, और स्निपेट की resize(size - pads) लाइन भार-संभालने वाली है, सजाने वाली नहीं। TQ== डिकोड करें, तो तीन बाइट्स मिलेंगी जो एक ही अक्षर M तक ट्रीम हो जाती हैं, बिल्कुल वहाँ जहाँ आपको लैंड करना है।

चालीस लाइनें, जो आपकी हैं

Base64 इतना छोटा है कि एक सही डिकोडर स्वयं रखना सम्मानजनक काम है, और C++ में इसका फ़ायदा किसी और भाषा से अच्छा है: std::string बफ़र प्रबंधन को आरामदेह बना देता है, और हाथ से बने डिकोडर एक ऐसी बात कर सकता है जो ऊपर की लाइब्रेरी वर्ज़न में से कोई नहीं कर सकता - उसी बाइट पर उँगली उठाना जिसने सुनाई। यह वर्ज़न RFC 4648 के सख़्त पठन का पालन करता है - दोनों सिर ट्रिम करो, अंदर का व्हिटस्पेस मना करो, बीच की पैडिंग मना करो, लंबाई के नियम लागू करो, और यह तक जाँच लो कि वे पैड बिट्स, जिन्हें RFC के अनुसार एक मानक-अनुकूल एन्कोडर को ज़ीरो करना होता है, सच में ज़ीरो हैं:

#include <cstddef>
#include <cstring>
#include <string>

std::string strict_decode(const std::string &in, size_t *error_pos = nullptr) {
  static const char *table =
      "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  auto fail = [&](size_t pos) {
    if (error_pos) *error_pos = pos;
    return std::string();
  };
  size_t start = 0;
  size_t end = in.size();
  while (start < end && (in[start] == ' ' || in[start] == '\t' ||
                         in[start] == '\r' || in[start] == '\n'))
    ++start;
  while (end > start && (in[end - 1] == ' ' || in[end - 1] == '\t' ||
                         in[end - 1] == '\r' || in[end - 1] == '\n'))
    --end;
  size_t pads = 0;
  while (end > start && in[end - 1] == '=') {
    --end;
    ++pads;
  }
  size_t body = end - start;
  if (pads > 2 || (pads == 1 && body % 4 != 3) ||
      (pads == 2 && body % 4 != 2) || (pads == 0 && body % 4 == 1))
    return fail(in.size());
  if (body >= 2 && body % 4 != 0) {
    int leftover = static_cast<int>((body % 4) * 6 % 8);
    int last = static_cast<int>(std::strchr(table, in[end - 1]) - table);
    if ((last & ((1 << leftover) - 1)) != 0)
      return fail(end - 1); /* ग़ैर-मानक पैडिंग बिट्स */
  }
  int value = 0;
  int bits = -8;
  std::string out;
  out.reserve(body / 4 * 3);
  for (size_t i = start; i < end; ++i) {
    const char *p = std::strchr(table, in[i]);
    if (!p)
      return fail(i);
    value = (value << 6) + static_cast<int>(p - table);
    bits += 6;
    if (bits >= 0) {
      out.push_back(static_cast<char>((value >> bits) & 0xFF));
      bits -= 8;
    }
  }
  return out;
}

देखिए कि यह क्या लागू करता है। शुरू और आख़िर का व्हिटस्पेस ट्रिम हो जाता है, क्योंकि ईमेल हेडर से कॉपी किया पेयलॉड बहुत संभावना से उसी के साथ आता है। अंदर का व्हिटस्पेस मना है, क्योंकि RFC 4648 कहता है कि डिकोडर को MUST वर्णमाला से बाहर के चर मना करने होंगे, जब तक कि आस-पास की विनिर्देश कुछ और न कहती हो, और सुरक्षा बाउंड्री पर आपको सख़्त पठन चाहिए। डेटा के बीच में आया पैड चर मना है, और वही हर ऐसी लंबाई जिसे असली पेयलॉड से मेल नहीं खाया जा सकता: एक ग्रुप से एक चर कम होना असंभव है, और एकल पैड बस तीन बॉडी चरों के पीछे ही क़ानूनी है। आख़िर की कैनोनिकल जाँच वह है जिसे ज़्यादातर इम्प्लिमेंटेशन छोड़ देते हैं: अगर आख़िरी ग्रुप में एक या दो पैड चर थे, तो आख़िरी वर्णमाला चर के न-इस्तेमाल लो बिट्स ज़ीरो होने चाहिए, वरना वही बाइट्स दो साफ़ अलग दिखने वाली स्ट्रिंग्स के रूप में लिखी जा सकती हैं। यही लचीलापन है जिसकी वजह से यह जाँच मौजूद है, और इसकी कीमत चार लाइनें हैं। आख़िर में, डिकोडर बिना पैडिंग वाले इनपुट को स्वीकार करता है, जो कि बिल्कुल वही है जो JWT सेगमेंट होते हैं। और असफलता पर वह आपको पोज़िशन हाथ में देता है: उसे TWF!ZQ== दें, तो एरर इंडेक्स 3 पर बैठता है, ! चिह्न के ऊपर - और यही वह फ़र्क़ है जो बग रिपोर्ट और फिक्स के बीच होता है।

Base64url: वह वर्णमाला जिसकी भाषा टोकन बोलते हैं

स्टैंडर्ड वर्णमाला के दो ऐसे चर हैं जो URL में बचकर नहीं रहते: क्वेरी स्ट्रिंग में + का मतलब स्पेस, और पथ में / का मतलब डायरेक्टरी। RFC 4648 का सेक्शन 5 इसे दो चर-बदलावों से ठीक करता है - + बनता है -, और / बनता है _ - और नतीजे के बारे में सीधा कहता है: इस एन्कोडिंग को "base64 एन्कोडिंग के बराबर नहीं माना जाना चाहिए"। यह JWTs, OAuth PKCE कोड challenges, YouTube वीडियो आइडेंटिफ़ायर, और ज़्यादातर API टोकन की वर्णमाला है, और यह = पैडिंग को भी आसानी से छोड़ देती है, क्योंकि टोकन में लंबाई ख़ुद-ब-ख़ुद मालूम होती है, और पैडिंग बस पर्सेंट-एस्केप बनने का इंतज़ार करती रहेगी। ऊपर के C++ डिकोडर में से कोई भी इसे नेटिव रूप में नहीं बोलता - OpenSSL तो - को उस सॉफ्ट इनपुट-अंत मार्कर तक मानता है - इसलिए इलाज है डिकोड करने से पहले एक छोटी ट्रांसकोड। यह इतनी छोटी है कि उसे सिर में रखना आसान है:

#include <string>

std::string url_to_standard(std::string in) {
  for (char &c : in) {
    if (c == '-') c = '+';
    else if (c == '_') c = '/';
  }
  switch (in.size() % 4) {
    case 2: in += "=="; break; /* छूटी हुई पैडिंग बहाल करें */
    case 3: in += '=';  break;
    default: break;
  }
  return in;
}

इसे असली JWT पर लगाइए, तो पहले दो सेगमेंट सादा JSON बनने का इंतज़ार करते हैं। एक क्लासिक उदाहरण टोकन का हेडर {"alg":"HS256","typ":"JWT"} में डिकोड होता है, और claims का एक सेट, जिसमें subject, name, और issued-at टाइमस्टैम्प शामिल है। तीसरा सेगमेंट भी वैसे ही डिकोड होता है और आपको raw सिग्नेचर बाइट्स देता है - न टेक्स्ट, और न किसी बात का सबूत। टोकन डिकोड करने से आपको पता चलता है कि वह क्या दावा करता है; सिग्नेचर सत्यापित करने से पता चलता है कि उसे मानना है या नहीं, और यह क्रिप्टोग्राफ़ी का काम है जो कोई base64 लाइब्रेरी आपके लिए नहीं करेगी। Windows पर हालत उसी दिशा में एकतरफ़ा है: CryptBinaryToStringA के पास एन्कोडिंग साइड के लिए CRYPT_STRING_BASE64URI फ्लैग है, लेकिन डिकोडिंग की दिशा में URL-safe फ्लैग की बिल्कुल कमी है, इसलिए ऊपर वाली ट्रांसकोड हर प्लेटफ़ॉर्म पर आपके मस्क्ल मेमोरी में अपनी जगह कमाती है।

बाइट्स से वापस टेक्स्ट तक

C++ डिकोडर से पूछिए कि "मैंने अभी कौन-सा चरसेट डिकोड किया?", तो आपको इस भाषा का सबसे ईमानदार जवाब मिलेगा: कुछ नहीं। डिकोडर शुरू से आख़िर तक बाइट-ओरिएंटेड होते हैं। वे टेक्स्ट नहीं देखते; वे बाइट्स देखते हैं, और जो बाइट्स पैक थे वही बिल्कुल वापस सौंपते हैं। अगर मूल UTF-8 था, तो आपके पास अब UTF-8 है, और और कुछ की ज़रूरत नहीं है। C++ की अच्छी मोड़ यह है कि std::string स्वयं लंबाई-सहित बाइट कंटेनर है, इसलिए क्लासिक C फेलियर मोड - पहली NUL पर रुकने वाला स्ट्रिंग फ़ंक्शन - ज़्यादातर भाप हो जाती है। डिकोड की गई फ़ाइल, डिकोड किया सर्टिफिकेट, डिकोड की गई इमेज: सारी string में रह सकती हैं, == से तुलना हो सकती है, हैश हो सकती हैं, by वैल्यू पार हो सकती हैं, और अंदर के ज़ीरो बाइट्स बस बाइट्स ही हैं। बस C स्ट्रिंग में बदलकर strlen से ना न लगाना; size() इस्तेमाल करें।

उन legacy एन्कोडिंग के लिए जो पुराने डेटाबेस, एक्सपोर्ट, और हाथ से बने टूल में अभी भी छुपी हैं - ISO-8859-1, Windows-1252, और उनके रिश्तेदार - स्टैंडर्ड टूल है POSIX iconv, जो glibc के साथ शिप होता है। पहले बाइट्स तक डिकोड करें, फिर स्रोत से मेल खाने वाले कोडेक से उन बाइट्स को UTF-8 में बदलें:

#include <iconv.h>
#include <cstddef>
#include <string>
#include <vector>

std::string to_utf8(const std::vector<unsigned char> &raw,
                    const char *source_charset) {
  iconv_t cd = iconv_open("UTF-8", source_charset);
  if (cd == (iconv_t)-1) return {};
  char *inptr = reinterpret_cast<char *>(const_cast<unsigned char *>(raw.data()));
  size_t inleft = raw.size();
  std::vector<char> utf8buf(raw.size() * 4 + 8);
  char *outptr = utf8buf.data();
  size_t outleft = utf8buf.size();
  if (iconv(cd, &inptr, &inleft, &outptr, &outleft) == (size_t)-1) {
    iconv_close(cd);
    return {};
  }
  iconv_close(cd);
  return std::string(utf8buf.data(),
                     static_cast<size_t>(outptr - utf8buf.data()));
}

राउंड ट्रिप दोनों दिशाओं में बिना हानि है: एक स्ट्रिंग को ISO-8859-1 में पैक करें, उसकी base64 करें, भेजें, डिकोड करें, बदलें - और आपको बिल्कुल वही मिलता है जिससे शुरुआत हुई थी, एक्सेंट वाले अक्षरों समेत अखंड। और बाइनरी डेटा का कोई चरसेट ही नहीं है - PNG चाहे आप पसंद करें चाहे न, PNG ही रहता है, और यह पूरे आर्टिकल का सबसे मुक्तिदायक जवाब है।

फ़ाइलें डिकोड करना

छोटी फ़ाइलें चार-कदमी नृत्य हैं: बाइनरी में खोलो, vector में पढ़ो, डिकोड करो, नतीजा बाइनरी में वापस लिखो। बाइनरी मोड, हर बार, हर प्लेटफ़ॉर्म पर - Windows पर टेक्स्ट-मोड की पढ़ाई CRLF जोड़ों को एकल न्यूलाइन में बदल देती, और डिकोडर को दिखने से पहले ही चुपचाप आपका डेटा बदल देती:

#include <fstream>
#include <iterator>
#include <string>
#include <vector>

std::vector<unsigned char> read_file(const std::string &path) {
  std::ifstream in(path, std::ios::binary);
  return {std::istreambuf_iterator<char>(in),
          std::istreambuf_iterator<char>()};
}

ऊपर वाले कोड में ब्रेसेस का ध्यान दें। एकल bracket के साथ, std::vector<unsigned char> bytes(istreambuf_iterator<char>(file), istreambuf_iterator<char>()) जैसे एक लाइन प्रसिद्ध "most vexing parse" है: कंपाइलर इसे vector रिटर्न करने वाले फ़ंक्शन की घोषणा के रूप में पढ़ता है, और ऐसा करने में बिल्कुल सही भी है। ऊपर की ब्रेस-आधारित इनिशियलाइज़र फ़ॉर्म इस सिंटैक्स को बिल्कुल पार कर लेती है। जब बाइट्स हाथ में आ जाएँ, तो उन्हें इस आर्टिकल के किसी भी डिकोडर से गुज़ारें, और नतीजा std::ofstream से std::ios::binary में लिखें, स्ट्रीम ऑपरेटर की जगह write(data.data(), data.size()) इस्तेमाल करके, ताकि जो भी अंदर बसे ज़ीरो बाइट्स हों, डिस्क तक की यात्रा में बचकर रह जाएँ। तब .b64 फ़ाइल और उसकी डिकोड की हुई जुड़वाँ बिल्कुल उतने ही अलग होंगी जितनी वह 33 प्रतिशत कर है जो आप अंदर आते समय चुकी थी - और यह चेकसम की संतोषजनक पल का काम करता है।

बड़ी फ़ाइलें: दोनों सिरों पर स्ट्रीम

उन फ़ाइलों के लिए जो मेमोरी में पूरी नहीं उतरतीं, OpenSSL सेक्शन का स्ट्रीमिंग डिकोडर पूरा काम कर देता है: एक चंक पढ़ो, उसे कॉन्टेक्स्ट से गुज़ारो, जो बाहर आया वही लिखो, दोहराओ। किसी भी पल RAM में सिर्फ़ एक छोटा बफ़र रहता है, इसलिए 10 GB की base64 फ़ाइल 10 KB वाली के उसी कोड से डिकोड होती है, और MIME-शैली का लाइन रैपिंग मार्ग में कोई प्री-प्रोसेसिंग माँगता नहीं, क्योंकि स्ट्रीमिंग डिकोडर न्यूलाइन को कंधे उछालकर देखता है:

#include <fstream>
#include <string>
#include <vector>
#include <openssl/evp.h>

bool decode_stream_to_file(const std::string &in_path,
                           const std::string &out_path) {
  std::ifstream in(in_path, std::ios::binary);
  std::ofstream out(out_path, std::ios::binary);
  if (!in || !out) return false;
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  EVP_DecodeInit(ctx);
  std::string chunk(65536, '\0');
  std::vector<unsigned char> decoded(49152);
  bool ok = true;
  for (;;) {
    std::streamsize got = in.read(chunk.data(), chunk.size()).gcount();
    if (got < 0) { ok = false; break; }
    if (got == 0) break;
    int outl = 0;
    int ret = EVP_DecodeUpdate(ctx, decoded.data(), &outl,
                               reinterpret_cast<const unsigned char *>(chunk.data()),
                               static_cast<int>(got));
    if (ret < 0) { ok = false; break; }
    out.write(reinterpret_cast<const char *>(decoded.data()), outl);
    if (ret == 0) break;
  }
  unsigned char tail[3];
  int tail_l = 0;
  if (ok && EVP_DecodeFinal(ctx, tail, &tail_l) != 1)
    ok = false;
  if (ok)
    out.write(reinterpret_cast<const char *>(tail), tail_l);
  EVP_ENCODE_CTX_free(ctx);
  return ok;
}

बफ़र साइज़ बेइरादे नहीं हैं: EVP फ़ंक्शनों की लंबाई पैरामीटर int हैं, इसलिए एक कॉल 2 GB तक सुरक्षित है, और ऊपर के नंबर हर चंक को 64 KB इनपुट पर रखते हैं, 48 KB आउटपुट बफ़र के साथ, जो बिल्कुल 4 में से 3 है। यही int सीमा है जिसकी वजह से स्ट्रीमिंग पथ मौजूद है, और इसे एक ठोस तथ्य के रूप में जानना बेहतर है, प्लेटफ़ॉर्म बग के तौर पर खोजने के बजाय। अगर इनपुट कभी ख़राब निकला, तो फ़ंक्शन पहले ही चंक पर false रिटर्न कर देता है जिसे डिकोड नहीं किया जा सका, और आउटपुट फ़ाइल उससे पहले के वैध सब कुछ पकड़े रखती है - जो, आपकी पाइपलाइन के हिसाब से, बिल्कुल वही आंशिक नतीजा हो सकता है जो आपको चाहिए था।

HTTP, API, और वे JSON फ़ील्ड जिनमें बाइट्स छुपते हैं

Base64 HTTP में दो आकारों में दिखता है। पहला है डेटा: एक JSON रिस्पॉन्स जिसमें "certificate" या "avatar" फ़ील्ड base64 से भरी हो, एक अपलोड एंडपॉइंट जो टेक्स्ट-सेफ़ कॉलम में बाइट्स स्वीकार करे, एक डाउनलोड एंडपॉइंट जो आपको .b64 फ़ाइल सौंपे। पैटर्न हमेशा वही है - JSON पार्स करो, स्ट्रिंग बाहर निकालो, डिकोड करो, नतीजे को बाइट्स मानो - और इस आर्टिकल की डिकोडिंग साइड ही पूरा इम्प्लीमेंटेशन है। दूसरा आकार है क्रेडेंशियल: Authorization: Basic हेडर user:password की base64 है, और तीस सालों से यह स्टैंडर्ड का base64 का एकमात्र उपयोग-केस रहा है। इसे पार्स करना दो कदमों का काम है, और पहला कदम वही जगह है जहाँ लोग बाइनरी-आसपास डेटा के बीच NUL से ख़त्म C स्ट्रिंग की तरफ हाथ बढ़ाते हैं और फिर आश्चर्य करते हैं:

#include <optional>
#include <string>

/* "चालीस लाइनें, जो आपकी हैं" सेक्शन से strict_decode */

std::optional<std::pair<std::string, std::string>> parse_basic_auth(
    const std::string &b64) {
  std::string raw = strict_decode(b64);
  size_t colon = raw.find(':');
  if (colon == std::string::npos)
    return std::nullopt;
  return std::make_pair(raw.substr(0, colon), raw.substr(colon + 1));
}

उसे Basic प्रिफ़िक्स के बाद का हेडर वैल्यू दीजिए, तो वह user और password को सही लंबाई-ट्रैक्ड स्ट्रिंग के रूप में सौंपता है, या कुछ भी नहीं, अगर पेयलॉड user:pass जोड़ी नहीं है। सुरक्षा नोट यहीं का है, भले यह C++ का टॉपिक नहीं है: Basic auth छिपाई है, सुरक्षा नहीं। हेडर बिना छिपाई सफर करता है, जो कोई भी नेटवर्क पढ़ सकता है वह पढ़ लेता है, इसलिए यह बस TLS के पीछे स्वीकार्य है, और तब भी यह मशीन-टू-मशीन कॉल के लिए चुनाव है, लोगों के लिए नहीं।

JWT: टोकन का दावा पढ़ना

JSON Web Token तीन base64url सेगमेंट हैं जो डॉट से जुड़े होते हैं: हेडर, claims, सिग्नेचर। पहले दो JSON ऑब्जेक्ट्स हैं; तीसरा header.claims स्ट्रिंग पर क्रिप्टोग्राफ़िक सिग्नेचर है, हेडर में बताए गए अल्गोरिथम से गणना की गई। C++ में JWT का कोई बिल्ट-इन टाइप नहीं है, लेकिन टोकन पढ़ने के लिए base64url सेक्शन की ट्रांसकोड और एक डिकोडर से ज़्यादा कुछ नहीं चाहिए, क्योंकि दिलचस्प हिस्सा पढ़ना है:

#include <cstddef>
#include <cstdio>
#include <string>

/* पिछले सेक्शन्स से url_to_standard और strict_decode */

int main() {
  const std::string token =
      "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9."
      "eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ."
      "SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
  size_t dot1 = token.find('.');
  size_t dot2 = token.find('.', dot1 + 1);
  std::string header = strict_decode(
      url_to_standard(token.substr(0, dot1)));
  std::string claims = strict_decode(
      url_to_standard(token.substr(dot1 + 1, dot2 - dot1 - 1)));
  std::printf("header: %s\n", header.c_str());
  std::printf("claims: %s\n", claims.c_str());
}

हेडर वापस {"alg":"HS256","typ":"JWT"} आता है, और claims {"sub":"1234567890","name":"John Doe","iat":1516239022} - subject, name, और issued-at टाइमस्टैम्प। यह पूरी पढ़ने की तरफ़ है, और यह वाकई काम की है: टोकन का दावा लॉग करना, एक्सपायरी फ़ील्ड देखकर 401 का डीबग करना, या तय करना कि किसे claims पर भरोसा है - सब कुछ एक डिकोड की दूरी पर है। जो यह नहीं है, वह है सत्यापन। सिग्नेचर सेगमेंट भी base64url है, और उसे डिकोड करने पर 32 या 64 raw बाइट्स मिलती हैं जो अकेली-अकेली कुछ साबित नहीं करतीं; सिग्नेचर तभी मायने रखती है जब आप header.claims का हैश साझा secret या public key से दोबारा गणना करके तुलना करें। डिकोड किए JWT को चिट्ठी की तरह संभालिए: वह जो कहता है वही कहता है, और सील की जाँच एक अलग, क्रिप्टोग्राफ़िक काम है।

Data URI: वे फ़ाइलें जो पेज में खुद पेस्ट हो गईं

डेटा URI वह URL है जिसका पेयलॉड पते के अंदर ही बसा है: data: के बाद वैकल्पिक मीडिया टाइप, वैकल्पिक ;base64 मार्कर, एक कॉमा, और फिर डेटा स्वयं - RFC 2397 का पूरा स्कीम। ब्राउज़र इमेज, फ़ॉन्ट और छोटे स्क्रिप्ट्स को HTML और CSS में सीधे इम्बेड करने के लिए इन्हें इस्तेमाल करते हैं, बिना किसी अतिरिक्त रिक्वेस्ट के, और अगर आप कभी ऐसा पेज देखें जो नेटवर्क बंद करने पर भी काम करता रहे, तो डेटा URI मज़बूत आरोपी है। C++ साइड पर, डिकोडिंग का काम है URI को तोड़ना, और फिर पेयलॉड को अपने रोज़मर्रा डिकोडर से गुज़ारना, क्योंकि ;base64 मार्कर मौजूद हो तो पेयलॉड सादा स्टैंडर्ड base64 होता है - आमतौर पर पैडेड, आमतौर पर एक ही लाइन पर:

#include <cstddef>
#include <string>

std::string data_uri_payload(const std::string &uri, bool *is_base64) {
  const std::string prefix = "data:";
  if (uri.rfind(prefix, 0) != 0)
    return {};
  size_t comma = uri.find(',');
  if (comma == std::string::npos)
    return {};
  std::string meta = uri.substr(prefix.size(), comma - prefix.size());
  *is_base64 = meta.size() >= 7 &&
               meta.compare(meta.size() - 7, 7, ";base64") == 0;
  return uri.substr(comma + 1);
}

इसे data:image/png;base64,iVBORw0KGgo=... के साथ कॉल करें, तो वह आपको पेयलॉड सौंपता है, साथ में फ्लैग जो बताता है कि कौन-सी डिकोडिंग राह लेनी है। दो गड्ढे। पहला, मार्कर का न होना यानी पेयलॉड URL-एन्कोडेड टेक्स्ट है, base64 नहीं, इसलिए फ्लैग कोई औपचारिकता नहीं है - कोई URI जो base64 जैसा दिखता हो मगर पर्सेंट-एन्कोडेड टेक्स्ट के तौर पर बना हो, वह कचरे में डिकोड होगा। दूसरा, कुछ जनरेटर लंबे डेटा URIs को MIME की तरह न्यूलाइन से रैप करते हैं; आपका सख़्त डिकोडर उन्हें मना कर देगा, इसलिए अगर स्रोत आपकी नियंत्रण में नहीं है, तो डिकोड से पहले लाइन ब्रेक हटा लीजिए। data:image/png URI के पेयलॉड को डिकोड करने पर बिल्कुल सही PNG बाइट्स मिलती हैं, हेडर समेत - और यही पूरे अभ्यास की चुपचुप संतुष्टि है।

ईमेल, MIME और 76-चर की आदत

ईमेल ही वह कारण है जिसने base64 को अपनी लाइनें रैप करना सिखाया। SMTP, अपने मूल रूप में, सात-बिट ASCII उठाने के लिए बना था, इसलिए बाइनरी कुछ भी छपने योग्य टेक्स्ट के रूप में दोबारा लिखा जाता तो ही सफर कर पाता। Privacy-Enhanced Mail ने 1987 में 64-चर लाइनों से यह काम किया, और MIME ने 1993 में ईमेल के लिए एन्कोडिंग स्टैंडर्ड बनाते समय सीमा को 76 चरों तक ढीला किया और नियम जोड़ा कि मानक-अनुकूल डिकोडर को लाइन ब्रेक को बस नज़रअंदाज़ करना होगा। आदत जीवित रही: ईमेल अटैचमेंट आज भी base64 है, 76 पर रैप, और सटीक गणित 4/3 गुणा 78/76 तक पहुँचता है - मूल साइज़ का लगभग 137 प्रतिशत, कुछ सौ बाइट्स हेडर जोड़कर। आपका C++ डिकोडर सारे को वापस 100 प्रतिशत तक सिकोड़ देता है, जो इस पूरे फॉर्मेट की पूरी बात है।

C++ का मोड़ यह है कि इस आर्टिकल के डिकोडर लाइन ब्रेक के बारे में मतभेद रखते हैं, और हर एक का अपना कारण है। OpenSSL स्ट्रीमिंग डिकोडर उन्हें स्ट्रीम में कहीं भी छलांग खाने देता है, जो कि बिल्कुल MIME का नियम है। OpenSSL वन-शॉट फ़ंक्शन पेयलॉड के अंदर किसी भी व्हिटस्पेस को मना करता है। Boost.Beast पहले न्यूलाइन पर बिना कहे रुक जाता है। Boost के इटेरेटर एक स्पेस पर एक्सेप्शन फेंकते हैं। इसलिए जब पेयलॉड ईमेल से आए, तो आपका पहला फैसला है कि कौन-सा डिकोडर इस्तेमाल करें, या आप खुद लाइन ब्रेक हटा लें - \r और \n पर एक-लाइन की erase-remove पार - और असली काम किसी भी पसंदीदा डिकोडर को करवा दें। आगे से हटाना वह बोरिंग, भरोसेमंद चुनाव है, और यही वह चुनाव है जो आपके डिकोडर के चुनाव को आपके पेयलॉड के इतिहास से आज़ाद रखता है।

डेटाबेस, कॉन्फ़िग फ़ाइलें और एनवायरनमेंट वेरिएबल

base64 का तीसरा घर है स्टोरेज लेयर: legacy डेटाबेस में कोई कॉलम जिसकी डॉक्यूमेंटेशन सिर्फ़ "base64" लिखी हो और और कुछ नहीं, JSON फ़ाइल में कोई कॉन्फ़िग ब्लॉब, या एनवायरनमेंट वेरिएबल में कोई पेयलॉड जिसको एक सर्विस ने शेल में बचने के लिए base64 कर दिया था। डिकोडिंग का पैटर्न हर जगह वही है - स्ट्रिंग पढ़ो, डिकोड करो, बाइट्स मानो - मगर बिना लेबल इनपुट को एक अलग पैराग्राफ़ का हक़ है, क्योंकि कभी-कभी आप वाकई नहीं जानते कि कौन-सी वर्णमाला इस्तेमाल हुई थी। आपको पता नहीं हो सकता, पर आप टेस्ट कर सकते हैं, क्योंकि चार चर ज़्यादातर काम करते हैं:

  • + या / है - बस स्टैंडर्ड वर्णमाला सही हो सकती है।
  • - या _ है - बस URL-safe वर्णमाला सही हो सकती है।
  • न तो, पर = पर ख़त्म होता है - पैडेड स्टैंडर्ड, या पैडेड URL-safe स्ट्रिंग जिसके पेयलॉड को बस उन्हीं दो बदले हुए चरों की कभी ज़रूरत न हुई हो।
  • न तो, बिना पैडिंग - दोनों में से कुछ भी हो सकता है; वेब पर raw URL-safe आकार ही आम है, इसलिए URL या टोकन में जन्मा डेटा के लिए पहले वह चलाइए।

जो स्ट्रिंग उन्हीं चार पहचान-चरों में से किसी का इस्तेमाल नहीं करती, वह दोनों वर्णमालाओं के तहत एक जैसी डिकोड होती है, इसलिए उन पर आज़माने का क्रम इस बात का मामला है कि डेटा कहाँ से आया: ईमेल में जन्मी चीज़ें स्टैंडर्ड वर्णमाला चाहती हैं, URL में जन्मी URL वाली। और याद रखिए कि वही स्ट्रिंग की पैडेड और बिना-पैडिंग वाली पढ़ाई दोनों आज़माइए - एक = कम होना "मना" और "हल" के बीच का फ़र्क़ है, यही वजह है कि ऊपर का सख़्त डिकोडर दोनों स्वीकार करता है।

कमांड लाइन पर दो base64 हैं

एक-टाइम के कामों के लिए, Linux मशीन पर आमतौर पर दो base64 डिकोडर मिलते हैं, और वे बिल्कुल उसी तरह अलग व्यवहार करते हैं जिससे लोग ज़ख्मी होते हैं। पहला है GNU coreutils का base64 (कुछ नए डिस्ट्रो uutils की री-इम्प्लीमेंटेशन शिप करते हैं, और दोनों उन्हीं फ्लैग को बोलते हैं - base64 --version से जाँच लीजिए)। वह RFC 4648 के अनुसार है, एन्कोडिंग पर 76 चरों पर रैप करता है (-w 0 इसे बंद कर देता है), और डिकोड पर न्यूलाइन को कहीं भी खुशी-खुशी स्वीकार करता है; उसका -i फ्लैग कचरा-सहनशीलता को ग़लती की जगह साफ़ कर देता है। दूसरा है OpenSSL का, और यहाँ मोड़ है: openssl base64 बिल्कुल अपना अलग ऐप नहीं है। 1.1.0 सीरीज़ (2016) से, enc प्रोग्राम अपनी इन्वोकेशन नाम जाँचता है, और अगर उसे "base64" के नाम से बुलाया गया, तो वह खुद को base64 मोड में बदल लेता है - argv[0] पर स्ट्रिंग तुलना, जो कि एलियस शिप करने का C तरीका है। -A के बिना, वह इनपुट के पहले 1024 बाइट्स में कहीं न्यूलाइन की उम्मीद रखता है, इसलिए लंबी एक-लाइन स्ट्रिंग वापस खाली, एग्ज़िट कोड 0 के साथ आती है। -A के साथ वह एक लाइन पढ़ता है, और enc कमांड की लिखित बग लिस्ट एक दो-वस्तुओं की म्यूज़ियम है: -A ऑप्शन बड़ी फ़ाइलों के साथ सही तरह से काम नहीं करता, और -A के बिना, अगर पहले 1024 बाइट्स में न्यूलाइन न हो, तो इनपुट की पहली दो लाइनें नज़रअंदाज़ कर दी जाती हैं। पाइपलाइन में, चुपचाप खाली फ़ाइल खाली पेयलॉड की सफल डिकोडिंग की बिल्कुल जैसे दिखती है।

# ईमानदार वन-लाइनर्स
base64 -d < payload.b64 > payload.bin
openssl base64 -d -A < payload.b64 > payload.bin

दोनों base64url को नेटिव रूप में बोलते नहीं हैं, जो ट्रांसकोड स्निपेट के आपके मस्क्ल मेमोरी में रहने का एक और कारण है। ज़रूरी चीज़ों के लिए, प्रोग्राम के अंदर डिकोड करें, जहाँ एरर आपके टेस्ट करने योग्य नंबर के रूप में लौटते हैं और चुप टूल की एग्ज़िट कोड आपका एकमात्र संकेत नहीं है।

वे फंदे जो ख़ासकर C++ को काटते हैं

  • ज़ीरो-फिल। EVP_DecodeBlock TQ== के लिए तीन बाइट्स देता है: अक्षर M और दो ज़ीरो। असली लंबाई पैडिंग से वापस निकालें, या स्ट्रीमिंग API इस्तेमाल करें, जो काउंट के बारे में ईमानदार है।
  • 3.5 से पहले का स्ट्रीमिंग अजीबपन। 3.5.0 (अप्रैल 2025) से पहले की OpenSSL रिलीज़ों में EVP_DecodeUpdate की भी वही ज़ीरो-फिल आदत थी। 3.0 या 3.3 पिन के ख़िलाफ़ लिखा कोड टेल लंबाई के बारे में आपको झूठ बोल रहा हो सकता है; फिक्स मैन पेज की history सेक्शन में दर्ज है।
  • चुपचाप रुकना। Boost.Beast का decode कोई एरर चैनल नहीं रखता: वह किसी भी अवैध चर, किसी भी न्यूलाइन, और असंभव टेल लंबाई पर रुक जाता है, और सीधे चेहरे के साथ आंशिक नतीजा रिटर्न करता है। जाँच करें कि consumed + pads == input.size() और कुल चार का गुणज है, वरना आप वही डिकोड कर रहे हैं जो उसने डिकोड करने का मन बनाया।
  • decoded_size की गली। b64::decoded_size(n) मानता है कि n चार से बँटता है। दो चरों के इनपुट से एक बाइट निकल सकती है, जबकि decoded_size(2) ज़ीरो कहता है - विषम लंबाइयों के लिए अतिरिक्त जगह जोड़ें।
  • ज़ीरो-बाइट पैड्स। Boost के इटेरेटर = को ज़ीरो वैल्यू के रूप में डिकोड करते हैं, इसलिए TWFuZQ== छह बाइट्स बन जाती है, आख़िर में दो ज़ीरो समेत। पैड काउंट घटा लीजिए, वरना अपने भूतों का आनंद लीजिए।
  • व्हिटस्पेस, चार तरह। OpenSSL स्ट्रीमिंग इसे छलांग खाने देता है, OpenSSL वन-शॉट इसे अंदर मना करता है, archive इटेरेटर इस पर एक्सेप्शन फेंकते हैं, और Beast इस पर रुक जाता है। पेस्ट की गई स्ट्रिंग्स व्हिटस्पेस उठाकर चलना पसंद करती हैं, और हर डिकोडर की इस पर अपनी राय है।
  • Signed char इंडेक्सिंग। अगर आपने चर से इंडेक्स की गई खुद की डिकोड टेबल बनाई है, तो unsigned char से इंडेक्स लगाइए। उन प्लेटफ़ॉर्म्स पर जहाँ char signed है, 127 से ऊपर का बाइट नकारात्मक इंडेक्स बन जाता है, जो कि लेब कोट पहने हुए undefined व्यवहार है।
  • माइनस साइन टाइम ट्रैवेलर है। OpenSSL में - PEM-युग का सॉफ्ट इनपुट-अंत मार्कर है, वर्णमाला का चर नहीं। डिकोड करने से पहले base64url को ट्रांसकोड करें।
  • int, size_t नहीं। EVP की लंबाई पैरामीटर int हैं। 2 GB से ऊपर, बस चंक वाली स्ट्रीमिंग राह सुरक्षित है, यही वजह है कि वह मौजूद है।
  • Windows पर टेक्स्ट मोड। फ़ाइल को टेक्स्ट में खोलने से CRLF, LF में बदल जाता है, और डिकोडिंग से पहले ही आपका इनपुट ख़राब हो जाता है। std::ios::binary, हर बार, हर प्लेटफ़ॉर्म पर।
  • most vexing parse। std::vector<char> v(istreambuf_iterator<char>(f), istreambuf_iterator<char>()) फ़ंक्शन घोषणा है। ब्रेस इनिशियलाइज़ेशन या पॉइंटर जोड़ी इस्तेमाल करें।
  • अन-कैनोनिकल पैड बिट्स। सहिष्णु डिकोडर ऐसी स्ट्रिंग्स स्वीकार कर सकता है जिनके न-इस्तेमाल पैड बिट्स ज़ीरो नहीं हैं, इसलिए दो साफ़ अलग दिखने वाली स्ट्रिंग्स वही बाइट्स में डिकोड होती हैं (base64 लचीलापन)। सुरक्षा बाउंड्री पर, जो ज़रूरत नहीं उसे मना करो - RFC 4648 कहता है कि डिकोडर बस यही कर सकते हैं।
  • कमांड लाइन चुपचाप फेल होती है। -A के बिना openssl base64 -d एक-लाइन इनपुट गिलती है (खाली आउटपुट, एग्ज़िट 0); लिखित बग बड़ी फ़ाइलों और न्यूलाइन-रहित इनपुट को दोनों दिशाओं में कवर करते हैं। पाइपलाइन में अपने आउटपुट की जाँच कीजिए।
  • बाइनरी पर strlen। std::string ज़ीरो बाइट्स को खुश रखता है, लेकिन जैसे ही आप C स्ट्रिंग को किसी legacy API में सौंपते हैं, strlen पहली NUL पर रुक जाता है। लंबाई और पॉइंटर दोनों पार करें, कभी नंगी पॉइंटर नहीं।

C++ में Base64 का छोटा सा इतिहास

फॉर्मेट भाषा के आधुनिक युग से पुराना है। अब MIME base64 कहने वाली एन्कोडिंग का पहला स्टैंडर्डरलाइज़्ड इस्तेमाल Privacy-Enhanced Mail प्रोटोकॉल था, जिसे 1987 में 64-चर लाइनों के साथ प्रस्तावित किया गया, आख़िर में चिपकाया हुआ RSA-MD2/MD5 message-integrity check समेत; नाम "base64" स्वयं 1993 में ही पहुँचा, जब MIME स्टैंडर्ड्स ने उसे नाम दिया। C++ मंच पर C++98 के रूप में 1998 में आया - MIME के पाँच साल बाद - और भाषा के डेवलपर्स का पहला base64 कोड Rene Nyffenegger का C जोड़ी था (2004-2008), जिसे 4 दिसंबर 2008 की एक Stack Overflow प्रश्न ने पूरे वेब पर फैलाया। उस कहानी का सबसे मज़ेदार हिस्सा है कि कौन नहीं आया: स्निपेट के रचयिता खुद कभी जवाब पोस्ट नहीं किया, मगर उसकी स्निपेट वह फ़ॉल्क गाना बन गई जिसे सबने कॉपी किया। थ्रेड में एक जवाब ने, सतर्कता से, उसके अपने वेबसाइट से पूरा इम्प्लीमेंटेशन छाप दिया, ताकि साइट गिर भी जाए तो वह बच जाए।

फिर एकोसिस्टम ने एकोसिस्टम का काम किया। 2002 में Robert Ramey के Boost.Serialization ने इटेरेटर एडैप्टर शिप किए - C++ टूलबॉक्स का सबसे पुराना base64, इतना सख़्त कि एक स्पेस पर एक्सेप्शन फेंक देता, एक साल पहले RFC 3548 उसी नियम को कोड कर आया था जो वह पहले से लागू कर रहा था। 2017 में Boost 1.66 ने Beast लाया, और उसके साथ वह हेडर-ओनली कोडेक, जो आज भी फुटर में Nyffenegger के श्रेय के साथ शिप होता है। बीच में स्टैंडर्ड स्वयं C++11, C++14, C++17, C++20, C++23 (2024 में प्रकाशित) तक गया, और इनमें से हर एक ने 64-चर वर्णमाला को देखा और आगे बढ़ गया। C++26 टेक्स्ट कोडेक काम के लिए नया <text_encoding> हेडर जोड़ता है, जो 2022 में ही मंज़ूर हुआ था; उसकी तकनीकी सामग्री मार्च 2026 की ISO मीटिंग, लंदन में, पूरी हुई, जहाँ कमेटी ने 114-12-3 के वोट से इसे प्रकाशन के रास्ते भेज दिया, और कमेटी की बाद की 2026 मीटिंग्स - 16-21 नवंबर की Búzios, ब्राज़ील वाली समेत - अगली वर्किंग ड्राफ़्ट, C++29, खोलने में निकल रही हैं, इस पर वोट नहीं कर रही। Base64 ड्राफ़्ट में कभी नहीं था। सात स्टैंडर्ड्स, तीन दशक, टेक्स्ट एन्कोडिंग का एक हेडर - और कमेटी के पास base64 जोड़ने का हर सम्भव बहाना आ चुका है, और उसने हर एक बहाने पर मुँह फेर लिया। C++ में base64 का अमली इतिहास है, और बना रहेगा, उसके लाइब्रेरी का इतिहास: OpenSSL के EVP रूटीन, Boost के दो रूप, एक Windows API कॉल, और एक चालीस-लाइन स्निपेट जो आपकी है।

मज़ेदार तथ्य, C++ संस्करण

  • वही फ़ंक्शनों की जोड़ी 2008 की एक Stack Overflow प्रश्न के जवाबों में दिखती है, Boost.Beast की स्रोत में श्रेय वाला फुटर लेकर, और अगिनती प्राइवेट कोडबेस के हेडर फ़ाइलों में। किसी C++ डेवलपर से पूछिए कि उनका base64 कहाँ से आया, तो सबसे ईमानदार जवाब है "मुझे नहीं पता, और न इंटरनेट को"।
  • Boost के archive इटेरेटर इस आर्टिकल का सबसे पुराना base64 हैं, कॉपीराइट 2002 - वही साल जिसमें .NET Framework 1.0 SDK शिप हुआ था। वे एक स्पेस पर एक्सेप्शन फेंकते हैं, यानी RFCs के पकड़ने से पहले वे "वर्णमाला से बाहर के चर मना करो" नियम लागू कर रहे थे: RFC 3548 ने 2003 में उसे कोड किया, और RFC 4648 ने 2006 में दोहराया।
  • OpenSSL का स्ट्रीमिंग डिकोडर 80-बाइट के अंदरूनी बफ़र से काम करता है, मगर हर 64 base64 चरों पर फ्लश करता है, वही लाइन चौड़ाई जिसका PEM कवच 1987 से इस्तेमाल कर रहा है। वह चुप 64 उन आख़िरी जगहों में से एक है जहाँ 2026 में पुराना फॉर्मेट अभी भी भार-संभालने वाला काम कर रहा है।
  • सबसे छोटी संभव पैडेड base64 चार चर है, TQ==: एक बाइट जो दो-चर के कपड़ों में सजी है। सबसे छोटी बिना-पैडिंग वाली दो चर है, TQ। आपको डिकोड करने को क्या मिलता है, पूरी तरह इस बात पर निर्भर है कि उसे कौन एन्कोड करता है, और वह शख़्स आपको नहीं सोच रहा था।
  • MIME का गणित सटीक है: 4/3 गुणा 78/76, इसीलिए ईमेल अटैचमेंट अपने मूल साइज़ का लगभग 137 प्रतिशत में आता है (ऊपर कुछ सौ बाइट्स हेडर जोड़कर)। आपका C++ डिकोडर उसे वापस 100 प्रतिशत तक सिकोड़ देता है, जो पूरे अभ्यास की चुपचुप खुशी है।
  • आम libstdc++ या MSVC पर, std::string छोटे पेयलॉड्स को अलोकेट करने की जगह small-स्ट्रिंग optimization से स्टैक बफ़र में उठाता चलता है। 9-बाइट इनपुट 6 बाइट्स में डिकोड होता है और हीप को छूता भी नहीं। आपकी टिकी-हटी कॉन्फ़िग ब्लॉब की base64 शायद हकीकत में स्टैक फ्रेम में बसी है, जो कि वह फ्री लंच है जिसे स्टैंडर्ड लाइब्रेरी कभी घोषणा नहीं करती।
  • वह openssl base64 कमांड जिसकी तरफ आप शेल में हाथ बढ़ाते हैं, कमांड ही नहीं है। वह enc प्रोग्राम है जो argv[0] में अपना नाम जाँचता है और व्यक्तित्व बदल लेता है। स्ट्रिंग तुलना से एलियस, जो कि C++ की चीज़ें करने की शैली है, C में।
  • YouTube वीडियो आइडेंटिफ़ायर base64url हैं: ग्यारह चर, बिना पैडिंग, URL के पास-पस + या / का नाम-निशान नहीं। प्लैनेट का सबसे ज़्यादा देखा गया एन्कोडिंग फॉर्मेट उसी "URL और फ़ाइल-नाम सेफ़" वेरिएंट पर चलता है जिसे RFC 4648 ने उस सेक्शन में जोड़ा है जो एक पेज पर फिट आता है।

जब आपको पैक करना हो

जो सब आपने अभी डिकोड किया, वह दुसरी तरफ़ वही टूलबॉक्स ने पैक किया था: वन-शॉट के लिए EVP_EncodeBlock, स्ट्रीम के लिए EVP_EncodeUpdate साथ में EVP_EncodeFinal (और वहीँ से आती हैं उन 64-चर लाइनों की कथा), वही बफ़र गणित उल्टा, और वही 33 प्रतिशत कर जो डिकोडिंग चुपचाप वापस कर देता है। पूरा पैकिंग की कहानी - साइज़ गणित बारीक-बारीक, वे एन्कोडर जो अपने आउटपुट को NUL से ख़त्म करते हैं, वह Boost इटेरेटर जो पैड चर से कभी नहीं मिला, base64url, MIME रैपिंग, फ़ाइलें, और CRLF आदत वाला Windows API - sister साइट पर C++ एन्कोडिंग गाइड में बसती है। जाइए, उसे पढ़िए, फिर वापस आइए और कुछ बड़ा खोलो। यही पूरा खेल है: कोई स्टैंडर्ड लाइब्रेरी नहीं, तीन भरोसेमंद वेंडर तीन अलग व्यक्तित्वों के साथ, एक डिकोडर जो उसी बाइट पर उँगली उठाता है जिसने सुनाई, एक 2025 का बगफिक्स जिसने स्ट्रीमिंग टेल बदल दी, और एक ज़ीरो-भरी ट्रिपलेट जो हमेशा के लिए याद रखनी है। मज़ा लीजिए, अनपैकिंग का।

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

संबंधित लेख: C++ (Cpp) में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड