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

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

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

एक ही नंबर सिर में रखना: एन्कोडिंग आपके डेटा को बढ़ाती है। तीन बाइट्स चार चर बन जाते हैं, इसलिए हर पेलोड आपकी प्रोग्राम से करीब एक-तिहाई बड़ा होकर निकलता है, और लाइन ब्रेक्स जोड़े जाएँ तो थोड़ा और। यह वही कर है, और इससे बचने का कोई रास्ता नहीं - पर C में इस कर की एक-एक क़िस्त है, क्योंकि आउटपुट बफ़र आप खुद अलोकेट करते हैं, और वह बफ़र बिल्कुल जितना चाहिए उतना ही बड़ा होना चाहिए। गणित एक बार सही कर लीजिए, तो इस आर्टिकल के हर एन्कोडर भविष्यवाणी करने लायक़ बन जाते हैं: न ओवरफ़्लो, न अंडरफ़्लो, न अंदाज़ा लगाना कि अगला बाइट कहाँ उतरेगा। फिर टूलबॉक्स है जिससे चुनना है - OpenSSL, Mbed TLS, APR-Util, GLib - और चयन मायने रखता है, क्योंकि हर एक अलग ढंग से रैप करता है, अलग ढंग से टर्मिनेट करता है, और अलग ढंग से एरर करता है।

चार एन्कोडर, चार व्यक्तित्व

चारों लाइब्रेरियाँ स्टैंडर्ड वर्णमाला को सही और एक-जैसा एन्कोड करती हैं - एक जैसे बाइट्स अंदर, एक जैसे चर बाहर, हमेशा। फ़र्क़ पैकेजिंग में है, और ही पैकेजिंग में इंटरऑपेरैबिलिटी बग छुपते हैं। यहाँ नज़ारा है:

लाइब्रेरी हेडर आउटपुट शैली फेलियर मोड
OpenSSL (libcrypto) <openssl/evp.h> न्यूलाइन नहीं; NUL टर्मिनेटर लिखता है असल में कुछ नहीं (सिर्फ़ अलोकेशन)
Mbed TLS <mbedtls/base64.h> न्यूलाइन नहीं; NUL-टेर्मिनेटेड ज़रूरी साइज़ के साथ "बफ़र बहुत छोटा" कोड
APR-Util <apr-1.0/apr_base64.h> न्यूलाइन नहीं; NUL जोड़ता है कुछ नहीं - अपने बफ़र साइज़ पर भरोसा
GLib <glib.h> न्यूलाइन नहीं; NUL-टेर्मिनेटेड, हीप-अलोकेटेड NULL रिटर्न करता है (सिर्फ़ अलोकेशन)

टेबल में ग़ायब चीज़ नोट करें: इनमें से कोई भी डिफ़ॉल्ट रूप से लाइनें रैप नहीं करता। यह इरादे से है - RFC 4648 कहता है कि इम्प्लिमेंटेशन लाइन फ़ीड्स न जोड़ें जब तक कि आसपास की स्पेसिफ़िकेशन उन्हें ख़ुद माँग न ले - और यह राहत की बात है, क्योंकि JSON स्ट्रिंग या URL के अंदर एक बेख़बर न्यूलाइन एरर है, फ़ीचर नहीं। रैपिंग ईमेल और PEM के लिए मौजूद है, और ज़रूरत हो तो वह आप या तो OpenSSL के स्ट्रीमिंग पथ से पाते हैं या पाँच लाइनों में खुद रैप करते हैं (ईमेल वाला सेक्शन दोनों दिखाता है)। लाइब्रेरी चुनने के लिए: अगर आप पहले से OpenSSL लिंक करते हैं तो वही इस्तेमाल करें, हर किलोबाइट पर बहस होने वाले एम्बेडेड बिल्ड्स के लिए Mbed TLS, Apache एकोसिस्टम के अंदर APR-Util, और जब प्रोग्राम का बाक़ी हिस्सा पहले से GLib हो तो GLib। इंस्टॉलेशन: libssl-dev (Debian/Ubuntu), openssl-devel (Fedora/RHEL), या brew install openssl (macOS); Mbed TLS के लिए libmbedtls-dev; APR-Util के लिए libaprutil1-dev और साथ में libapr1-dev; GLib के लिए glib2.0-dev।

अलोकेट करने से पहले हिसाब लगाइए

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

size_t encoded_chars(size_t n) {
  return ((n + 2) / 3) * 4;
}

1000 बाइट्स के लिए वह 1336 चर है; 1 बाइट के लिए 4; 0 के लिए 0। उसके बाद दो एडज्टमेंट आते हैं। पहला, OpenSSL और Mbed TLS दोनों डेटा के बाद NUL टर्मिनेटर जोड़ते हैं (और साइज़ पूछने पर Mbed TLS उसके लिए जगह भी अलग रखता है), इसलिए आपके बफ़र को एक अतिरिक्त बाइट चाहिए: encoded_chars(n) + 1। दूसरा, अगर आप लाइन-रैप्ड आउटपुट चाहते हैं, तो हर लाइन के लिए एक न्यूलाइन जोड़ें: OpenSSL का स्ट्रीमिंग एन्कोडर हर 48 इनपुट बाइट्स पर 64-चर लाइन देता है, इसलिए रैप्ड लंबाई encoded_chars(n) + (n + 47) / 48 होती है। 1000 बाइट्स से जाँचें: 1336 चर ज़्यादा 21 न्यूलाइन से 1357, और यही बिल्कुल वह है जो एन्कोडर देता है। फ़ॉर्मूला एक बार फ़ंक्शन के रूप में लिखकर हर जगह इस्तेमाल करें; "फिट हो जाता है" और रात के 3 बजे की हीप कोरप्शं के बीच का फ़र्क़ यही है।

size_t b64_buffer_size(size_t in_len) {
  return ((in_len + 2) / 3) * 4 + 1; /* चर + NUL */
}
size_t b64_buffer_size_wrapped(size_t in_len) {
  return ((in_len + 2) / 3) * 4 + (in_len + 47) / 48 + 1;
}

OpenSSL: एक ब्लॉक या एक चलता हुआ नल

OpenSSL का वन-शॉट फ़ंक्शन कामगार है, और इसी गुट का सबसे दोस्ताना:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *text = "Mane";
  unsigned char out[32];
  int n = EVP_EncodeBlock(out, (const unsigned char *)text,
                          (int)strlen(text));
  printf("len=%d str=%s\n", n, (char *)out);
  return 0;
}

यह एन्कोडेड चर out में लिखता है, उनके बाद NUL जोड़ता है, और लंबाई NUL के बिना रिटर्न करता है - तो %s से प्रिंट करना सुरक्षित है, और लंबाई चाहिए तो वह भी उपलब्ध है। आउटपुट बफ़र encoded_chars(n) + 1 बाइट्स रखने के लिए पर्याप्त होना चाहिए। कोई एरर पथ हैंडल करने को नहीं है: एन्कोडिंग फेल नहीं हो सकती, क्योंकि कोई भी बाइट जायज़ इनपुट है, और फ़ंक्शन में कोई इनपुट-वैलिडेशन कॉन्सेप्ट नहीं है जिसमें उलझा जाए। ग़लती की एक ही राह है - उसे बहुत छोटा बफ़र देना - और गणित वाला सेक्शन इसका एन्टीडोट है।

