Java में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड
हाल यह है: आपके पास बाइट्स हैं। एक फ़ाइल, एक पासवर्ड, एक सर्टिफ़िकेट, एक 13-बाइट का स्वागत-संदेश, एक 200-मेगाबाइट का अपलोड। और आपको उसे किसी ऐसे चीज़ के अंदर चाहिए जो सिर्फ़ टेक्स्ट समझती है: JSON फ़ील्ड, HTTP हेडर, डेटाबेस कॉलम, URL, कॉन्फ़िग फ़ाइल। यह ही Base64 की पूरी नौकरी है, और यह गाइड उसे अच्छे से करने का Java हैंडबुक है। एक तेज़ ओरिएंटेशन, क्योंकि होम पेज पर फ़ॉर्मैट का चरण-दर-चरण दौर होता है: Base64 हर तीन बाइट्स डेटा को 64-चिह्नों की वर्णमाला से चार चरों में रीवाइट करता है, और आख़िरी चंक छोटा हो तो एक-दो = पैड टँग दिए जाते हैं। इस सफ़र की कीमत साइज़ है: हर तीन बाइट्स चार चर बन जाते हैं, इसलिए एन्कोडेड आउटपुट इनपुट से करीब 33 फ़ीसदी बड़ा पड़ता है, और लाइन ब्रेक्स की बात हो तो थोड़ा और भी।
हेडलाइन न्यूज़ है, और वह अच्छी है। 18 मार्च 2014 से हर JDK स्टैंडर्ड लाइब्रेरी में एक पूरा Base64 टूलकिट साथ ला रहा है: java.util.Base64। कोई डाउनलोड नहीं, कोई Maven कोऑर्डिनेट नहीं, कोई नेटिव लाइब्रेरी नहीं। एक इम्पोर्ट, तीन एन्कोडर शख्सियतें, और Java 8 से आज के Java 26 तक वही व्यवहार। इस लेख की हर चीज़ उसी एक क्लास पर बनी है, और वह डेटा पर कभी थ्रो नहीं करता: एन्कोडर की नौकरी ग़ैर-वैध इनपुट पर फेल ही नहीं हो सकती, क्योंकि हर संभव बाइट एन्कोड हो सकता है।
शुरू करने से पहले एक ईमानदार सीमा: यह कहानी का एन्कोडर वाला हिस्सा है। आप सीखेंगे वह स्ट्रिंग-से-बाइट्स का फैसला जो सहीपन तय करता है, पैडिंग और रैपिंग के डायल्स, base64url और उसका टोकन के लिए नो-पैडिंग मोड, और वे यूज़ केसेज़ जहाँ Java डेवलपर एन्कोडेड आउटपुट से सबसे ज़्यादा मिलते हैं। डिकोडिंग, जहाँ ज़्यादातर असली दर्द बसता है, अपने ही गाइड में मिलती है और इस लेख के आख़िर में लिंक है।
एक इम्पोर्ट, ज़ीरो डाउनलोड
Java में Base64 इंस्टॉल करना वही एक-लाइन जवाब है जो आप व्हाइटबोर्ड पर देते हैं: "वह JDK में है।" क्लास java.util.Base64 1.8 से java.base मॉड्यूल का हिस्सा है, और बारह साल बाद भी उसका javadoc Since: 1.8 ही लिखता है। आप जो इंस्टॉल करते हैं, सिर्फ़ एक JDK है: किसी भी वेंडर (Oracle, Eclipse Temurin, Amazon Corretto, Zulu) की Java 8 या नई चलेगी, और Debian-बेस्ड बॉक्स पर वह एक ही कमांड है:
sudo apt install openjdk-17-jdk-headless
API एक फ़ैक्टरी है: आप कभी एन्कोडर नहीं बनाते; आप क्लास से एक माँगते हैं। एन्कोडर की तरफ़ चार दरवाज़े हैं, और सभी नेस्टेड क्लास Base64.Encoder के इंस्टेंस वापस करते हैं:
| फ़ैक्टरी मेटोड | वर्णमाला | आउटपुट शक्ल |
|---|---|---|
getEncoder() |
A-Z a-z 0-9 + / |
पैडेड, लाइन ब्रेक्स नहीं |
getUrlEncoder() |
A-Z a-z 0-9 - _ |
पैडेड, लाइन ब्रेक्स नहीं |
getMimeEncoder() |
A-Z a-z 0-9 + / |
पैडेड, 76-चर लाइनें, CRLF |
getMimeEncoder(int, byte[]) |
A-Z a-z 0-9 + / |
पैडेड, आपकी लाइन लेंथ, आपका सेपरेटर |
तीन प्रॉपर्टीज़ आगे से जानने लायक हैं। इंस्टेंस थ्रेड-सेफ़ हैं, और फ़ैक्टरी हर कॉल पर वही साझा इंस्टेंस वापस करती है, इसलिए Base64.getEncoder() == Base64.getEncoder() सच है; एक स्टैटिक फ़ील्ड में बनाइए और हर जगह साझा कीजिए। एन्कोडर डेटा पर कभी थ्रो नहीं करते: हर बाइट वैल्यू का एक एन्कोडिंग है, इसलिए कोई "ग़ैर-वैध इनपुट" अवस्था हैंडल करने को नहीं है, और एकमात्र एक्ससेप्शन जो आपको मिलेंगे वे ग़लत कन्फ़िग (ख़राब लाइन सेपरेटर) या बहुत छोटे डिस्टिनेशन अरेय़ के बारे में हैं। और इस लिस्ट के हर एन्कोडर डिफ़ॉल्ट में पैडिंग जोड़ते हैं; उसे बंद करने वाला डायल, withoutPadding(), base64url सेक्शन में मिलेगा, क्योंकि बस वहाँ ही आपको वह चाहिए।
पुरानी लाइब्रेरियाँ कोडबेस में अब भी मिलती रहेंगी, इसलिए पूरे मंज़ारे का एक तेज़ नक्शा। Apache Commons Codec (वर्तमान 1.22.1) 1.0 से अपनी खुद की org.apache.commons.codec.binary.Base64 शिप करता रहा है, जिसका Builder API सख़्त-या-लचिला पॉलिसी, लाइन लेंथ और सेपरेटर को डायल की तरह खोलता है; वह सही टूल सिर्फ़ तब है जब आपको pre-Java-8 JVMs का सपोर्ट करना पड़े। Guava com.google.common.io.BaseEncoding देता है, एक समान क़ुशोर का वयोवृद्ध, जो बिग डेटा स्टैक्स में अभी भी आम है। किसी भी आधुनिक JVM पर चलने वाली चीज़ के लिए java.util.Base64 डिफ़ॉल्ट है: ज़ीरो डिपेंडेंसी, और कम्युनिटी बेंचमार्क्स बार-बार उसे इस गिरोह का सबसे तेज़ पाते हैं (विस्तार से वह बात सुरक्षा और स्पीड वाले सेक्शन में)।
आपकी पहली एन्कोड
एन्कोडिंग की ज़िंदगी का नब्बे फ़ीसदी हिस्सा तीन लाइनों में समा जाता है। यहाँ पूरी रस्म है, उसी सबसे छोटे उदाहरण से जो Wikipedia की Base64 आर्टिकल वर्णमाला समझाने के लिए इस्तेमाल करती है:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class FirstEncode {
public static void main(String[] args) {
byte[] text = "Man".getBytes(StandardCharsets.UTF_8);
String packed = Base64.getEncoder().encodeToString(text);
System.out.println(packed); // TWFu
}
}
स्ट्रिंग TWFu वही उदाहरण है जो Wikipedia की Base64 आर्टिकल वर्णमाला समझाने के लिए इस्तेमाल करती है, इसलिए अगर आपका एन्कोडर "Man" को इसमें बदल दे, तो मशीन ईमानदार है। पर उस उदाहरण की पहली लाइन को देखिए, क्योंकि बस वही लाइन है जहाँ Java में एन्कोडिंग असल में होती है। जान-बूझ कर encodeToString(String) नाम का कोई मेटोड नहीं है। Java String UTF-16 कोड यूनिट्स की सिलसिला है, बाइट्स की नहीं, और Base64 एक बाइट फ़ॉर्मैट है, इसलिए API आपको बाइट का सवाल खुद तय करने पर मजबूर करता है: "Man".getBytes(StandardCharsets.UTF_8)। वही एक कॉल, एक्सप्लिसिट कैरेक्टर सेट के साथ, ही वह जगह है जहाँ "café" अगले सौ सालों तक सही रहता है, और यह पूरे लेख की सबसे ज़रूरी आदत है। अगला सेक्शन बस इसी के लिए है, क्योंकि दूसरा रास्ता क्लासिक मोजिबैक बग है।
दूसरी लाइन पर दो नोट्स। encodeToString() एन्कोडेड बाइट्स से बनी String वापस करता है; javadoc बताता है कि वह रिज़ल्ट को ISO-8859-1 कैरेक्टर सेट से बनाता है, जो अभ्यास से कोई बड़ी बात नहीं क्योंकि हर Base64 आउटपुट चर सादा ASCII है और Latin-1, UTF-8, और कैरेक्टर सेट ज़ूल के ज़्यादातर हिस्सों में एक जैसा दिखता है। और अगर आप आउटपुट बफ़र खुद संभालना चाहते हैं, तो encode(byte[]) एक नया byte[] वापस करता है, और encode(byte[] src, byte[] dst) आपके दिए डिस्टिनेशन में लिखता है, गिनती वापस करके (डिस्टिनेशन छोटा पड़ने पर IllegalArgumentException: Output byte array is too small for encoding all input bytes थ्रो करता है, एक बाइट तक नहीं लिखे)।
कैरेक्टर सेट का फैसला
स्ट्रिंग-से-बाइट्स का कदम ठोस बनाने के लिए क्लासिक केस देखिए। "café" एक ही शब्द है, पर बाइट्स में वह पूरी तरह आपके चुने कैरेक्टर सेट पर निर्भर है:
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class CharsetEncode {
public static void main(String[] args) {
byte[] utf8 = "café".getBytes(StandardCharsets.UTF_8);
byte[] latin1 = "café".getBytes(Charset.forName("ISO-8859-1"));
System.out.println(utf8.length + " vs " + latin1.length);
// 5 vs 4: एक्सेंट UTF-8 में दो बाइट्स है, Latin-1 में एक
System.out.println(Base64.getEncoder().encodeToString(utf8));
// Y2Fmw6k=
System.out.println(Base64.getEncoder().encodeToString(latin1));
// Y2Fm6Q==
}
}
एक शब्द के लिए दो अलग Base64 स्ट्रिंग्स, और दोनों "सही" हैं जितना समय तक पाठक को बताया जाए कि उसे किस कैरेक्टर सेट से पढ़ना है। पूरा सबक एक लाइन में: एन्कोडर आपके सौंपे बाइट्स के प्रति वफ़ादार है, और बाइट्स के लिए ज़िम्मेदार आप हैं। अभ्यास में मतलब यह: अपने साथी के साथ UTF-8 पर तह कीजिए, StandardCharsets.UTF_8 एक्सप्लिसिट पास कीजिए, और कैरेक्टर सेट को स्पेक, स्कीमा, या कॉमिट मैसेज में लिख दीजिए, क्योंकि लेने वाली तरफ़ का कोई इसे Base64 अकेले से अंदाज़ा नहीं लगा सकता। इस बग की डिकोडर-तरफ़ की जुड़वाँ बहन sister गाइड की विषय-वस्तु है।
एक वर्ज़न नोट, क्योंकि यह लेज़ी कोड के फेल मोड को बदल देता है। बिना-एरगुमेंट new String(bytes) और बिना-कैरेक्टर-सेट String.getBytes() प्लेटफ़ॉर्म डिफ़ॉल्ट कैरेक्टर सेट इस्तेमाल करते हैं, जो इतिहास में Windows पर Cp1252 था और Linux पर कुछ भी लोकल-डिपेंडेंट। JDK 18 (JEP 400, "डिफ़ॉल्ट रूप में UTF-8") से डिफ़ॉल्ट हर प्लेटफ़ॉर्म पर UTF-8 है, इसलिए आधुनिक JVM पर लेज़ी फ़ॉर्म बस सही होती है। पर इससे वह सुरक्षित नहीं बनती: आपका कोड उस JDK से लंबा ज़िंदा रहेगा जिसके लिए वह लिखा गया, और जो व्यक्ति इसे वारिस करे उसे पता होने की ज़रूरत नहीं चाहिए कि डिफ़ॉल्ट क्या है। कैरेक्टर सेट लिखिए।
एक संबंधित डिज़ाइन बारीकी: API में कहीं भी encode(String) नाम का ओवरलोड नहीं है, और वह जान-बूझ कर है। पाइपलाइन के बाकी हर कदम (अरेय़, बफ़र, स्ट्रीम्स) बाइट्स लेते हैं, और कोई String लेने वाला मेटोड आपके लिए एक कैरेक्टर सेट चुनने पर मजबूर होता, जो बस वही फैसला है जो JDK लेने से इनकार कर देता है। मौजूद एकमात्र String-टाइप्ड मेटोड, encodeToString, आउटपुट पक्ष पर है, जहाँ कैरेक्टर सेट का सवाल ही नहीं: Base64 आउटपुट साफ़ ASCII है। API की पूरी शक्ल "अपने बाइट्स जान-बूझ कर तय कीजिए" का एक छोटा तर्क है।
पैडिंग, रैपिंग और MIME का डायल
Java के एन्कोडर डिफ़ॉल्ट में आपके लिए दो फ़ॉर्मैटिंग फैसले ले लेते हैं, और दोनों समझने लायक हैं क्योंकि दोनों डायल हैं जिन्हें आप घुमा सकते हैं। पहला पैडिंग है: हर एन्कोडर = चर जोड़ता है जो आउटपुट को चार का गुणज बनाते हैं, जैसे RFC 4648 माँगता है: इम्प्लीमेंटेशन को एन्कोडेड डेटा के अंत में उचित पैड चर शामिल करना होगा, जब तक कि रिफ़रेंस स्पेसिफ़िकेशन कुछ और न कहे। दूसरा लाइन रैपिंग है: सिर्फ़ MIME एन्कोडर रैप करता है, 76 चरों पर, कारियर रिटर्न और लाइन फीड के साथ, और वह आख़िरी आंशिक लाइन के बाद कोई लाइन सेपरेटर नहीं जोड़ता - एक बारीकी जो javadoc खुद ख़ास तौर पर बताता है और दूसरे टूल ग़लत करते हैं:
| एन्कोडर | आउटपुट को पैड करता है | लाइनें रैप करता है | लाइन सेपरेटर |
|---|---|---|---|
getEncoder() |
हाँ | नहीं | n/a |
getUrlEncoder() |
हाँ | नहीं | n/a |
getMimeEncoder() |
हाँ | हाँ, 76 चर | CRLF |
getMimeEncoder(64, "\n") |
हाँ | हाँ, 64 चर | LF |
MIME का डायल उन लोगों के लिए API का सबसे उपयोगी हिस्सा है जो दूसरों के फ़ॉर्मैट वारिस करते हैं। स्टैंडर्ड कन्स्ट्रक्टर getMimeEncoder() है (76, CRLF, सीधा RFC 2045 से); दो-एरगुमेंट वाला वर्ज़न, getMimeEncoder(int lineLength, byte[] lineSeparator), आपको दूसरे रिवाज़ दोहराने देता है। दो अजीबियाँ जान लेनी हैं: लाइन लेंथ "चार के निकटतम गुणज तक नीचे राउंड" हो जाती है, इसलिए 77 माँगने पर चुपचाप 76 मिलता है, और राउंड वाला वैल्यू न पॉज़िटिव हो तो रैपिंग ही नहीं होती। और सेपरेटर में Base64 वर्णमाला का कोई चर नहीं हो सकता, वरना कन्स्ट्रक्टर वहीं पर IllegalArgumentException थ्रो कर देता है, क्योंकि एक ऐसा सेपरेटर जो डेटा से उलझा हो सकता है, वह बस होने का इंतज़ार करता हुआ बग है। यहाँ डायल है काम पर, MIME-स्टैंडर्ड और PEM-फ़्लेवर वाला:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class WrapDials {
public static void main(String[] args) {
byte[] data = "Hello, wrapped world! This line keeps going and going and going until it finally has to wrap.".getBytes(StandardCharsets.UTF_8);
Base64.Encoder mime = Base64.getMimeEncoder();
Base64.Encoder pem = Base64.getMimeEncoder(64, "\n".getBytes(StandardCharsets.ISO_8859_1));
System.out.println(mime.encodeToString(data));
// 76-चर लाइनें, उनके बीच CRLF
System.out.println(pem.encodeToString(data));
// 64-चर लाइनें, उनके बीच सादा LF
}
}
दो प्रैक्टिकल नोट्स। अगर आपका उपभोक्ता रैप्ड स्ट्रिंग को लाइन ब्रेक के साथ ख़त्म होने की उम्मीद से देखता है (कुछ ईमेल टूलिंग वही करती है), तो एन्कोड के बाद वह खुद जोड़ लीजिए: JDK जान-बूझ कर आख़िरी आंशिक लाइन के बाद रुक जाता है। और अगर आप वह डेटा बना रहे हैं जो URL या टोकन में बसेगा, तो रैपिंग बिल्कुल ग़लत डायल है; उन उपभोक्ता को एक लंबी लाइन चाहिए और आमतौर पर पैडिंग नहीं, जो अगले सेक्शन की बात है।
base64url और नो-पैडिंग वाला डायल
स्टैंडर्ड Base64 अपनी वर्णमाला के अंत में + और / रखता है, और वही दो चर हैं जो URL में सही नहीं बैठते: क्वरी स्ट्रिंग में + तो सर्वर तक पहुँचने से पहले ही स्पेस बन चुका होता है, / पाथ सेपरेटर है, और लटका हुआ = तीन-चर के राक्षस में परसेंट-एन्कोडिंग की तलाश करता है। RFC 4648 सेक्शन 5 हल खींचता है: URL- और फ़ाइल-नाम-सेफ़ वर्णमाला, जहाँ + बनता है -, / बनता है _, और आख़िर वाला = पैडिंग आमतौर पर तब गिरा दिया जाता है जब लेंथ ज़मीन से ही पता हो जाए। RFC नाम के लिए कठोर है: इस एन्कोडिंग को "base64 एन्कोडिंग से एक जैसा नहीं माना जाना चाहिए", और जो नाम आप सुनेंगे वह है base64url। JSON Web Tokens, OAuth state पैरामीटर, API सेशन IDs, और ग्यारह-चर वाले वीडियो IDs सब इसी बोली में बसे हैं।
Java आपको getUrlEncoder() से वर्णमाला देता है, पर यहाँ वह डायल है जो लोगों को पकड़ता है: URL-safe एन्कोडर डिफ़ॉल्ट में पैड करता है, और टोकन स्टैंडर्ड को पैडिंग नहीं चाहिए। RFC 7515 स्पष्ट है कि JWS हिस्से base64url का इस्तेमाल "सभी आख़िरी '=' चरों को छोड़कर ... और किसी भी लाइन ब्रेक, व्हाइटस्पेस, या बाकी अतिरिक्त चरों के बिना" करते हैं। इसलिए कैनोनिकल Java JWT रेसिपी एक दो-मेटोड की चेन है:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class TokenParts {
public static void main(String[] args) {
Base64.Encoder url = Base64.getUrlEncoder().withoutPadding();
byte[] header = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}".getBytes(StandardCharsets.UTF_8);
byte[] payload = "{\"sub\":\"1234567890\",\"name\":\"John Doe\"}".getBytes(StandardCharsets.UTF_8);
System.out.println(url.encodeToString(header));
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
System.out.println(url.encodeToString(payload));
// eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0
}
}
withoutPadding() कॉल एक नया एन्कोडर इंस्टेंस वापस करता है जो बिल्कुल वैसे ही व्यवहार करता है सिवाय इस बात के कि वह आख़िरी पैड छोड़ देता है; मूल इंस्टेंस छूए बिना, और javadoc बिल्कुल यही कहता है। डिकोडर तरफ़ पैडेड और बिना-पैडिंग दोनों इनपुट स्वीकार करता है, इसलिए बिना पैडिंग बनाई गई वैल्यू सख़्त डिकोडर से भी पढ़ी जा सकती है, इसीलिए जो भी चीज़ API बाउंड्री पार करे, उसे बिना पैडिंग ही सुरक्षित पसंद है। अब, एक बड़ा डिस्क्लेमर: ऊपर के दो हिस्से JWT की unsigned आधी हैं। असली टोकन को "header.payload" पर गणना की गई सिग्नेचर की ज़रूरत है, और वह क्रिप्टोग्राफी है, एन्कोडिंग नहीं। प्रोडक्शन के लिए JOSE लाइब्रेरी से टोकन मिंट और वेरिफ़ाई कीजिए: JJWT (0.13.0) या nimbus-jose-jwt (10.9.1)। JJWT का API आर्टिफैक्ट, उदाहरण के लिए, एक कोऑर्डिनेट दूर है:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.13.0</version>
</dependency>
<!-- runtime पर प्रोजेक्ट डॉक्स के मुताबिक jjwt-impl और jjwt-jackson जोड़ें -->
YouTube IDs इस डायल का दूसरा चेहरा है: ग्यारह चरों का base64url बिना पैडिंग के, एक पहचान-चिह्न जो URL जहाँ-जाने भी पेस्ट होने पर जीवित रहना चाहिए। अगर आपका सिस्टम पहचान-चिह्न बनाता है जो URLs में सफ़र करते हैं, तो ऊपर की withoutPadding() चेन ही वह शक्ल है जो कॉपी कीजिए।
फ़ाइलों को एन्कोड करना
रोज़-रोज़ की फ़ाइल नौकरी डिकोडर की पसंदीदा का दर्पण है: फ़ाइल पढ़ो, उसे एन्कोड करो, टेक्स्ट बाहर लिखो। java.nio.file के साथ चार लाइनें:
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class EncodeFile {
public static void main(String[] args) throws Exception {
byte[] raw = Files.readAllBytes(Paths.get("report.pdf"));
String packed = Base64.getEncoder().encodeToString(raw);
Files.write(Paths.get("report.pdf.b64"), packed.getBytes(StandardCharsets.ISO_8859_1));
System.out.println(raw.length + " -> " + packed.length());
}
}
वह आख़िरी प्रिंट वह 33 फ़ीसदी की बिल है, जो दिखने लगी है। 1 MB की फ़ाइल करीब 1.33 MB टेक्स्ट बन जाती है (मूल का 4/3, साथ में ज़्यादा से ज़्यादा दो पैड चर), और अगर आपने उसे MIME-शैली में रैप किया तो लाइन ब्रेक्स कुछ फ़ीसदी और जोड़ देते हैं: पुरानी मेल-युग की गणना, अभी भी सही, है 4/3 गुणा 78/76, यानी रैप्ड MIME पेलोड के लिए मूल से करीब 1.37 गुना। दो नतीजे। पहला, किसी भी स्टोरेज या मैसेज फ़ील्ड का साइज़ एन्कोडेड लेंथ से तय कीजिए, रॉ लेंथ से नहीं: VARCHAR(255) वाला कॉलम जो 192-बाइट रॉ वैल्यू को खुशी-खुशी संभालता है, उसकी 256-चर एन्कोडिंग को ठुकरा देगा। दूसरा, एन्कोडिंग की दिशा ही वह है जो मेमोरी को बदतर करती है, इसलिए बड़ी फ़ाइलों के लिए अरेय़ वाला वर्ज़न ग़लत टूल है और स्ट्रीमिंग सेक्शन सही है। फ़ाइल वालों के लिए एक छोटी खुशी: क्योंकि पहले आउटपुट चर पहले इनपुट बाइट्स की प्योर फ़ंक्शन हैं, हर Base64-एन्कोडेड PNG iVBORw0K से शुरू होती है और हर एन्कोडेड GIF R0lGOD से; एक बाइट डिकोड होने से पहले ही आप फ़ाइल की जात पहचान सकते हैं।
JSON, APIs और डेटा URIs
दो सबसे आम जगहें जहाँ एन्कोडेड आउटपुट वायर पर बसा होता है।
एक: JSON के अंदर बायनेरी। फ़ाइल अपलोड एंडपॉइंट्स, कंटेंट APIs, सिक्रेट स्टोर और वेबहुक्स JSON के अंदर बायनेरी को Base64 टेक्स्ट की शक्ल में एम्बेड करते हैं, क्योंकि रॉ बाइट्स JSON स्ट्रिंग एस्केपिंग को तोड़ देते। एन्कोडर साइड बाउंड्री पर एक लाइनर है, और एकमात्र फैसला यह है कि स्पेक कौन-सी बोली माँगती है:
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class JsonField {
public static void main(String[] args) throws Exception {
byte[] image = Files.readAllBytes(Paths.get("logo.png"));
// स्पेक कहता है base64url, बिना पैडिंग के:
String field = Base64.getUrlEncoder().withoutPadding().encodeToString(image);
// "field" को अपने JSON लाइब्रेरी को सादी स्ट्रिंग वैल्यू की शक्ल में सौंपें।
System.out.println(field.length());
}
}
फँसाव एन्कोडिंग नहीं है; स्पेक पढ़ना है। कुछ APIs को पैडेड स्टैंडर्ड Base64 चाहिए, कुछ को बिना पैडिंग का base64url, और कुछ दोनों पर लचिले हैं। जब स्पेक चुप हो, तो सबसे सस्ता ठीक है दूसरी तरफ़ का एक उदाहरण वैल्यू देखना: कहीं भी - या _ मिल जाए तो वर्णमाला तय हो जाती है, और आख़िर में = पैडिंग तय करता है। ग़लत बोली चुनना आम तौर पर दूसरी तरफ़ को क्रैश नहीं कराता; वह आमतौर पर फ़ाइल को कोरप्ट कर देता है, और वह बग खोजने की सबसे धीमी जात है।
दो: डेटा URIs। वह data:image/png;base64,... स्ट्रिंग जो इमेज को HTML या CSS में इनलाइन करती है, RFC 2397 का data URI है: data:, वैकल्पिक मीडिया टाइप, वैकल्पिक ;base64 फ़्लैग, एक कॉमा, और फिर डेटा। एक बनाना स्ट्रिंग कॉन्काटेनेशन है, और एकमात्र फैसला यह कि फ़्लैग है या नहीं (फ़्लैग न हो तो पेलोड परसेंट-एन्कोडेड टेक्स्ट है, जो बायनेरी के लिए किसी को नहीं चाहिए):
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class DataUriBuild {
public static void main(String[] args) throws Exception {
byte[] icon = Files.readAllBytes(Paths.get("icon.png"));
String b64 = Base64.getEncoder().encodeToString(icon);
String uri = "data:image/png;base64," + b64;
System.out.println(uri.substring(0, Math.min(40, uri.length())) + "...");
// data:image/png;base64,iVBORw0KGgo...
}
}
RFC की अपनी सलाह रसदार तरीके से लागू होती है: data URIs छोटी वैल्यूज़ के लिए हैं। 50 KB आइकॉन इनलाइन करना सामान्य ट्रेड है (एक रिक्वेस्ट कम); 5 MB फोटो इनलाइन करना कन्वीनिएन्स के कॉस्ट्यूम में बसा परफ़ॉर्मन्स बग है। फ़्लैग रखिए, मीडिया टाइप सच रखिए, और बाइट्स छोटे रखिए।
Basic Auth हेडर बनाना
वेब का सबसे पुराना ऑथेंटिकेशन हेडर Java में अभी भी सबसे आसान Base64 यूज़ केस है, क्योंकि यह बिल्कुल एक एन्कोड कॉल है। RFC 7617 के मुताबिक, Basic रिक्वेस्ट Authorization: Basic भेजता है और उसके बाद username:password का Base64 एन्कोडिंग; RFC का अपना उदाहरण, QWxhZGRpbjpvcGVuIHNlc2FtZQ==, है "Aladdin:open sesame" बदले हुए शक्ल में। क्लाइंट साइड पर हेडर बनाना Base64 की दो लाइनें और एक मॉडर्न HTTP कॉल है:
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class BasicAuthClient {
public static void main(String[] args) throws Exception {
byte[] credentials = ("alice:secret123").getBytes(StandardCharsets.UTF_8);
String header = "Basic " + Base64.getEncoder().encodeToString(credentials);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://example.com/api/status"))
.header("Authorization", header)
.GET()
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.statusCode());
}
}
इस हेडर के साथ तीन सावधानियाँ चलती हैं। पहली, RFC स्पष्ट है कि Basic एन्कोडिंग है, रक्षा नहीं: क्रेडेंशियल्स को पैकेट देखने वाला कोई भी पढ़ सकता है, इसलिए यह हेडर बस अपने नीचे के HTTPS के बराबर मज़बूत है, और TLS के अलावा कुछ भी पर यह बुरा विचार है। दूसरी, कैरेक्टर सेट: RFC US-ASCII क्रेडेंशियल्स की उम्मीद करता है (बाकी सब के लिए UTF-8, और charset auth-पैरामीटर सलाहमंद है), इसलिए StandardCharsets.UTF_8 चुनिए और दोनों सिरों पर एक जैसी ही रहिए। तीसरी, एक वर्ज़न नोट: java.net.http क्लाइंट Java 11 से है; पुरानी JVM पर वही हेडर HttpURLConnection पर एक setRequestProperty कॉल के साथ जाता है, और Base64 वाली लाइन दोनों में एक जैसी ही है। इसी हेडर की सर्वर तरफ़, पार्सिंग और डिकोडिंग sister गाइड का उदाहरण है, पहले-कॉलन-पर-बंटने और कॉन्स्टेंट-टाइम तुलना के साथ। दो सिरों को एक ही API के दो कॉल मिलते हैं, और यह ही इसका चुपचाप रौनक है।
कॉन्फ़िग्स, एनवायरनमेंट वेरिएबल और कॉलम में वैल्यूज़
Base64 एक टेक्स्ट कंटेनर है, इसलिए वह जगहों पर दिखता है जहाँ आपकी उम्मीद ही नहीं: env फ़ाइल में सेमीकॉलन वाला डेटाबेस DSN, properties फ़ाइल में कोट्स वाला पासवर्ड, कॉन्फ़िग मैप में मल्टी-लाइन सर्टिफ़िकेट, TEXT कॉलम में बायनेरी ब्लॉब इसलिए क्योंकि स्कीमा तब से पहले की है जब कोई BLOB के बारे में सोचता भी नहीं था। एन्कोडिंग साइड एक कॉल की नौकरी है, और ईमानदार चौखटा यह है कि वह क्या है: फ़ॉर्मैट-सुरक्षा की ट्रिक, रहस्यता की ट्रिक नहीं:
import java.nio.charset.StandardCharsets;
import java.util.Base64;
public class ConfigEncode {
public static void main(String[] args) {
String dsn = "pg:host=db;password=qu\"ote";
byte[] raw = dsn.getBytes(StandardCharsets.UTF_8);
String packed = Base64.getEncoder().encodeToString(raw);
System.out.println(packed);
// cGc6aG9zdD1kYjtwYXNzd29yZD1xdSJvdGU=
System.out.println("DB_DSN_B64=" + packed);
}
}
दो नियम इसे ईमानदार रखते हैं। पहला, कभी सिक्रेट को Base64 में रखकर उसे एन्क्रिप्टेड मत कहिए: Base64 कोई एन्ट्रॉपी नहीं जोड़ता और कोई जानकारी नहीं हटाता, जैसे ही कोई डेवलपर फ़ाइल पढ़ता है वह एक कॉल में वैल्यू डिकोड कर सकता है, और RFC का सुरक्षा सेक्शन बस इसी फेलियर की तरफ़ इशारा करता है, जहाँ लोग "एन्कोडेड" प्रोटोकॉल एक्सचेंज पेस्ट करके क्रेडेंशियल्स उगल बैठते हैं। अगर वैल्यू सिक्रेट है, तो पहले एन्क्रिप्ट कीजिए, और सिर्फ़ तब सिफ़रटेक्स्ट को Base64 में पैक कीजिए जब चैनल टेक्स्ट ही माँगता हो। दूसरा, साइज़ का बजट बनाइए: संरक्षित वैल्यू मूल से करीब एक-तिहाई बड़ी होती है, और वह कॉलम या फ़ील्ड जो रॉ वैल्यू संभाल लेता था एन्कोडेड वैल्यू को नहीं संभालेगा। और जब वैल्यू वापस आए, तो उसे बाउंड्री पर डिकोड कीजिए और बाइट्स के रूप में रखिए (बायनेरी के लिए) या एक्सप्लिसिट-कैरेक्टर-सेट स्ट्रिंग के रूप में (टेक्स्ट के लिए); वह दिशा sister गाइड का इलाक़ा है।
बड़े डेटा के लिए स्ट्रीमिंग
एन्कोडिंग वह दिशा है जो मेमोरी को बदतर करती है, इसलिए यहाँ बड़ी-फ़ाइल की कहानी वर्किंग सेट छोटा रखने की है। फ़ाइलें सेक्शन के उदाहरण का अरेय़ वाला वर्ज़न तब तक काफ़ी है जब तक फ़ाइल मेमोरी में आराम से न फिट रहे; उससे आगे, स्ट्रीम एडैप्टर ही सही चाल है। wrap(OutputStream) एक आउटपुट स्ट्रीम वापस करता है जो लिखते-लिखते एन्कोड करती है, इसलिए मल्टी-गीगाबाइट की फ़ाइल कभी एक बाइट अरेय़ के रूप में नही रहनी पड़ती:
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
public class StreamEncode {
public static void main(String[] args) throws Exception {
OutputStream packed = Base64.getEncoder().wrap(Files.newOutputStream(Paths.get("bigfile.b64")));
InputStream raw = Files.newInputStream(Paths.get("bigfile.bin"));
byte[] buf = new byte[8192];
int n;
while ((n = raw.read(buf)) != -1) {
packed.write(buf, 0, n);
}
packed.close();
raw.close();
}
}
इस स्ट्रीम पर एक ऐसा व्यवहार है जो हाइलाइट के लायक़ है, क्योंकि javadoc खुद उसकी तरफ़ इशारा करता है: रैप्ड स्ट्रीम अंदर कुछ बचे हुए बाइट्स थाम सकती है, और सिफ़ारिश है कि "उपयोग के बाद वापस मिली आउटपुट स्ट्रीम को तुरंत बंद कर दें, जिसके दौरान वह सभी संभव बचे हुए बाइट्स को नीचे की आउटपुट स्ट्रीम में फ़्लश कर देगी"। अगर आप लिखना बंद करके बंद करने से पहले आउटपुट फ़ाइल पढ़ लें, तो आपके डेटा की पूंछ अभी भी एन्कोडर में बैठी है, और फ़ाइल ट्रंकटेड दिखती है। इसीलिए उदाहरण फ़ाइल पर कोई हाथ लगने से पहले packed बंद करता है, और प्रोडक्शन में आप दोनों स्ट्रीम्स को try-with-resources ब्लॉक में रखेंगे। आदत बना लीजिए: एन्कोडिंग स्ट्रीम पर, बंद करना एन्कोडिंग का हिस्सा है।
पुराने गार्ड से मुलाक़ात
वारिस किए गए कोडबेस java.util.Base64 से पहले के Base64 APIs से भरे पड़े हैं, और उन्हें पहचान लेना आपको "मेरा आउटपुट क्यों रैप हो रहा है" जैसी रहस्यमय बातों से बचाता है। चार वही हैं जो आप असल में मिलेंगे:
| API | कहाँ मिलेगा | क्या करें |
|---|---|---|
sun.misc.BASE64Encoder / BASE64Decoder |
pre-Java-8 कोड | java.util.Base64 पर माइग्रेट कीजिए; Java 9 में हटाया गया |
javax.xml.bind.DatatypeConverter |
XML-युग का कोड, पुराने वेब सर्विस | Java 11 में हटाया गया (JEP 320); माइग्रेट कीजिए |
org.apache.commons.codec.binary.Base64 |
ऐसा कोड जो pre-8 JVMs पर चलना ज़रूरी है | pre-8 सपोर्ट के लिए रखिए; वरना JDK वाली क्लास ही डिफ़ॉल्ट है |
com.google.common.io.BaseEncoding |
Guava-भारी और बिग डेटा स्टैक्स | बेफ़िक्र चलता है; JDK वाली क्लास के डिपेंडेंसी नहीं हैं |
नाटक sun.misc जोड़ी का है। वह आंतरिक, असपोर्टेड API थी (वही जाति जो उस दौर के JDK पर बे-फ़िक्र कंपाइल होती है और बिना किसी देप्रीकेशन वॉर्निंग के गायब हो जाती है), और उसके आउटपुट की अपनी आदतें थीं, जैसे एन्कोडेड टेक्स्ट को लाइन-रैप करना, जिससे "मेरे Base64 में लाइन ब्रेक्स हैं" जैसी बड़ी संख्या में बग्स जन्मते रहे। जब सितंबर 2017 में Java 9 शिप हुई, तो मॉड्यूल-सिस्टम की सफ़ाई ने उसे हटाया, और आधिकारिक माइग्रेशन गाइड शब्द नहीं चबाता: "खास तौर पर, sun.misc.BASE64Encoder और sun.misc.BASE64Decoder को हटा दिया गया। बजाय इसके, supported java.util.Base64 क्लास इस्तेमाल करें, जो JDK 8 में जोड़ी गई थी"। अगर आप jdeps चलाएँ उन कोड पर जो अभी भी पुरानी क्लास का रिफ़रेंस करते हैं, तो टूल डिपेंडेंसी को "JDK removed internal API" चिह्नित करता है, जो JDK की तरफ़ से ट्रैफ़िक कोन जितना निकटतम इशारा है। JAXB का DatatypeConverter लंबी पर मिलती-जुलती ज़िंदगी गुज़ारे, Java 9 के दौर में Java EE मॉड्यूलों के साथ देप्रीकेटेड, और JEP 320, "Java EE और CORBA मॉड्यूल हटाएँ", ने Java 11 में उसे बिल्कुल हटा दिया। दोनों माइग्रेशन मेकैनिकल हैं: पुरानी printBase64Binary और BASE64Encoder().encode कॉलें getEncoder().encodeToString पर एक-से-एक मैप हो जाती हैं, रैपिंग के फ़र्क़ को छोड़कर, और एक बार कोड java.util.Base64 पर आ जाए तो वह 8 से 26 तक हर JDK पर बिना किसी और ख़याल के चलता है।
सुरक्षा और स्पीड
सुरक्षा का सेक्शन छोटा है, क्योंकि एन्कोडर का काम डेटा पर कभी फेल नहीं हो सकता, पर वह ख़ाली नहीं है। Base64 एन्क्रिप्शन नहीं है, और स्टैंडर्ड खुद यही कहता है: Base एन्कोडिंग "देखने-समझने में आसानी से पहचाने जाने वाले तथ्यों, जैसे पासवर्ड, को दृश्य रूप से छिपा देती है, पर किसी गणनात्मक गोपनीयता नहीं देती", और वही सेक्शन नोट भी करता है कि इससे "सुरक्षा घटनाएँ हो चुकी हैं"। एन्कोडर की तरफ़ के प्रैक्टिकल निष्कर्ष: सीक्रेट को सुरक्षित बनाने के लिए एन्कोड न कीजिए (वह अब कम सुरक्षित है, क्योंकि वह ज़्यादा चैनलों में फिट होता है); अगर वैल्यू सीक्रेट है, तो पहले एन्क्रिप्ट कीजिए और सिफ़रटेक्स्ट को एन्कोड कीजिए; और मेल्लेबिलिटी की जोड़ी को याद रखिए, जहाँ रिसीवर डिकोड डेटा को बदले बिना एक वैध स्पेलिंग को दूसरी से बदल सकता है (अलग पैडिंग, ख़ाली बिट्स में कचरा)। यहाँ एक डिटर्मिनिस्टिक एन्कोडर काम आता है: java.util.Base64 बिल्कुल एक इनपुट के लिए बिल्कुल एक आउटपुट देता है, इसलिए अगर आपका ही सिस्टम एक वैल्यू को लिखता भी है और पढ़ता भी है, तो स्पेलिंग स्थिर है, और कैनोनिकल फ़ॉर्म जाँच की ज़रूरत बाहरी वैल्यूज़ को है, ट्रास्ट बाउंड्री पर मौजूद उन्हें।
स्पीड पर, एन्कोडर की तरफ़ की कहानी डिकोडर की जैसी ही है: आधुनिक JVM पर बिल्ट-इन इम्प्लीमेंटेशन इतना तेज़ है कि Base64 लगभग कभी बॉटलनेक नहीं बनेगा, और वह ही बेंचमार्क है जिसके ख़िलाफ़ बाकी पूरा इकोसिस्टम खुद को मापता है। sister गाइड में उल्लिखित वही 2025 का gRPC-java बेंचमार्क (issue 11857, JMH, JDK 17 और 21 पर) ने JDK एन्कोडर को Guava के मुक़ाबले लगभग 2.5 से 3.8 गुना थ्रूपुट पर रखा, सबसे बड़ा फ़ासला x86 पर। दो प्रैक्टिकल नोट्स: हॉट पाथ के लिए एक ही एन्कोडर इंस्टेंस साझा कीजिए (फ़ैक्टरी पहले से वही साझा इंस्टेंस वापस करती है) और अलॉकेशन छुड़ाने के लिए encode(byte[], byte[]) को पहले-से-साइज़ किए गए अरेय़ में लिखने दीजिए; बहुत बड़े डेटा के लिए, स्ट्रीमिंग सेक्शन वह मेमोरी की कहानी है, और रैपिंग का ख़र्च डिस्क के मुक़ाबले शोर है। Base64 में एकमात्र असली परफ़ॉर्मन्स टैक्स साइज़ ही है, और कोई इम्प्लीमेंटेशन, इस समेत, उसमें छूट नहीं दिला सकता।
फँसावों की चेकलिस्ट
सारे फँसाव एक जगह इकट्ठे, सब Java-खास:
- गायब कैरेक्टर सेट। बिना-एक्सप्लिसिट-कैरेक्टर-सेट
text.getBytes()प्लेटफ़ॉर्म डिफ़ॉल्ट इस्तेमाल करता है: JDK 18+ पर बस सदफ़रमी से सही, उससे पुराने पर ग़लत, और हर जगह सिद्धांत रूप में ग़लत।StandardCharsets.UTF_8पास कीजिए और स्पेक में कैरेक्टर सेट लिख दीजिए। - पैडेड JWT।
getUrlEncoder()डिफ़ॉल्ट रूप में पैड करता है, और टोकन को पैडिंग नहीं चाहिए।withoutPadding()कॉल रसेप का हिस्सा है, कोई ऑप्शनल एक्स्ट्रा नहीं; आख़िर में=वाला टोकन वह टोकन है जो कुछ वैलिडेटर्स ठुकराएँगे और कुछ ख़राब कर देंगे। - रैप्ड आउटपुट। MIME एन्कोडर 76 पर CRLF के साथ रैप करता है और कोई ट्रेलिंग लाइन ब्रेक नहीं जोड़ता। अगर उपभोक्ता को ट्रेलिंग ब्रेक चाहिए, तो जोड़ दीजिए; अगर ब्रेक ही नहीं चाहिए, तो MIME एन्कोडर का इस्तेमाल न कीजिए।
- डबल एन्कोड। पहले से Base64 वैल्यू को एन्कोड करने पर एक बिल्कुल वैध, बिल्कुल बेकार स्ट्रिंग बनती है। क्लासिक कारण: कोई फ़ील्ड API से पहले-से-एन्कोड होकर आती है और आपका कोड "मददगार" बनकर उसे दोबारा एन्कोड कर देता है। एन्कोड करने से पहले जाँच लीजिए।
- URL में प्लस साइन्स। स्टैंडर्ड Base64 आउटपुट में
+होता है, जो क्वरी स्ट्रिंग में Java तक पहुँचने से पहले ही स्पेस बन जाता है। अगर स्टैंडर्ड-वर्णमाला वाली वैल्यू को URL में सफ़र करना है, तो उसे परसेंट-एन्कोड कीजिए, या उसे शुरू से ही URL-safe वर्णमाला में बनाइए। - 33 फ़ीसदी की बिल। रॉ कॉलम में फिट होती वैल्यू एन्कोडेड कॉलम में नहीं फिट होगी। स्टोरेज, मैसेज फ़ील्ड्स और हेडर्स को
4 * ceil(n / 3)से साइज़ कीजिए, और याद रखिए कि रैप्ड MIME आउटपुट उसके ऊपर कुछ फ़ीसदी और लेता है। - बंद नहीं हुई स्ट्रीम। रैप्ड आउटपुट स्ट्रीम बंद होने तक बचे हुए बाइट्स पकड़े रखती है। बंद करने से पहले फ़ाइल पढ़ने पर आपको ट्रंकटेड एन्कोडिंग मिलेगी। हर बार try-with-resources।
- खुले में सीक्रेट्स। Base64 पैकिंग टेप है, ताला नहीं। कॉन्फ़िग फ़ाइल, लॉग, या एनवायरनमेंट वेरिएबल में एन्कोडेड क्रेडेंशियल्स पढ़े जा सकने वाले क्रेडेंशियल्स हैं। पहले एन्क्रिप्ट कीजिए, वरना एन्कोड ही न कीजिए।
- Android की दीवार। Android पर
java.util.Base64बस API स्तर 26 से मौजूद है; उससे नीचे फ़्रेमवर्क क्लास हैandroid.util.Base64अपने फ़्लैग कॉन्स्टेंट्स के साथ (NO_PADDING,URL_SAFE, औरNO_WRAP)। बिना जाँचे एक को हार्ड-कोड करना बस वही उन डेवाइस पर टूटता है जिन्हें आपने कभी टेस्ट नहीं किया। - लाइन लेंथ का क्यूज़िक।
getMimeEncoder(77, ...)चुपचाप 76 पर रैप करता है, क्योंकि लेंथ को चार-के-गुणज तक नीचे राउंड कर दिया जाता है, और 3 या उससे कम माँगने से रैपिंग बिल्कुल बंद हो जाती है। अगर आपके फ़ॉर्मैट को 4 का गुणज न होने वाली लाइन लेंथ चाहिए, तो MIME डायल वह टूल नहीं है।
sun.misc से स्टैंडर्ड लाइब्रेरी तक
Java की कहानी एक छोटी है, साफ़ पहले और बाद के साथ। 2014 से पहले, अगर JDK के अंदर Base64 चाहिए था तो आपको वह आंतरिक जोड़ी मिलती थी sun.misc.BASE64Encoder और sun.misc.BASE64Decoder - पहला दिन से असपोर्टेड, अपनी 76-चर रैपिंग आदतों के साथ - या XML कोड में javax.xml.bind.DatatypeConverter की तरफ़ बढ़ते, या आपने बिल्ड में Apache Commons Codec या Guava जोड़ी, और इसी तरह बड़ी संख्या में एंटरप्राइज़ कोडबेस के पास तीन Base64 इम्प्लीमेंटेशन हो गए और यह पता ही नहीं कि कौन-सा कौन है। 18 मार्च 2014 को Java 8 ने java.util.Base64 शिप किया: एक क्लास, तीन वर्णमालाएँ, RFC 4648 और RFC 2045 के नियम सही ढंग से इम्प्लीमेंट किए, फ़ैक्टरी पैटर्न, पैडिंग और रैपिंग के डायल्स, और दोनों दिशाओं में स्ट्रीम एडैप्टर्स। वह Base64 था जो भाषा को शुरू से ही रखना चाहिए था, और javadoc तब से Since: 1.8 ही कहता रहा है।
सफ़ाई दो लहरों में आई। Java 9 (21 सितंबर 2017) ने sun.misc जोड़ी को मॉड्यूल-सिस्टम सफ़ाई का हिस्सा बनाकर हटाया, माइग्रेशन गाइड हर डेवलपर को JDK 8 वाली क्लास की तरफ़ इशारा करते हुए, और Java 11 ने JAXB मॉड्यूल और उसके DatatypeConverter को भी साथ-साथ हटा दिया (JEP 320)। Java 18 (22 मार्च 2022) में JEP 400, "डिफ़ॉल्ट रूप में UTF-8", हुआ, जिसने Base64 को बिल्कुल हाथ नहीं लगाया पर उसने जिन लापरवाह getBytes() कॉलों का फेल मोड बदल दिया जिनसे वह फीड होती है: हर OS पर प्लेटफ़ॉर्म डिफ़ॉल्ट कैरेक्टर सेट UTF-8 बन गया, इसलिए पुराने मोजिबैक पैटर्न नई JVMs पर बस दोहराना बंद कर दिया। 1.8 से सार्वजनिक API का एक भी मेटोड नहीं बदला। जो बदला वह नीचे का इंजन है: बग फिक्स और परफ़ॉर्मन्स का काम, इसीलिए कम्युनिटी बेंचमार्क्स बार-बार वही निष्कर्ष देते रहे कि स्टैंडर्ड लाइब्रेरी वाला वर्ज़न उसी लेगसी लाइब्रेरी को पछाड़ रहा है जिसे वह बदल रहा है। आज, 8 से 26 तक किसी भी JDK पर, "Java में इसे Base64 कैसे करें" का जवाब एक इम्पोर्ट और एक फ़ैक्टरी कॉल है, और वह एक दशक से ज़्यादा से वही है।
कुछ नर्ड डिलीट्स
क्योंकि एक हैंडबुक मुस्कान पर ख़त्म होना चाहिए, यहाँ कुछ Java-खास तथ्य हैं जो बस मज़े के हैं:
- javadoc कहता है
Since: 1.8, और यह बारह साल से सच रहा है। एक भी मेटोड नहीं जुड़ा, एक भी नहीं हटा, एक भी व्यवहार नहीं बदला: भाषा के सबसे लंबे समय तक जमे हुए API सरफ़ेस में से एक, और आप इसे बिना सोचे इस्तेमाल करते हैं। encodeToStringअपने रिज़ल्ट String को ISO-8859-1 कैरेक्टर सेट से बनाता है, javadoc के मुताबिक। असल में यह एक बिल्कुल ज़रूरी-नहीं बात है, क्योंकि Base64 आउटपुट प्योर ASCII है और Latin-1, UTF-8, और कैरेक्टर-सेट जूल के ज़्यादातर बाकी हिस्सों में एक जैसा दिखता है, पर javadoc आपको बस बताता है, जो JDK का ही स्वभाव है।- MIME एन्कोडर आख़िरी आंशिक लाइन के बाद कोई लाइन सेपरेटर नहीं जोड़ता। दूसरे टूल्स, जिनमें कुछ बहुत मशहूर ईमेल लाइब्रेरी शामिल हैं, रैप्ड आउटपुट को एक ट्रेलिंग CRLF पर ख़त्म करते हैं। अगर आपका रिफ़रेंस इम्प्लीमेंटेशन के ख़िलाफ़ डिफ़ आख़िर में बस दो चरों का है, तो आप इस क्यूज़िक से मिल गए।
getMimeEncoderसे 77-चर लाइनें माँगिए और वह आपको 76 देगा: लाइन लेंथ को सबसे नज़दीकी चार-के-गुणज तक नीचे राउंड कर दिया जाता है, चुपचाप, क्योंकि एक रैप जो चार-चर ग्रुप को तोड़े वह कचरा बना देगा। API आपकी इज़न माँगने की बजाय एक टूटी लाइन बनाने से इनकार कर देता है।Base64.getEncoder() == Base64.getEncoder()सच है। फ़ैक्टरी मेटोड्स हर कॉल पर वही साझा इंस्टेंस वापस करते हैं, इसलिए "नया लीजिए" वाला API सिर्फ़ singleton का कॉस्ट्यूम है, और थ्रेड-सेफ्टी का वादा बस उस चीज़ का वर्णन है जो JVM पहले से कर ही रहा है।- Android पर, ट्विन API
android.util.Base64वही फैसले फ़्लैग्स के रूप में पेश करता है:NO_PADDING,URL_SAFE,NO_WRAP। दो APIs, एक डेसिज़न टेबल, जो इस बात का चुपचाप साक्ष्य है कि Base64 डिज़ाइन अब कितना ठोस हो चुका है। - RFC 4648 का URL-safe सेक्शन वही जगह है जहाँ नाम "base64url" जन्मा: स्पेक कहता है कि एन्कोडिंग को "base64url कहा जा सकता है" और चेतावनी देता है कि इसे "base64 एन्कोडिंग से एक जैसा नहीं माना जाना चाहिए"। URL-safe वर्णमाला की उत्पत्ति के फ़ुटनोट में 2001 के P2P-hackers मेलिंग लिस्ट पोस्ट की तरफ़ इशारा है, इसलिए जो नाम आप हर URL में पेस्ट करते हैं, उसकी परवरिश मेलिंग लिस्ट में हुई है।
- शब्द
base64को एन्कोड कीजिए और आपकोYmFzZTY0मिलेगा, पैडिंग के बिना, क्योंकि छह, तीन का गुणज है। फ़ॉर्मैट खुद का वर्णन करता हुआ, Morse में बोलने वाले आईने का तकनीकी बराबरी है, और यह वही आईने की अपनी परछाई है। - Java-8 से पहले के कोड पर
jdeps -jdkinternalsचलाइए और देखिए वहsun.misc.BASE64Encoderको "JDK removed internal API" के रूप में चिह्नित करता है। आधिकारिक माइग्रेशन गाइड में टूल का उदाहरण एक Base64 क्लास है, जो JDK का आपकी इम्पोर्ट्स की तरफ़ उंगली उठाकर कहना है "हमने इस बारे में बात कर रखी थी"। - 1.37 का गुणांक। हर रैप्ड MIME पेलोड अपने मूल साइज़ का लगभग 1.37 गुना खर्च करता है (वर्णमाला के लिए 4/3, CRLF रिदम के लिए 78/76), इतना स्थिर अनुपात कि पुरानी ईमेल गणना आज भी उसे उद्धृत करती है: 1990 की मेल इंफ्रास्ट्रक्चर ने हर अटैचमेंट पर जो टोल लगाया, वही बिल
getMimeEncoder()आज लगाता है।
दूसरी दिशा की तरफ़
यह कहानी का एन्कोडर वाला हिस्सा था, और वह दोनों में से शांत वाला है: काम कभी डेटा पर फेल नहीं होता, फँसाव आपके फैसलों (कैरेक्टर सेट, पैडिंग, रैपिंग, बोली) के बारे में हैं, दूसरों की अचानक-मुश्किलों के बारे में नहीं, और पूरा API एक इम्पोर्ट में फिट है। दूसरी दिशा वह जगह है जहाँ Base64 सुविधाजनक बनना बंद करके विरोधी बनना शुरू करता है, क्योंकि डिकोडिंग वही जगह है जहाँ आप दूसरों की पैडिंग की पसंद, उनके लाइन ब्रेक्स, उनके कैरेक्टर सेट्स, उनकी रस्सी से मिलते हैं, और आप और सच के बीच IllegalArgumentException खड़ा रहता है। Java में Base64 डिकोडिंग, जो इसी पेज से लिंक है, डिकोडर को उसी गहराई से कवर करता है: तीन डिकोडर शख्सियतें, बिल्कुल सटीक एरर मैसेज, पैडिंग के नियम, base64url और JWTs, MIME और PEM, और Java-खास गड्ढे एक जगह इकट्ठे। दोनों को एक जोड़े की तरह पढ़िए और पूरा विषय आपका है।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: Java में Base64 डिकोडिंग: एक सम्पूर्ण गाइड