स्ट्रीमिंग जोड़ी तब है जब डेटा बड़ा हो या टुकड़ों-टुकड़ों में आए। EVP_EncodeUpdate इनपुट को 48-बाइट ब्लॉक्स में प्रोसेस करता है और हर पूरे ब्लॉक पर 64 चर प्लस एक न्यूलाइन (65 बाइट्स) लिखता है, और बाक़ी हिस्से को कॉन्टेक्स्ट में पकड़े रहता है जब तक और डेटा न आए या फाइनल कॉल न हो:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  if (ctx == NULL) {
    return 1;
  }
  EVP_EncodeInit(ctx);
  unsigned char in[1000];
  for (int i = 0; i < 1000; i++) {
    in[i] = (unsigned char)(i % 251);
  }
  unsigned char out[1400]; /* 1336 चर + 21 न्यूलाइन + जगह */
  int outl = 0;
  int total = 0;
  EVP_EncodeUpdate(ctx, out + total, &outl, in, 1000);
  total += outl;
  EVP_EncodeFinal(ctx, out + total, &outl);
  total += outl;
  int nl = 0;
  for (int i = 0; i < total; i++) {
    if (out[i] == '\n') nl++;
  }
  printf("encoded 1000 bytes into %d chars, %d newlines\n",
      total, nl);
  EVP_ENCODE_CTX_free(ctx);
  return 0;
}

उस प्रोग्राम का आउटपुट 1357 बाइट्स है, 21 लाइन ब्रेक्स के साथ - गणित वाले सेक्शन का फ़ॉर्मूला, असलियत में। दो प्रैक्टिकल नोट्स। वर्ज़न वाला नोट: OpenSSL 1.1.0 (2016) के बाद से कॉन्टेक्स्ट टाइप ऑपेक है, इसलिए EVP_ENCODE_CTX_new() से अलोकेट करें और EVP_ENCODE_CTX_free() से फ्री करें; 1.0.2 और उससे पहले के वर्ज़न के लिए टारगेट किए ट्यूटोरियल में मिलने वाला पुराना स्टैक पैटर्न EVP_ENCODE_CTX ctx; आधुनिक हेडर्स के साथ कंपाइल नहीं होता, OpenSSL 3.x समेत। और डिज़ाइन वाला नोट: क्योंकि EVP_EncodeUpdate से सिर्फ़ पूरे 48-बाइट ब्लॉक्स बाहर आते हैं, सबसे साफ़ चंक्ड पाइपलाइन उसे 48 के गुणज डालती है - फिर जो भी लाइन फ़ंक्शन लिखे वह पूरी लाइन होती है, और EVP_EncodeFinal अकेला तय करता है कि टेल को कैसे रैप करना है। अगर आपका इनपुट अलग-अलग आकारों में आता है (नेटवर्क रीड), तो कॉंटेक्स्ट खुद अलाइनमेंट आपके लिए संभाल लेता है; 48-के-गुणज की आदत बस वह चीज़ है जो आउटपुट को भविष्यवाणी-योग्य बनाती है।

Mbed TLS: पूछिए, फिर एन्कोड कीजिए, स्ट्रिंग पाइए

Mbed TLS की एन्कोडिंग इस गुट में सबसे साफ़ करार रखती है, और वह करार एक साइज़ क्वेरी के चारों ओर बना है जिसे NULL डेस्टिनेशन के साथ कॉल किया जा सकता है:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <mbedtls/base64.h>
int main(void) {
  const char *text = "Mane";
  size_t slen = strlen(text);
  size_t needed = 0;
  int rc = mbedtls_base64_encode(NULL, 0, &needed,
      (const unsigned char *)text, slen);
  if (rc != MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) {
    printf("size query failed: %d\n", rc);
    return 1;
  }
  printf("needs %zu bytes\n", needed);
  unsigned char *out = malloc(needed);
  size_t olen = 0;
  rc = mbedtls_base64_encode(out, needed, &olen,
      (const unsigned char *)text, slen);
  if (rc != 0) {
    printf("encode failed: %d\n", rc);
    free(out);
    return 1;
  }
  printf("olen=%zu str=%s\n", olen, out);
  free(out);
  return 0;
}

विवरण को ध्यान से पढ़िए, क्योंकि ये एक दोस्ताना API का मास्टरक्लास हैं। साइज़ क्वेरी needed को एन्कोडेड चरों के प्लस NUL के लिए एक बताती है - "Mane" के लिए वह 8 प्लस 1, यानी 9 - और अपने को "बफ़र बहुत छोटा" कोड के साथ सिग्नल करती है (MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL, यानी -0x002A), क्योंकि NULL डेस्टिनेशन परिभाषा के हिसाब से बहुत छोटी ही है। असली कॉल तो चरों और NUL को लिखती है, और *olen 8 के रूप में वापस आता है - टर्मिनेटर के बिना लंबाई - इसलिए बफ़र पहले से प्रिंट होने लायक़ C स्ट्रिंग है। अगर आप एक बाइट कम बफ़र सौंपें, तो आपको वही "बहुत-छोटा" कोड वापस मिलता है, ज़रूरी साइज़ के साथ *olen में, इसलिए फेलियर आपको बिल्कुल बता देता है कि आप कितने की कमी से रह गए। एक और नोट: लाइब्रेरी अपनी टेबल लुकअप्स कॉन्स्टेंट-टाइम हेल्पर्स के ज़रिए करती है, एक छोटी सी सावधानी जो ज़्यादातर एन्कोडर्स में नहीं दिखती।

APR-Util और GLib: बाक़ी दो

APR-Util का एन्कोडर int लंबाई वाला सादा जोड़ी-फ़ंक्शन है:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <apr-1.0/apr_base64.h>
int main(void) {
  const char *text = "Mane";
  int needed = apr_base64_encode_len((int)strlen(text));
  char *out = malloc((size_t)needed);
  int n = apr_base64_encode(out, text, (int)strlen(text));
  printf("n=%d (includes NUL) str=%s\n", n, out);
  free(out);
  return 0;
}

यहाँ apr_base64_encode_len() और रिटर्न वैल्यू दोनों NUL को गिनती में लेते हैं, इसलिए n चरों की गिनती से एक ज़्यादा है - OpenSSL और Mbed TLS से हिसाब-किताब का एक फ़र्क़, जो एक से ज़्यादा कोडबेस में अफ़-बाय-वन बग पैदा किए हैं। वही 32-बिट लंबाई सीमा लागू है: 2 GB के आसपास या उससे ऊपर के मानों के लिए यह औज़ार नहीं है। apr_base64_encode_binary() भी है, जो EBCDIC मशीनों पर इनपुट की EBCDIC-से-ASCII कन्वर्ज़न छोड़ देती है - उन मेन्फ़्रेम्स पर जहाँ वरना वह कन्वर्ज़न हो ही जाती, और बाक़ी हर जगह कोई फ़र्क़ नहीं। वही जोड़ी, binary वैरिएंट, और मेल कराने वाले डिकोड फ़ंक्शन वाकई अप्र-यूटिल का पूरा Base64 सर्फ़ेस हैं: कोई पूल-अलोकेटेड वैरिएंट नहीं, कोई स्ट्रीमिंग वैरिएंट नहीं, इसलिए ऊपर वाला मैलोक पैटर्न ही एकमात्र पैटर्न है।

GLib का एन्कोडर हीप-अलोकेटेड शैली का है - आपको NUL-टेर्मिनेटेड स्ट्रिंग मिलती है, साथ में एक ड्यूटी:

#include <stdio.h>
#include <glib.h>
int main(void) {
  const char *text = "Mane";
  gchar *enc = g_base64_encode((const guchar *)text, strlen(text));
  printf("%s\n", enc);
  g_free(enc);
  return 0;
}

कोई रैपिंग नहीं, NUL-टेर्मिनेटेड, g_free से फ्री करें - प्रोटोटाइप पर G_GNUC_MALLOC एनोटेशन ही वह चीज़ है जो स्टैटिक एनालायज़र्स को यह बताती है। जब आपको लाइन ब्रेक्स चाहिए, तो इनक्रिमेंटल जोड़ी वही औज़ार है: g_base64_encode_step() एक स्टेट इंटीजर और break_lines फ्लैग लेता है और बताता है कि उसने कितने आउटपुट बाइट्स लिखे, और g_base64_encode_close() आख़िरी आधी-अधूरी समूह को पूरा करती है। यह वही स्टेट-मशीन शैप है जो OpenSSL की स्ट्रीमिंग जोड़ी की है, बस GLib के पैरामीटर स्टाइल के साथ।

हाथ से बना URL-सेफ़ Base64

ऊपर की चारों लाइब्रेरियाँ स्टैंडर्ड वर्णमाला बोलती हैं: A-Z, a-z, 0-9, प्लस, और स्लैश। वेब, हालाँकि, ज़्यादा-से-ज़्यादा RFC 4648 के सेक्शन 5 वाले दूसरे डायलक्ट को बोलती है, जिसे base64url कहते हैं: वही एन्कोडिंग, जिसमें + बदलकर - और / बदलकर _, और जब लंबाई मालूम हो तो आख़िरी = पैडिंग छोड़ दी जाती है। JSON Web Tokens, OAuth state पैरामीटर, और अनेक API IDs इसे इस्तेमाल करते हैं, क्योंकि + और / दोनों URLs में ख़तरनाक हैं, जबकि - और _ अनरिज़र्व्ड चर हैं जो बिना रुके गुज़र जाते हैं। चूँकि कोई C लाइब्रेरी यह डायलक्ट नेटिव रूप से नहीं देती, आप खुद बनाते हैं - और बस दो छोटे बदलाव हैं, क्योंकि आपके पास पहले से एक स्टैंडर्ड-वर्णमाला एन्कोडर मौजूद है:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
static void to_base64url(const char *std_b64, char *out, size_t out_cap) {
  size_t i = 0;
  for (const char *p = std_b64; *p && *p != '='; p++) {
    char c = *p;
    if (c == '+') c = '-';
    if (c == '/') c = '_';
    out[i++] = c;
  }
  out[i] = '\0'; /* पैडिंग इरादे से छोड़ी गई */
}

इस्तेमाल दो कदमों का है - पहले स्टैंडर्ड एन्कोड करें, फिर अनुवाद करें:

unsigned char enc[32];
EVP_EncodeBlock(enc, (const unsigned char *)"hi>there", 8);
char url_safe[32];
to_base64url((const char *)enc, url_safe, sizeof(url_safe));
printf("%s\n", url_safe); /* aGk-dGhlcmU */

दो सावधानियाँ। पहली, लूप पहले = पर ही रुक जाता है, और वही पैडिंग को गिराता है - उसे "ठीक" मत करें, पैडिंग गिराना ही मक़्सद है (जिस रिसीवर को चाहिए, वह लंबाई से फिर जोड़ सकता है)। दूसरी, out को पूरे एन्कोडेड साइज़ के लिए बनाइए, कम नहीं: अनुवाद पैड तक चर-दर-चर है, इसलिए जितनी कैपेसिटी आपने स्टैंडर्ड रूप के लिए अलगाई है, वही बिल्कुल सही है। इंटरऑपेरैबिलिटी के लिए एक ईमानदार नोट: अगर आपके डेटा में + या / में मैप होने वाले बाइट्स बिल्कुल न हों, तो स्टैंडर्ड और URL-सेफ़ रूप एक जैसे होंगे, और भ्रम पर कोई कभी उँगली न उठाएगा - बग बस तब सतह पर आता है जब डेटा में अंततः एक ऐसा चर आ जाए। डायलक्ट को चैनल की ख़ासियत मानिए (URLs, टोकन), डेटा की नहीं।

टेक्स्ट और कैरेक्टर सेट्स: UTF-8 बस बाइट्स हैं

एक सवाल जो C के नए आने वालों को चौंकाता है: एक्सेंट वाले टेक्स्ट, इमोजी, CJK चरों के साथ क्या होता है? जवाब इस आर्टिकल का सबसे आज़ाद करने वाला तथ्य है - कुछ भी नहीं होता। Base64 बाइट्स पर काम करता है, और C बाइट्स की भाषा है। अगर आपका टेक्स्ट UTF-8 है (जो 2026 में, संभवतः है), तो "café" का UTF-8 एन्कोडिंग पाँच बाइट्स है - 63 61 66 c3 a9 - और Base64 वे पाँच बाइट्स बिल्कुल वैसे ही एन्कोड करता है जैसे कोई और पाँच बाइट्स, Y2Fmw6k= बनाकर। न कोई कैरेक्टरसेट पैरामीटर, न BOM, न कोई कन्वर्ज़न स्टेप, न कोई लाइब्रेरी कॉल। कोडेक को पता ही नहीं कि बाइट्स का मतलब क्या है, और परवाह भी नहीं; यही पूरा डिज़ाइन है।

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *utf8 = "caf\303\251"; /* UTF-8 में café */
  unsigned char enc[32];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)utf8,
                          (int)strlen(utf8));
  printf("%.*s\n", n, (char *)enc); /* Y2Fmw6k= */
  return 0;
}

इस सेक्शन के किनारों पर दो फंदे बसे हैं। पहला wchar_t: अगर आपका डेटा वाइड चरों के रूप में आया है, तो एन्कोडिंग से पहले उसे बाइट-सीक्वेंस में बदलना होगा (Linux पर UTF-8 में, wcstombs() के ज़रिए या आपके लोक़ेल मैकानिज़्म से) - wchar_t ऐरे का Base64 टेक्स्ट का एन्कोडिंग नहीं, आंतरिक रिप्रिज़ेंटेशन का एन्कोडिंग होता है, और वह प्लेटफ़ॉर्म-दर-प्लेटफ़ॉर्म अलग-अलग होगा। दूसरा सोर्स एन्कोडिंग है: आपके C फ़ाइल में स्ट्रिंग लिटरल सोर्स फ़ाइल की एन्कोडिंग में एन्कोडेड होता है (कोई भी आधुनिक प्रोजेक्ट, UTF-8), इसलिए "café" सीधे लिखने से तब काम चलेगा जब फ़ाइल सच में UTF-8 हो और कम्पाइलर को पता हो (आधुनिक टूलचेन्स में डिफ़ॉल्ट ही है)। वे बाइट्स एन्कोड करें जो आप भेजना चाहते हैं, और बाइट्स का मतलब क्या है, यह बात रिसीवर को संभालने दीजिए।

इमेजेस: बफ़र से स्ट्रिंग तक

वेब C में सबसे आम "असली" एन्कोडिंग काम: एक बिनरी फ़ाइल - JPEG, PNG, आइकन - को टेक्स्ट चैनल से गुज़रना है, इसलिए वह Base64 स्ट्रिंग बन जाती है। रेसिपी है फ़ाइल को बफ़र में पढ़ना, फ़ॉर्मूले से आउटपुट का साइज़ निकालना, एन्कोड करना, और आगे बढ़ जाना। फ़ाइल-पढ़ने वाला आधा हिस्सा सावधानी के हक़दार है, क्योंकि वही जगह है जहाँ C प्रोग्राम असल में टूटते हैं:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <openssl/evp.h>
int main(void) {
  FILE *f = fopen("photo.png", "rb");
  if (f == NULL) {
    return 1;
  }
  fseek(f, 0, SEEK_END);
  long size = ftell(f);
  fseek(f, 0, SEEK_SET);
  unsigned char *data = malloc((size_t)size);
  size_t got = fread(data, 1, (size_t)size, f);
  fclose(f);
  size_t out_cap = ((got + 2) / 3) * 4 + 1;
  unsigned char *enc = malloc(out_cap);
  int n = EVP_EncodeBlock(enc, data, (int)got);
  printf("png of %zu bytes becomes %d base64 chars\n", got, n);
  free(data);
  free(enc);
  return 0;
}

नोट्स: बिनरी पढ़ने के लिए rb - किसी भी प्लेटफ़ॉर्म पर बहस-बिना, क्योंकि text mode बाइट्स बदल सकता है और got बदल सकता है; साइज़ के लिए fseek/ftell जोड़ी (seek बिना pipes और sockets के लिए, बढ़ते हुए बफ़र में पढ़िए) - और एन्कोड के लिए got, size नहीं, क्योंकि short read एक असली संभावना है। 1 MB की इमेज करीब 1.33 MB का टेक्स्ट बन जाती है - वही कर, पहले से बिल किया हुआ, और यही वजह है कि JSON में base64 वाली इमेज पेलोड देखकर रुककर पूछना चाहिए कि क्या असली फ़ाइल अपलोड सस्ता तो नहीं होता।

फ़ाइलें और .b64 की आदत

इमेज वाले काम का दूसरा चेहरा: आपको फ़ाइल की Base64 रूप को डिस्क पर लिखना है - .b64 साइडकार, टेक्स्ट-सेफ़ स्टोर में बिनरी का बैकअप, या मेलर के लिए अटैचमेंट। वही गणित, अलग लिखने वाला। वह आदत जो अपनाने लायक़ है: टेक्स्ट आउटपुट को एक्सप्लिसिट लाइन ब्रेक्स के साथ लिखना, उस लंबाई पर जो रिसीविंग तरफ़ उम्मीद करती है - ईमेल के लिए 76, PEM-शैली खपत करने वालों के लिए 64, या बिल्कुल नहीं अगर रिसीवर आपका अपना कोड है:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  FILE *f = fopen("data.bin", "rb");
  if (f == NULL) {
    return 1;
  }
  fseek(f, 0, SEEK_END);
  long size = ftell(f);
  fseek(f, 0, SEEK_SET);
  unsigned char *data = malloc((size_t)size);
  size_t got = fread(data, 1, (size_t)size, f);
  fclose(f);
  unsigned char *enc = malloc(((got + 2) / 3) * 4 + 1);
  int n = EVP_EncodeBlock(enc, data, (int)got);
  FILE *out = fopen("data.b64", "w");
  for (int i = 0; i < n; i += 76) {
    int chunk = i + 76 < n ? i + 76 : n;
    fwrite(enc + i, 1, (size_t)(chunk - i), out);
    fputc('\n', out);
  }
  fclose(out);
  free(data);
  free(enc);
  return 0;
}

लूप 76-चर लाइनें लिखता है और आख़िर में एक छोटी लाइन; whitespace छोड़ने वाला डिकोडर (हर सीरियस ऐसा करता है) लाइन लंबाई की परवाह ही नहीं करता, और इसीलिए सलाह लेने वाला रिसीवर है, आपका स्वाद नहीं। अगर प्लेटफ़ॉर्म लाइन-एंडिंग चाहिए तो writing तरफ़ पर आउटपुट फ़ाइल को text mode (w) में रखिए, या wb अगर रिसीवर चर सख़ती से गिनता है - और अगर गिनता है, तो वह बिल्कुल वही चाहता है जिसे आपने वादा किया: 76 चर प्लस एक लाइन ब्रेक, बाक़ी कुछ नहीं। वही वादा, बाइट्स नहीं, .b64 फ़ाइल को फॉर्मेट बनाता है।

Data URIs: असली इम्बेडिंग

Data URIs (RFC 2397) डिकोडिंग आर्टिकल के पसंदीदा आगमन का दूसरा पहलू हैं: data:image/png;base64,... प्राप्त करने के बजाय, आप खुद बनाते हैं। रूप है data:, मीडिया टाइप, ;base64, कॉमा, पेलोड - और C में यह बस एन्कोड के बाद एक snprintf की बात है:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  /* 8-बाइट PNG सिग्नेचर */
  const unsigned char png_sig[8] =
    { 0x89, 'P', 'N', 'G', '\r', '\n', 0x1a, '\n' };
  unsigned char enc[32];
  int n = EVP_EncodeBlock(enc, png_sig, 8);
  char uri[96];
  snprintf(uri, sizeof(uri), "data:image/png;base64,%.*s",
      n, (char *)enc);
  printf("%s\n", uri);
  return 0;
}

तीन डिज़ाइन नोट्स। URI में जो मीडिया टाइप आप डालते हैं वह एक दावा है जिसकी ज़िम्मेदारी आपकी है - पहले असली फ़ाइल के magic बाइट्स स्निफ़ करें, वरना data:image/png जो JPEG लेकर घूम रहा है, वह हर खपत करने वाले को अलग ढंग से भ्रमित करेगा। ;base64 फ्लैग तब ज़रूरी है जब पेलोड Base64 हो; उसे छोड़ा तो पेलोड की जगह percent-encoded टेक्स्ट होना चाहिए, जो बिल्कुल अलग फॉर्मेट है। और RFC की खुद की राय है कि data URIs छोटे मानों के लिए हैं: HTML पेज में 5 MB लोगो को inline इम्बेड करना काम तो करता है, पर वह एक डिज़ाइन की बदबू है जिसे असली asset URL ठीक कर देता। वही रचना JSON APIs में हमेशा मिलती है जहाँ क्लाइंट को फ़ॉर्म डेटा के उसी रिक्वेस्ट में avatar चाहिए - एन्कोड कीजिए, आगे लगा दीजिए, भेज दीजिए।

HTTP और JSON: पेलोड्स जो बचकर निकलते हैं

C में एन्कोड करने का सबसे बड़ा आधुनिक कारण JSON है। JSON स्ट्रिंग चरों की ऐसी श्रृंखला है जिनमें escaping नियम हैं, और कच्चे बाइट्स वहाँ नहीं बैठते: स्ट्रिंग लिटरल के बीच NUL एक C सवाल है, JSON स्ट्रिंग के अंदर सादी लाइन ब्रेक अमान्य JSON है, और बेख़बर बाइट्स को एक परिभाषित escape कहानी चाहिए। Base64 पूरा सवाल छूटकर निकल जाता है, क्योंकि वह सिर्फ़ उन्हीं चरों को बनाता है जिन्हें JSON कभी escape नहीं करना पड़ता - 64 वर्णमाला चर प्लस, standard डायलक्ट में, = - जिनमें से कोई कोट नहीं और बैकस्लैश नहीं। बिनरी स्ट्रिंग के रूप में अंदर जाती है और दूसरी तरफ़ बिल्कुल वही निकलती है जैसी गई थी:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  unsigned char enc[64];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)"hello", 5);
  char json[160];
  snprintf(json, sizeof(json),
      "{\"avatar\": \"%.*s\"}", n, (char *)enc);
  printf("%s\n", json);
  return 0;
}

यह {"avatar": "aGVsbG8="} प्रिंट करता है - एक पूरा, वैध JSON ऑब्जेक्ट, कोई escape यंत्र नहीं, और %.*s की सटीकता लंबाई को बिल्कुल सही रखती है, भले ही आप कभी ऐसा एन्कोडर अपनाएँ जो NUL-terminate न करे। ईमानदार कीमत साइज़ है: जो हर बाइट आप JSON स्ट्रिंग के रूप में भेजते हैं, उसकी कीमत 4/3 बाइट हवा, प्लस फ़ील्ड का नाम और कोट्स, लगती है, इसलिए 10 KB की बिनरी JSON के अंदर 13.3 KB की स्ट्रिंग बन जाती है। कभी-कभी छोटे ब्लॉब्स (आइकन, थंबनेल, सिग्नेचर, टोकन्स) के लिए यह ठीक कीमत है; 500 MB के अपलोड के लिए यह वह आर्किटेक्चर है जिसे आप पछताएंगे, और असली फ़ाइल अपलोड वही औज़ार है। एक लाइन और: standard वर्णमाला का + और / JSON स्ट्रिंग के अंदर सुरक्षित हैं, पर अगर वही स्ट्रिंग बाद में URL क्वेरी में सफ़र करे, तो सुरक्षित नहीं - यह URL-सेफ़ सेक्शन का काम है।

JWTs: तीन हिस्से, एक वर्णमाला

base64url का C में फ्लैगशिप खपत करने वाला JSON Web Token है। RFC 7519 के अनुसार एक compact JWT तीन base64url-एन्कोडेड हिस्सों का है, जो डॉट से जुड़े हैं - हेडर, पेलोड, सिग्नेचर - और बनाना एक सुखद अभ्यास है, क्योंकि हर टुकड़ा वह फ़ंक्शन है जो आपके पास पहले से है: standard एन्कोड कीजिए, URL-सेफ़ में बदल दीजिए, sign कीजिए, दोहराएँ। यहाँ OpenSSL के HMAC से बना HS256 टोकन है:

#include <stdio.h>
#include <string.h>
#include <openssl/hmac.h>
#include <openssl/evp.h>
static void to_base64url(const char *std_b64, char *out, size_t out_cap) {
  size_t i = 0;
  for (const char *p = std_b64; *p && *p != '='; p++) {
    char c = *p;
    if (c == '+') c = '-';
    if (c == '/') c = '_';
    out[i++] = c;
  }
  out[i] = '\0';
}
int main(void) {
  const char *secret = "my-hmac-secret-key";
  const char *header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
  const char *payload = "{\"sub\":\"114365\",\"name\":\"Alice\"}";
  unsigned char hb[64], pb[64];
  EVP_EncodeBlock(hb, (const unsigned char *)header,
                  (int)strlen(header));
  EVP_EncodeBlock(pb, (const unsigned char *)payload,
                  (int)strlen(payload));
  char hu[64], pu[64];
  to_base64url((const char *)hb, hu, sizeof(hu));
  to_base64url((const char *)pb, pu, sizeof(pu));
  char signing_input[256];
  snprintf(signing_input, sizeof(signing_input), "%s.%s", hu, pu);
  unsigned char mac[EVP_MAX_MD_SIZE];
  unsigned int mac_len = 0;
  HMAC(EVP_sha256(), secret, (int)strlen(secret),
      (const unsigned char *)signing_input,
      (size_t)strlen(signing_input), mac, &mac_len);
  unsigned char mb[64];
  EVP_EncodeBlock(mb, mac, (int)mac_len);
  char mu[128];
  to_base64url((const char *)mb, mu, sizeof(mu));
  printf("%s.%s.%s\n", hu, pu, mu);
  return 0;
}

रचना दो बातें सिखाती है। पहली, साइनिंग इनपुट वे दो URL-सेफ़ हिस्से हैं जो एक बिंदु से जुड़े हैं - बिल्कुल वही बाइट्स जो रिसीवर देखेगा - इसलिए base64url में बदलना साइनिंग से पहले होना चाहिए, बाद में नहीं; standard-वर्णमाला रूप पर सिग्नेचर लगा दी तो रिसीवर की जाँच फेल होगी, जो एक ऐसा बग है जो कंपाइल होता है, चला भी जाता है, और ऐसा दिखता है जैसे कुंजी का मेल न खला हो। दूसरी, हेडर और पेलोड Base64 के आवरण में सादा JSON हैं: कोई भी इन्हें पढ़ सकता है, और यही डिज़ाइन है। टोकन एक हस्ताक्षरित नोट है, मोहरबंद लिफ़ाफ़ा नहीं - इसलिए उसमें कुछ भी मत डालो जिसे कोई बीच-काटने वाला user पढ़े और आपको परवाह न हो, और JWT पेलोड में कभी, कभी भी पासवर्ड मत डालना "क्योंकि वह एन्कोडेड है"। इस काम का Base64 हिस्सा छोटा और बोरिंग है, और यही JWT इम्प्लिमेंटेशन की सबसे बड़ी तारीफ़ है।

HTTP Basic Auth: टोकन बनाना

सबसे पुराना ऑथेंटिकेशन हेडर सबसे सादा Base64 काम भी है: username:पासवर्ड, standard वर्णमाला में एन्कोडेड, शब्द Basic के बाद। C में बनाना दो लाइन का काम है, और एकमात्र सूक्ष्मता यह है कि पासवर्ड में कॉलन हो सकता है (और रिसीविंग तरफ़ पर बँटवड़ा पहले कॉलन पर होना चाहिए):

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *user = "alice";
  const char *pass = "s3cr3t";
  char creds[128];
  snprintf(creds, sizeof(creds), "%s:%s", user, pass);
  unsigned char enc[160];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)creds,
                          (int)strlen(creds));
  printf("Authorization: Basic %.*s\n", n, (char *)enc);
  return 0;
}

यह Authorization: Basic YWxpY2U6czNjcjN0 प्रिंट करता है। RFC की चेतावनी भेजने वाली तरफ़ पर भी लागू होती है: यह एन्कोडिंग है, सुरक्षा नहीं। सादे HTTP कनेक्शन पर क्रेडेंशियल वायर पर किसी के लिए भी एक base64 -d दूर है, इसलिए Basic auth एक HTTPS-only आदत है। (आधुनिक विकल्प - bearer टोकन्स, mTLS - सब वही यंत्र दोबारा इस्तेमाल करते हैं: स्ट्रिंग बनाइए, एन्कोड कीजिए, हेडर में डाल दीजिए। प्रोटोकॉल के headers होने से ही Base64, HTTP की वह राह रहा है जिससे संरचित डेटा टेक्स्ट हेडर्स से तान-मोलाना होता रहा है।)

ईमेल और PEM: रैपिंग का घर

ईमेल ही वजह है कि लाइन रैपिंग मौजूद ही है। SMTP ने लाइन लंबाई पर सीमा लगाई है, इसलिए MIME ने एन्कोडेड लाइनों को 76 चरों तक सीमित किया (PEM, उसका पूर्वज, 64 तक), और तीस साल से हर मेल सिस्टम वह सीमा मानता आ रहा है। अगर आपका C प्रोग्राम इमेल बॉडी या अटैच्मेंट के लिए Base64 बनाता है, तो रैपिंग वैकल्पिक सज्जा नहीं है - रैप न किए गए 200 KB की लाइन को मेल इंफ््र्ट्रक्चर के कुछ हिस्से या तो मना कर देंगे या बिगाड़ देंगे। OpenSSL का स्ट्रीमिंग एन्कोडर रैप्ड आउटपुट फ़्री देता है (उसकी 64-चर की विरासत-लंबाई पर), और जब बिल्कुल 76 चाहिए, तो वन-शॉट नतीजे को रैप करना पाँच-लाइन लूप है:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  unsigned char enc[64];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)"hello world", 11);
  for (int i = 0; i < n; i += 76) {
    int chunk = i + 76 < n ? i + 76 : n;
    printf("%.*s\r\n", chunk, (char *)enc + i);
  }
  return 0;
}

\r\n पर नज़र रखिए: ईमेल को CRLF लाइन-एंडिंग चाहिए, और अगर एन्कोडेड टेक्स्ट कई MIME parts में से एक है, तो base64 ब्लॉक के चारों ओर सब कुछ वही नियम मानता है - लाइन लंबाइयाँ, CRLF एंडिंग्स, कोई इस्तिثा नहीं। PEM फ़ाइलें (ज़्यादातर keys और सर्टिफ़िकेट्स का फॉर्मेट) -----BEGIN और -----END मार्करों के बीच 64-चर लाइनों के साथ वही विचार इस्तेमाल करती हैं, और key दोबारा save करने पर OpenSSL के टूल्स उस कवच को देखने की उम्मीद करते हैं - इसलिए अगर आपका प्रोग्राम PEM को छूता है, तो 64 पर रैप कीजिए और लेबल्स बरकरार रखिए। हर दूसरी जगह - JSON, URLs, APIs, डेटाबेस - RFC का नियम लागू होता है और आप बिल्कुल रैप नहीं करते।

कॉन्फ़िग और कॉलम्स के रास्ते मान छुपाकर ले जाना

एक चुपके-सिपके इस्तेमाल: ऐसे मान जो टेक्स्ट फॉर्मेट को तोड़ देते, उन्हें Base64 में पैक कर दिया जाता है ताकि न तोड़ें। सेमीकॉलन वाली डेटाबेस DSN, कोट्स वाला पासवर्ड, लाइन ब्रेक वाला टोकन - ओप्स वाला इंसान एक बार एन्कोड कर देता है और कॉन्फ़िग फ़ाइल कभी तकलीफ़ देने वाले चरों को नहीं देखती:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  const char *dsn = "pg:host=db;password=qu\"ote";
  unsigned char enc[128];
  int n = EVP_EncodeBlock(enc, (const unsigned char *)dsn,
                          (int)strlen(dsn));
  printf("DB_DSN_B64=%.*s\n", n, (char *)enc);
  return 0;
}

प्रोग्राम वही लाइन प्रिंट करता है जो .env फ़ाइल में पेस्ट करनी है, और बाद में उसे पढ़ने वाला C कोड एक getenv प्लस एक डिकोड है। तीन ईमानदार चेतावनियाँ, सब इस बारे में कि यह क्या नहीं है। यह एन्क्रिप्शन नहीं है: जो भी कॉन्फ़िग फ़ाइल पढ़ सकता है, वह एक कॉल में मान डिकोड कर सकता है, इसलिए कभी सिक्रिट को Base64 में पैक करके उसे सुरक्षित मत कहिए। यह escaping भी नहीं है: अगर फॉर्मेट को संरचना बनी रहनी चाहिए, तो सच्ची एन्कोडिंग (URLs के लिए percent-encoding, JSON के लिए JSON escaping) ही सही औज़ार है, और Base64 उन मानों के लिए है जो वे फॉर्मेट नहीं बयां कर सकते - बिनरी वाले। और इसकी कीमत साइज़ में लगती है: डेटाबेस TEXT कॉलम में Base64 के रूप में रखा मान मूल से करीब 33 प्रतिशत ज़्यादा जगह लेता है, जो टोकन्स के लिए ठीक है और फ़ाइल कॉलम्स के लिए असली नंबर है (वही तो BLOB कॉलम्स के लिए हैं)।

Shell से एन्कोडिंग

cc कॉल की तरफ़ हाथ बढ़ाने से पहले याद रखिए कि दोनों स्टैंडर्ड टूल्स एन्कोड करते हैं, और तेज़ी से। coreutils सामान्य औज़ार है: base64 डिफ़ॉल्ट 76-चर रैप के साथ एन्कोड करता है, -w कॉलम बदलता है, और -w 0 रैपिंग बिल्कुल बंद कर देता है:

base64 photo.png > photo.b64
base64 -w 0 photo.png > photo-oneline.b64
cat note.txt | base64 -w 0

OpenSSL का टूल वही काम TLS-वंश के पैकेजिंग के साथ है: openssl base64 (openssl enc -base64 का दोस्ताना alias) 64 चरों पर रैप करता है और -A उसे एक लाइन कर देता है:

openssl base64 < photo.png > photo.b64
openssl base64 -A < photo.png > photo-oneline.b64

रैपिंग के फ़र्क़ की परवाह क्यों? क्योंकि अगर डाउनस्ट्रीम parser चर गिनता है, तो दोनों टूल्स के डिफ़ॉल्ट आउटपुट आपस में नहीं बदल सकते - लाइन पर 76 के मुकाबले लाइन पर 64 फ़ाइल में दिखने लायक़ फ़र्क़ है, और whitespace हटाने वाला parser परवाह नहीं करता जबकि लाइन लंबाई जाँचने वाला बिल्कुल परवाह करता है। जब आपका C प्रोग्राम बनाने वाला है और shell खपत करने वाला (या उल्टा), तो पहले रैप पर सहमत हो जाइए। BSD-फ़्लेवर वाली सिस्टम्स के लिए एक डायलक्ट नोट: वहाँ डिकोड फ्लैग इतिहास में -D रहा है, और पुराने macOS रिलीज़ उसे आज भी याद रखते हैं; एन्कोड वाली तरफ़ हर जगह base64 है, जो वैसे भी इस सेक्शन का एकमात्र दिशा है।

बड़े सामान की स्ट्रीमिंग

कई-गीगाबाइट की फ़ाइल को एक malloc में एन्कोड करना वह मेमोरी सवाल है जो आपको चाहिए ही नहीं था। स्ट्रीमिंग राह बिल्कुल इसी लिए मौजूद है, और OpenSSL के ब्लॉक-अनुशासन से कोड लगभग साधारण बन जाता है: EVP_EncodeUpdate में फ़ाइल जितना डेटा दे, उतना डाल दीजिए, उसे हर आधे-पूरे 48-बाइट ब्लॉक का बाक़ी हिस्सा कॉन्टेक्स्ट में पकड़े रहने दीजिए, और हर ब्लॉक के 65 आउटपुट बाइट्स सीधे डेस्टिनेशन फ़ाइल में लिख दो। पीक मेमोरी आपके दो बफ़र हैं - कुछ दसियों किलोबाइट्स - फ़ाइल चाहे कितनी बड़ी हो:

#include <stdio.h>
#include <string.h>
#include <openssl/evp.h>
int main(void) {
  EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
  if (ctx == NULL) {
    return 1;
  }
  EVP_EncodeInit(ctx);
  FILE *in = fopen("video.mp4", "rb");
  FILE *out = fopen("video.b64", "w");
  if (in == NULL || out == NULL) {
    return 1;
  }
  char inbuf[48 * 1024];            /* 48 का गुणज: साफ़ लाइनें */
  unsigned char outbuf[1024 * 65 + 65];  /* हर 48-बाइट ब्लॉक पर 65 आउटपुट बाइट्स, साथ में हेडरूम */
  size_t got;
  while ((got = fread(inbuf, 1, sizeof(inbuf), in)) > 0) {
    int outl = 0;
    EVP_EncodeUpdate(ctx, outbuf, &outl,
        (const unsigned char *)inbuf, (int)got);
    fwrite(outbuf, 1, (size_t)outl, out);
  }
  unsigned char tail[66];
  int outl = 0;
  EVP_EncodeFinal(ctx, tail, &outl);
  fwrite(tail, 1, (size_t)outl, out);
  EVP_ENCODE_CTX_free(ctx);
  fclose(in);
  fclose(out);
  return 0;
}

उस लूप में काम कर रही दो डिज़ाइन तफ़सीलें हैं। इनपुट बफ़र 48 बाइट्स का गुणज है, इसलिए हर कॉल एन्कोडर को पूरे ब्लॉक ही सौंपती है और जो लाइन वह लिखे वह पूरी 64-चर लाइन है; फाइनल कॉल फिर असली टेल को रैप करती है। अगर आपका इनपुट अलग-अलग आकारों में आए (सॉकेट, धीमी डिस्क), तो कॉन्टेक्स्ट फिर भी मिस-अलाइनमेंट सही से पकड़ लेता है - 48-के-गुणज का चयन आउटपुट भविष्यवाणी-योग्यता के लिए है, सटीकता के लिए नहीं। दूसरी तफ़सील आउटपुट बफ़र का साइज़ है: हर 48 इनपुट बाइट्स पर 65 बाइट्स प्लस आख़िरी ब्लॉक की हेडरूम (66), जो गणित वाले सेक्शन का रैप्ड फ़ॉर्मूला हर चंक पर लागू किया हुआ है। प्रोग्रेस रिपोर्टिंग एक लाइन है - out में लिखे गए बाइट्स को फ़ाइल साइज़ से निकाले गए कुल के साथ गिनिए - और चूँकि एन्कोडिंग डेटा को बढ़ाती है, आउटपुट फ़ाइल इनपुट से करीब 33 प्रतिशत बड़ी उतरेगी: डिस्क की बिल पहले से काट लीजिए।

एन्कोडिंग के फंदे, सब C-ख़ास

एक जगह इकट्ठे, और सब C के आकार के, वही फंदे:

  • अफ़-बाय-वन, दोनों दिशाओं में। OpenSSL NUL के बिना लंबाई रिटर्न करता है; Mbed TLS की साइज़ क्वेरी NUL की जगह समेटती है; APR अपनी लंबाइयों में NUL गिनता है। तीन लाइब्रेरियाँ, तीन हिसाब-किताब की रवायतें। बफ़र-साइज़ फ़ंक्शन एक बार लिखिए (गणित वाला सेक्शन) और कॉल साइट पर सिर में अंक-गणित करना बंद कीजिए।
  • NUL वह बाइट है जिसकी कीमत आपको चुकानी होगी। इस आर्टिकल के हर एन्कोडर को टर्मिनेटर के लिए आउटपुट में एक अतिरिक्त बाइट चाहिए, और ऐसे ही हर बफ़र-अपेंड को भी जिसे आप हाथ से लिखें। encoded_chars(n) के बिल्कुल बराबर साइज़ वाला बफ़र उस पल एक बाइट कम हो जाता है जब किसी को printf("%s") का काम चाहिए हो।
  • एन्कोडिंग फेल नहीं हो सकती, इसलिए आप उसे ओवरफ्लो होने न दीजिए। कोई एरर कोड नहीं है जो छोटे बफ़र को पकड़े - एन्कोडर खुशी-खुशी सिरा पार लिखेगा। C में Base64 एन्कोडिंग की फेलियर मॉडल बिल्कुल आपकी है: सही साइज़ दीजिए, वरना वह बिना किसी डायनॉस्टिक के मेमोरी भ्रष्ट करेगा।
  • डबल एन्कोडिंग क्लासिक चुप-चाप बग है। जो मान पहले से Base64 है और उसे फिर एन्कोडर से गुज़ारा जाए, वह एक बिल्कुल जायज़ Base64 स्ट्रिंग बनाता है जो डिकोड होने पर डेटा की जगह Base64 स्ट्रिंग देती है। लक्षण - "डिकोड तो होता है, मगर ग़लत चीज़ में" - ढूँढने में एक दोपहर लगती है। अगर कोई मान "प्री-एन्कोडेड" रूप में आए, तो यह मानने से पहले कि वह कच्चा डेटा है, जाँच लीजिए कि लंबाई चार का गुणज है और सिर्फ़ वर्णमाला के चर हैं; अगर वह एन्कोडेड है, तो एन्कोड स्किप कीजिए।
  • URLs में प्लस चिन्ह। standard-वर्णमाला आउटपुट को क्वेरी स्ट्रिंग में डालने पर आपकी सर्वर फ़ॉर्म parse करने तक पहुँचने पर + space बन चुका होता है - percent/form एन्कोडिंग में + space होता है। URLs में सफ़र करने वाले टोकन्स और IDs को URL-सेफ़ डायलक्ट चाहिए, बिना शर्त।
  • पाइप के ग़लत किनारे पर text mode। बिनरी फ़ाइल को text mode में पढ़ने से बाइट्स बदल सकते हैं (कुछ प्लेटफ़ॉर्म्स पर) और लंबाई बदल सकती है; ग़लत line-ending रीवाज़ के साथ रैप्ड Base64 लिखने से वे रिसीवर टूट जाते हैं जो चर गिनते हैं। बिनरी इनपुट के लिए rb, जहाँ spec एक माँगता हो वहाँ एक्सप्लिसिट \r\n या \n, और कभी C runtime को अपनी लाइन-एंडिंग चुपचाप तय करने न दीजिए।
  • साइज़ गणित में int ओवरफ्लो। int अंक-गणित में ((n + 2) / 3) * 4 करीब 1.5 GB से ऊपर के इनपुट्स पर ओवरफ्लो करता है, एक छोटी सकारात्मक "ज़रूरी साइज़" और हीप smash देकर। गणित size_t (या uint64_t) में कीजिए, और वही वजह है कि APR के int-आधारित API में वह 2 GB सीमा है जो इंजीनियरिंग से नहीं हट सकती।
  • रैपिंग वहाँ जहाँ रिसीवर की उम्मीद न हो। RFC 4648 कहता है: लाइन फ़ीड्स नहीं, जब तक आसपास की spec माँगे। JSON स्ट्रिंग वैल्यू के अंदर न्यूलाइन अमान्य है; URL के अंदर वह एक अलग रिक्वेस्ट है। मेल के लिए रैप कीजिए, PEM के लिए रैप कीजिए, और कहीं और नहीं।

छोटी सी चेकलिस्ट

बफ़र साइज़ फ़ॉर्मूले से निकालिए, अंदाज़े से नहीं, और पूरे कोडबेस के लिए एक ही साइज़ फ़ंक्शन रखिए। (pointer, length) को साथ ही रखिए, भले ही बफ़र NUL-terminated हो, क्योंकि लंबाई ही करार है और NUL सिर्फ़ सुविधा है। डायलक्ट चैनल से चुनिए: JSON और bodies के लिए standard, URLs और टोकन्स के लिए URL-सेफ़, मेल और कवच के लिए रैप्ड, और बाक़ी हर जगह अनरैप्ड। data URI में MIME type दावा करने से पहले magic बाइट्स जाँचिए। Base64 को कभी एन्क्रिप्शन, percent-encoding की जगह, या सिक्रिट छुपाने की जगह के रूप में मत इस्तेमाल कीजिए - यह संदूक है, ताला नहीं। और जब डेटा बड़ा हो, तो उसे स्ट्रीम कीजिए: ब्लॉक-आधारित एन्कोडर बिल्कुल इसी लिए बनाए गए हैं, और स्थिर मेमोरी ही पूरा मक़्सद है।

इतिहास: पैकिंग की मानकीकरण की कहानी

एन्कोडर का इतिहास लाइन लंबाइयों की कहानी है। पहला Base64 1990 के दशक की शुरुआत का एक C प्रोग्राम था। Privacy-Enhanced Mail (RFC 1421, 1993) को 7-बिट mail से बिनरी ले जाना था, और उसके लेखकों ने 64-चर लाइनों में हर चर पर छह बिट्स चुना - वह 64, SMTP की लाइन-लंबाई सहनशीलता का अवशेष है, और C कोड ने पैकिंग टेबल-लुकअप दर टेबल-लुकअप की। जब MIME ने वेब के लिए वही वर्णमाला मानकीकृत की (1993 में RFC 1521, 1996 में RFC 2045), उसने लाइन को 76 चरों तक ढीला किया, और दुनिया ने दो आदतें उठाईं - 64 और 76 - जो दोनों "वही" Base64 लाइन लंबाई दावा करती थीं। एन्कोडर भी मेल कर गए: OpenSSL का स्ट्रीमिंग राह 64 पर ही रहा (उसकी PEM विरासत), coreutils का टूल 76 चुना (उसकी MIME वंशजता), और उसी मशीन पर दोनों टूल्स आज भी असंतुष्ट हैं कि लाइन ब्रेक कहाँ जाएं। मानदंड ने अंततः 2006 में पक्ष लिया: RFC 4648 ने कहा कि implementations लाइन फ़ीड्स बिल्कुल नहीं जोड़ें, जब तक संदर्भित spec उन्हें खुद से ऐसा न करवाए, और इसीलिए इस आर्टिकल की हर लाइब्रेरी unwrapped आउटपुट में डिफ़ॉल्ट है, और रैपिंग अब सिर्फ़ ईमेल और कवच के लिए opt-in फीचर है। वर्णमाला स्वयं, पैडिंग के नियम, और "खाली पैड बिट्स ज़ीरो होने चाहिए" की कैनोनिकलिटी नियम, वे उन पुरानी PEM और MIME RFCs से हैं, और 4648 उन्हें इस परिवार के कैनोनिकल नियमों के रूप में दोबारा बयान करता है। और RFC का सेक्शन 11 एक reference implementation की तरफ़ इशारा करता है - एक ISO C99 प्रोग्राम, जो बाहर hosted है क्योंकि कोड स्वयं "procedural कारणों से इस RFC में शामिल नहीं किया जा सकता था" - एक और याद दिलाती है कि इस फॉर्मेट में C द्वितीय श्रेणी का नागरिक नहीं है। C स्टैंडर्ड लाइब्रेरी, अपनी तरफ़ से, कभी साथ नहीं चल पाई: C89 1990 में जम गई, इनमें से कुछ भी होने से पहले, और 2024 की C23 आज भी Base64 फ़ंक्शन के बिना शिप होती है। तो आप जो लाइब्रेरियाँ link करते हो, वही मानदंड हैं, और उनके बीच का चयन छोटा पर असली डिज़ाइन फैसला है - और यही इस आर्टिकल की बात रही है।

अजीब-सी छोटी बातें

कुछ तथ्य जो बस मज़ेदार हैं, सब C में पैकिंग वाली तरफ़ के बारे में:

  • "64" ही वह रेडिक्स है: हर आउटपुट चर छह बिट्स है, और 2 की घात 6 यानी 64 है। यह फॉर्मेट अपनी वर्णमाला को वैसे ही नाम देता है जैसे C अपने इंटिजर्स का नाम रखता है - उस नंबर के असल होने वाले हिसाब से।
  • OpenSSL के स्ट्रीमिंग एन्कोडर का 48-बाइट ब्लॉक बेवजह की हिसाब-किताब नहीं है: 48 इनपुट बाइट्स बिल्कुल 16 समूहों के 3 हैं, और 64 आउटपुट चर बिल्कुल 16 समूहों के 4 हैं। दोनों नंबर 16 के गुणज हैं, और वह गोलता है जो हार्डवेयर और कैश लाइनों को खुश करती है - या कम-से-कम कोड पढ़ने वाले इंसानों को।
  • एक इनपुट बाइट चार चरों में एन्कोड होता है, जिनमें दो = हैं। सबसे छोटा संभव ख़ाली-न-होने वाला पेलोड 50 प्रतिशत पैडिंग है - फॉर्मेट की सबसे बर्बाद करने वाली एन्कोडिंग, और वही जो हर टेस्ट suite इस्तेमाल करता है, क्योंकि इसे ग़लत लिखना बहुत आसान है।
  • Mbed TLS इस आर्टिकल का एकमात्र एन्कोडर है जो अपनी लुकअप्स कॉन्स्टेंट टाइम में करता है, क्योंकि एम्बेडेड crypto लिखने वाले वेरिएबल-टाइम टेबल इंडेक्सिंग पर भरोसा ही नहीं करते, भले ही वह कोडेक साइफ़र न हो। वह डर-सतर्कता दूसरों में भी फैल जाती है।
  • कैनोनिकल एन्कोडिंग का नियम - खाली पैड बिट्स ज़ीरो होने चाहिए - सुनने में साधारण लगता है, जब तक आप यह न सीख लें कि इसे तोड़ने का मतलब है कि दो अलग-अलग स्ट्रिंग्स एक ही बाइट्स में डिकोड हो सकती हैं, और यह मौजूद हर "क्या यह स्ट्रिंग उस फ़ाइल की एन्कोडिंग है?" जाँच को तोड़ देता है। आपके एन्कोडर सब पालन करते हैं; यही वजह है कि base64 एक hash-stable रिप्रिज़ेंटेशन है और कंटेंट स्टोर में फ़ाइल-नाम की जगह ले सकता है।
  • OpenSSL का EVP_EncodeBlock उन दुर्लभ C फ़ंक्शन्स में से एक है जिनके रिटर्न वैल्यू, उसका अपना आउटपुट, और उसका NUL टर्मिनेटर तीनों मेल खाते हैं: यह n चर लिखता है, एक NUL, और n रिटर्न करता है। अफ़-बाय-वन के लिए मशहूर भाषा में, यह एक शांति का पल है।
  • APR-Util यहाँ का एकमात्र एन्कोडर है जो EBCDIC के मतलब पूछता है, क्योंकि Apache आज भी उन मशीनों पर चलता है जहाँ अक्षरों का क्रम ASCII जैसा नहीं होता। उन मशीनों पर, किसी स्ट्रिंग का "एन्कोड" करना पहले चुपचाप उसकी वर्णमाला का क्रम बदलना समेटता है।
  • खाली इनपुट हर लाइब्रेरी में खाली स्ट्रिंग में एन्कोड होता है, न पैडिंग के साथ, न न्यूलाइनों के। फॉर्मेट की आइडेंटिटी element, चारों में मौजूद और सही, और यही वजह है कि यह वह सबसे सस्ता यूनिट टेस्ट है जो आप कभी लिखेंगे।

डिकोडर की तरफ़ उलटते हुए

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

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

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