Kotlin में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड
हर Base64 बातचीत का आधा हिस्सा पैक डेटा को वापस बाइट्स में पढ़ने के बारे में है। दूसरा आधा हिस्सा, जिसके लिए आप बिल्कुल सही साइट पर हैं, उस पैक डेटा को सबसे पहले बनाने के बारे में है। आपके एप्लिकेशन में कहीं न कहीं बाइट्स हैं जिनको ऐसे चैनल से सफ़र करना है जो सिर्फ़ टेक्स्ट समझता है: JSON स्ट्रिंग, HTTP हेडर, ईमेल, URL, कॉन्फ़िग फ़ाइल। Base64 क्लासिक जवाब है, और Kotlin की स्टैंडर्ड लाइब्रेरी में इसके लिए first-class जवाब है: kotlin.io.encoding में Base64 क्लास, Kotlin 2.2 से स्टेबल।
यह गाइड इस बात पर चलती है कि जब आप Base64 बनाने वाले हैं तो आपको असल में क्या चाहिए: चार प्रीसेट स्कीम, पैडिंग डायल, वे लाइन-रैपिंग नियम जो ईमेल और सर्टिफ़िकेट्स के लिए ज़िंदगी का हिस्सा हैं, URL-सुरक्षित वर्णमाला, बाइट्स कहाँ से आते हैं, और असली दुनिया के परिदृश्यों का एक सेट, जिनमें से हर एक में Kotlin है। फ़ॉर्मैट ख़ुद - 3 बाइट्स कैसे 4 चर बनते हैं, = कहाँ से आता है - होम पेज पर कवर है, इसलिए यहाँ सीधे Kotlin पर आते हैं।
एक क्लास, चार प्रीसेट
पूरा API एक ही क्लास है, Base64, kotlin.io.encoding पैकेज में। कोई encoder ऑब्जेक्ट नहीं जिसे आप बनाएं, और कोई बिल्डर भी नहीं। इसके बजाय, क्लास चार प्रीसेट इंस्टेंस के साथ आती है, हर RFC स्कीम के लिए एक, और एक कॉम्पैनिऑन ऑब्जेक्ट जो चुपचाप सबसे आम वाले की जगह खड़ा रहता है:
| इंस्टेंस | वर्णमाला | एन्कोड पर लाइन रैपिंग | एन्कोड पर पैडिंग | इसका इस्तेमाल |
|---|---|---|---|---|
Base64.Default | + और / | नहीं | = निकालता है | सामान्य उपयोग, APIs, डेटा URLs |
Base64.UrlSafe | - और _ | नहीं | = निकालता है (बंद कर दीजिए) | URLs, टोकन, JWTs |
Base64.Mime | + और / | हर 76 चरों पर CRLF | = निकालता है | ईमेल बॉडी और अटैचमेंट्स |
Base64.Pem | + और / | हर 64 चरों पर CRLF | = निकालता है | सर्टिफ़िकेट्स और प्राइवेट कुंजियाँ |
वह नेमिंग बारीकी जो लोगों को पकड़ती है: ये इंस्टेंस हैं, फैक्ट्रियाँ नहीं। हर इंस्टेंस एक इम्मी्यूटेबल वैल्यू है, और उसके व्यवहार को बदलना, जैसे पैडिंग, पुराने को म्यूटेट करने के बजाय नया इंस्टेंस लौटता है। इससे प्रीसेट को थ्रेड्स में साझा करने और ऑब्जेक्ट्स में स्टोर करने के लिए सुरक्षित बना देता है, और इसीलिए पूरा क्लास एक साधारण वैल्यू टाइप रह सकता है जिसमें कोई आंतरिक स्टेट नहीं है।
आपकी पहली एन्कोडिंग: बाइट्स अंदर, स्ट्रिंग बाहर
यह साइट पर सबसे छोटा उपयोगी प्रोग्राम है: पाँच बाइट्स अंदर, आठ-चरों की स्ट्रिंग बाहर। इनपुट हमेशा ByteArray होता है (या उसका स्लाइस), और रिज़ल्ट एक साधारण String है जिसे आप जहाँ भी टेक्स्ट स्वीकार हो, वहीं रख सकते हैं:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val packed = Base64.encode(bytes)
println(packed) // SGVsbG8=
}
वह एक लाइन दिखने से ज़्यादा काम करती है। Kotlin आपको उसी ऑपरेशन के कई रूप देता है, और वे सब बस वैसा ही पढ़े जैसे फ़ंक्शन कहता है:
encode(bytes)Stringलौटता है, ऊपर वाली शक्ल।encodeToByteArray(bytes)ASCII चरों काByteArrayलौटता है, तब फ़ायदेमंद जब पैक्ड शक्ल खुद किसी और बफ़र में जा रही हो।encodeIntoByteArray(bytes, destination)उसीByteArrayमें लिखता है जो आप पहले से अलोकेट कर चुके हैं, जो हॉट पथ्स पर एक अलोकेशन छुड़ा देता है।encodeToAppendable(bytes, builder)किसी भी चीज़ में ऐपेंड करता है जोAppendableइम्प्लीमेंट करती है, जैसेStringBuilder, जो तब प्राकृतिक फिट है जब आप बड़ा दस्तावेज़ इकट्ठा कर रहे हों।
चारों को वही वैकल्पिक startIndex और endIndex रेंज मिलती है, इसलिए आप बड़े बफ़र का स्लाइस पहले कॉपी किए बिना पैक कर सकते हैं। चूँकि Base64.Default कॉम्पैनिऑन ऑब्जेक्ट है, आप इंस्टेंस गिराकर Base64.encode(bytes) भी sugar के रूप में लिख सकते हैं; दोनों रूप एक ही कॉल हैं।
4/3 नियम: यह कितना लंबा होगा?
एन्कोडर को शिप करने से पहले यह जानने लायक है कि आउटपुट बिल्कुल कितना बड़ा होगा, क्योंकि Base64 वही जानकारी चरों पर खर्च करती है जो उसके पास पहले से थी। गणित सख़्त है: हर 3 इनपुट बाइट्स बिल्कुल 4 आउटपुट चर बन जाते हैं, इसलिए बचे हुए 1 या 2 बाइट्स भी 4 के पूरे ग्रुप को खा लेते हैं, जिसे ग्रुप भरने के लिए = से पैड किया जाता है। पहले कुछ साइज़्स के लिए नतीजा:
| इनपुट बाइट्स | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| आउटपुट चर | 4 | 4 | 4 | 8 | 8 | 8 | 12 | 12 |
टेबल के पीछे का सूत्र है 4 * ceil(bytes / 3)। सबसे ख़राब हालत में एक ही बाइट 4 चर बन जाती है, 300 प्रतिशत का अतिरिक्त शुल्क; तीन बाइट्स से ऊपर यह वायर पर लगभग एक-तिहाई ज़्यादा डेटा तक पहुँचकर तय हो जाता है। यह पूरा कॉस्ट मॉडल है; इंस्टेंस-दर-इंस्टेंस कोई भिन्नता नहीं, और इसीलिए बड़े पेलोड को Base64 बनाने में आपको जान-बूझ कर चलना चाहिए, रिफ़्लेक्स से हाथ बढ़ाने के बजाय।
पैडिंग एक सेटिंग है, नियति नहीं
एन्कोड की तरफ़, = चर एक पॉलिसी तयारी हैं, और Kotlin इसे first-class बना देता है। हर इंस्टेंस में एक PaddingOption रहता है, और withPadding आपको डायल हिलाए एक नया इंस्टेंस सौंपता है। चारों प्रीसेट PRESENT से शुरू होते हैं, इसीलिए "Hello" SGVsbG8= की शक्ल में निकलता है, SGVsbG8 की नहीं:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello".encodeToByteArray()
val noPad = Base64.Default.withPadding(Base64.PaddingOption.ABSENT)
println(Base64.encode(bytes)) // SGVsbG8=
println(noPad.encode(bytes)) // SGVsbG8
}
डायल पर चार जगह हैं। नाम का पहला शब्द तय करता है कि एन्कोडर क्या निकालेगा; दूसरा आधा तय करता है कि जब आप (या दूसरी तरफ़) बाद में उसे उल्टा करोगे, तो उसी इंस्टेंस का डिकोडर कितना सख़्त होगा:
| PaddingOption | एन्कोडर = निकालता है | डिकोडर = स्वीकार करता है |
|---|---|---|
PRESENT | हाँ | ज़रूरी, बाकि सब फेल |
ABSENT | नहीं | मना, बेकार पैड फेल |
PRESENT_OPTIONAL | हाँ | दोनों तरह |
ABSENT_OPTIONAL | नहीं | दोनों तरह |
एन्कोडिंग के समय सबसे आम चुनाव UrlSafe वर्णमाला के साथ ABSENT है, जो बिल्कुल वह शक्ल है जो JSON Web Tokens और कई URL स्कीम उम्मीद करती हैं। आपसे वह थोड़ी ही देर में फिर मिलेगा।
Base64url: URLs, टोकन और JWTs
क्लासिक वर्णमाला में + और / होते हैं, और URL में दोनों बिलाव हैं: क्वरी स्ट्रिंग में + को आमतौर पर स्पेस की तरह पढ़ा जाता है, और / पाथ सेपरेटर है। RFC 4648 सेक्शन 5 URL-सुरक्षित वेरिएंट की परिभाषा देता है, जिसमें - और _ अदल-बदल दिए जाते हैं, और Base64.UrlSafe वही स्कीम है। वही बाइट्स जो क्लासिक वर्णमाला में / बनाते हैं, उनकी एन्कोडिंग से वह बदलाव काम करते हुए दिखता है:
import kotlin.io.encoding.Base64
fun main() {
val bytes = "Hello?".encodeToByteArray()
println(Base64.encode(bytes)) // SGVsbG8/
println(Base64.UrlSafe.encode(bytes)) // SGVsbG8_
}
इसका कैनोनिकल असली-दुनिया उपयोगकर्ता JWT है, जिसका हेडर और पेलोड बिंदुओं से जुड़ा base64url बिना पैडिंग है। यहाँ इसे बनाने की एन्कोडिंग वाली आधी है, जो शक्ल आपको तब भी समझनी चाहिए जब अंतिम टोकन साइन करने का काम लाइब्रेरी करे:
import kotlin.io.encoding.Base64
fun main() {
val header = """{"alg":"HS256","typ":"JWT"}"""
val payload = """{"sub":"1234567890","name":"John Doe"}"""
val noPad = Base64.UrlSafe.withPadding(Base64.PaddingOption.ABSENT)
val h = noPad.encode(header.encodeToByteArray())
val p = noPad.encode(payload.encodeToByteArray())
val token = "$h.$p.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8"
println(token)
}
प्रिंट होता है:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.Ym9nVXNlZlNpZ25hdHVyZUZvckRlbW8
दो चेतावनियाँ यहीं लगती हैं। पहली: तीसरा सेगमेंट सिग्नेचर है, और असली सिग्नेचर बनाने के लिए असली क्रिप्टोग्राफी चाहिए (JCA/JCE साइनर या JWT लाइब्रेरी), कभी हाथ से बने बाइट्स नहीं; ऊपर वाला स्निपेट बस एन्कोडिंग शक्ल दिखा रहा है। दूसरी: अगर आप JVM पर हैं और आदत में java.util.Base64.getUrlEncoder() की तरफ़ हाथ बढ़ाते हैं, तो नोट कीजिए कि वह डिफ़ॉल्ट रूप से पैड करता है, इसलिए JWT-शक्ल के आउटपुट को वहाँ .withoutPadding() चाहिए; Kotlin प्रीसेट बॉक्स से निकलते ही उतनी ही ज़ोरों से पैड करता है, और उसमें आप withPadding कॉल के रास्ते बाहर निकलते हैं।
लाइन रैपिंग: Mime और Pem प्रीसेट
चारों प्रीसेट में से दो अपने आउटपुट को छोटी-छोटी लाइन्स में रैप करते हैं, और वजह ऐतिहासिक है। पुराने ईमेल ट्रांसपोर्ट लंबी लाइन्स खराब कर देते थे, इसलिए RFC 2045 सेक्शन 6.8 MIME base64 को 76 चर प्रति लाइन तक सीमित करता है; PKI टूल, पुरानी PEM परंपरा के साथ, 64 इस्तेमाल करते हैं। Kotlin दोनों नियम प्रीसेट के अंदर ही पका देता है: लाइन सेपरेटर CRLF है, ब्रेक बिल्कुल लिमिट पर लगता है, और अंत में कोई ट्रेलिंग सेपरेटर नहीं होता। 200-बाइट का पेलोड हर रैपर से गुज़रकर ऐसा दिखता है:
import kotlin.io.encoding.Base64
fun main() {
val data = ByteArray(200) { (it % 251).toByte() }
println(Base64.Mime.encode(data).lines().maxOf { it.length }) // 76
println(Base64.Pem.encode(data).lines().maxOf { it.length }) // 64
}
उस इनपुट के लिए Mime 4 लाइन्स बनाता है और Pem 5। पहली Mime लाइन यह है:
AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4
वह फँसाव जो याद रखना है, आपकी कोड लिखने वाली दिशा का उल्टा है: रैप्ड आउटपुट एक लाइन नहीं है। अगर आप Mime आउटपुट किसी सख़्त सिंगल-लाइन उपभोक्ता को खिलाना चाहें, तो CRLFs डिकोडिंग एरर बन जाएँगे, इसलिए रैपर चैनल से चुनिए, सुविधा से नहीं। APIs, डेटा URLs और कुछ भी मॉडर्न के लिए Default सही डिफ़ॉल्ट है, और रैपिंग ईमेल-ओर-सर्टिफ़िकेट की कहानी है।
बाइट्स कहाँ से आते हैं?
एन्कोडर उतना ही ईमानदार है जितने बाइट्स आप उसे सौंपते हैं, और दिलचस्प फैसले encode कॉल होने से एक कदम पहले होते हैं। सबसे आम सोर्स टेक्स्ट है, और सबसे आम ग़लती है कैरेक्टर सेट को चुपचाप फैसला करने देना:
text.encodeToByteArray()हर प्लेटफ़ॉर्म पर हमेशा UTF-8 होता है। JSON, ईमेल और वेब डेटा के लिए यह सही चुनाव है, और ग़लत अगर टेक्स्ट Latin-1 या UTF-16 हो और दूसरी तरफ़ उसी हिसाब से डिकोड करे।- JVM पर आप इनलाइन एक्सटेंशन
text.toByteArray(charset)से एक्सप्लिसिट चुन सकते हैं, जो Kotlin 1.0 से स्टैंडर्ड लाइब्रेरी में है और Java केgetBytes(charset)का Kotlin-तरफ़ का जवाब है।kotlin.StringपरgetBytesनहीं है, इसलिए अगर आप Kotlin स्ट्रिंग परtext.getBytes()लिखेंगे, तो कंपाइलर आपको बता देगा; एक्सटेंशन ही रास्ता है।
import kotlin.io.encoding.Base64
fun main() {
val text = "héllo"
println(Base64.encode(text.encodeToByteArray())) // aMOpbGxv
println(Base64.encode(text.toByteArray(Charsets.ISO_8859_1))) // aOlsbG8=
}
वही पाँच अक्षर, दो अलग पैक्ड शक्लें, क्योंकि किसी भी Base64 की बात शुरू होने से पहले बाइट्स अलग थे। अगर डिकोडर बाद में UTF-8 माने, तो Latin-1 वर्ज़न मोजिबेक में डिकोड हो जाएगा, और दोनों सिरों पर कोई भी Base64 ट्रिक कैरेक्टर सेट की मेल-न-खलती ठीक नहीं कर सकती।
बाइट्स के दूसरे सोर्स वही शक्ल फ़ॉलो करते हैं। फ़ाइल है file.readBytes() या path.readBytes() और फिर encode। प्री-अलोकेटेड बफ़र encodeIntoByteArray(bytes, destination) इस्तेमाल करता है। बनते-बनाते दस्तावेज़ encodeToAppendable(bytes, builder) इस्तेमाल करता है, जो डिस्टिनेशन लौटता है ताकि कॉल बिल्डर मेटोड्स की तरह चेन हों:
import kotlin.io.encoding.Base64
fun main() {
val sb = StringBuilder("prefix-")
Base64.encodeToAppendable("Hello".encodeToByteArray(), sb)
println(sb) // prefix-SGVsbG8=
}
और JVM पर इनपुट्स के लिए एक स्ट्रीमिंग रूप है जो मेमोरी में फिट नहीं होते, जो अभी भी एक्सपेरिमेंटल चिह्नित है और अपने नाम से इम्पोर्ट हो सकता है। नेमिंग में मोड़: encodingWith एक आउटपुट स्ट्रीम को रैप करता है, इसलिए उससे किए गए writes base64 की शक्ल में बाहर आते हैं, और base64 बाइट्स नीचे की स्ट्रीम में उतरते हैं:
import java.io.ByteArrayOutputStream
import kotlin.io.encoding.Base64
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.io.encoding.encodingWith
@OptIn(ExperimentalEncodingApi::class)
fun main() {
val raw = ByteArray(10_000) { (it % 251).toByte() }
val packed = ByteArrayOutputStream()
packed.encodingWith(Base64.Default).use { encoded ->
encoded.write(raw)
}
println(packed.size()) // 13336
}
अनुभव-नियम: जो भी फिट हो उसके लिए इन-मेमोरी encode, जो फिट न हों उनके लिए encodingWith, और हर बार एक्सप्लिसिट कैरेक्टर सेट जब बाइट्स असल में टेक्स्ट हों।
फ़ील्ड नोट्स: HTTP Basic Auth
HTTP Basic ऑथेंटिकेशन इंटरनेट पर सबसे पुराना Base64 use case है, और service-to-service ट्रैफ़िक में आज भी वह हर जगह है। RFC 7617 स्कीम तय करता है: यूज़र और पासवर्ड लीजिए, एक कॉलन से जोड़िए, रिज़ल्ट को base64 कीजिए, और Authorization हेडर में Basic के साथ एक स्पेस और पैक्ड स्ट्रिंग के रूप में भेजिए। Kotlin में:
import kotlin.io.encoding.Base64
fun main() {
val credentials = "alice:s3cr3t"
val header = "Basic " + Base64.encode(credentials.encodeToByteArray())
println(header) // Basic YWxpY2U6czNjcjN0
}
यहाँ Base64 क्यों, कुछ मज़बूत का बजाय? क्योंकि हेडर वैल्यू एक सिंगल प्रिंटेबल टोकन होनी चाहिए, और Base64 वही गारंटी देता है। ईमानदार चेतावनी: Base64 एन्कोडिंग है, एनक्रिप्शन नहीं। कोई भी क्लाइंट एक ही स्टेप में YWxpY2U6czNjcjN0 को उल्टा करके alice:s3cr3t तक पहुँच सकता है, इसीलिए Basic auth सिर्फ़ TLS कनेक्शन पर ही रखना चाहिए, आदर्श रूप से टोकन क्रेडेंशियल्स के साथ, इंसानों के पासवर्ड्स के बजाय। ऐसे हेडर को पार्स करते समय कॉलन पर बिल्कुल एक बार split कीजिए, क्योंकि पासवर्ड में कानूनी तौर पर कॉलन हो सकते हैं।
फ़ील्ड नोट्स: इमेजेस और डेटा URLs
डेटा URLs बायनेरी एसेट्स को सीधे HTML, CSS और JSON के अंदर इनबेड करते हैं ताकि ब्राउज़र दूसरा रिक्वेस्ट न बनाए। शक्ल है: मीडिया टाइप, एक कॉमा, base64 शब्द, दूसरा कॉमा, और पैक्ड बाइट्स:
import kotlin.io.encoding.Base64
fun main() {
val png = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47, 0x0D.toByte(), 0x0A.toByte(), 0x1A.toByte(), 0x0A.toByte())
val dataUrl = "data:image/png;base64," + Base64.encode(png)
println(dataUrl) // data:image/png;base64,iVBORw0KGgo=
}
ऊपर वाले बाइट्स किसी PNG फ़ाइल के पहले आठ हैं, वह मैजिक नंबर जो हर डिकोडर जाँचता है। Base64 क्यों फिट बैठता है: पेलोड को मार्कअप के अंदर URL-सुरक्षित टेक्स्ट टोकन होना चाहिए, और Base64 ही एकमात्र व्यापक रूप से सपोर्टेड बायनेरी-टू-टेक्स्ट है जिसकी ग्रामर स्थिर है। फँसाव साइज़ है। 300 किलोबाइट का लोगो लगभग 400 किलोबाइट का मार्कअप बन जाता है, और हर अतिरिक्त किलोबाइट का भुगतान हर पेज लोड पर होता है जिसमें वह शामिल हो। डेटा URLs आइकॉन्स, एवटार और छोटे sprites के लिए बढ़िया टूल हैं; वीडियो के लिए भयंकर, और बड़ी तस्वीर के लिए भी बस औसत। इनलाइन करने से पहले माप लीजिए।
फ़ील्ड नोट्स: ईमेल अटैचमेंट्स
SMTP एक टेक्स्ट प्रोटोकॉल है जो बायनेरी के किसी भी धारणा से पुराना है, इसलिए आपके पाए हर ईमेल का हर अटैचमेंट Base64 है, 76 चरों पर रैप्ड, और Content-Transfer-Encoding: base64 हेडर से घोषित। 5-बाइट %PDF- हेडर के लिए Kotlin-बनाया हुआ बॉडी स्लॉट भरा हुआ, एक छोटे बायनेरी अटैचमेंट वाला न्यूनतम MIME पार्ट ऐसा दिखता है:
From: sender@example.com
To: receiver@example.com
Subject: report
MIME-Version: 1.0
Content-Type: multipart/mixed; boundary="cut-here"
--cut-here
Content-Type: text/plain; charset="utf-8"
The quarterly report follows as an attachment.
--cut-here
Content-Type: application/pdf
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename="report.pdf"
JVBERi0=
--cut-here--
(बॉडी JVBERi0= पाँच बाइट्स %PDF- की base64 है; असली रिपोर्ट कई 76-चरों की लाइन्स पर रैप होती।) फ़ाइल के बाइट्स होने पर Kotlin तरफ़ एक लाइन है:
import kotlin.io.encoding.Base64
fun main() {
val pdf = byteArrayOf(0x25, 0x50, 0x44, 0x46, 0x2D) // "%PDF-"
println(Base64.Mime.encode(pdf)) // JVBERi0=
}
फँसाव चैनल अनुशासन हैं। बॉडी के लिए Default नहीं, Mime इस्तेमाल कीजिए, क्योंकि सख़्त MIME पार्सर रैपिंग की उम्मीद करता है, और 10,000 चरों की साधारण Default लाइन कुछ ट्रांसपोर्ट ठुकरा भी देंगे और खराब भी। Content-Transfer-Encoding लाइन में हेडर के केस को बिल्कुल base64 ही रखिए, और याद रखिए कि रैपिंग फ़ॉर्मैट का हिस्सा है: अनरैप्ड आउटपुट और रैप्ड आउटपुट वही बाइट्स की अलग-अलग रिप्रिज़ेंटेशन्स हैं, और दूसरी तरफ़ का पार्सर को पता होना चाहिए कि वह कौन-सा चबा रहा है।
फ़ील्ड नोट्स: JSON APIs और अपलोड्स
जब किसी API को JSON दस्तावेज़ में बायनेरी चाहिए, तो रीति-नीति है base64 रखने वाली स्ट्रिंग फ़ील्ड, और यह एकोसिस्टम के सबसे सुविधाजनक पैटर्नों में से एक है, क्योंकि JSON में टेक्स्ट के लिए पहले से घर है। kotlinx.serialization के साथ राउंड ट्रिप सीधा है:
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlin.io.encoding.Base64
@Serializable
data class UploadRequest(val name: String, val payload: String)
fun main() {
val icon = byteArrayOf(0x89.toByte(), 0x50, 0x4E, 0x47)
val request = UploadRequest("icon.png", Base64.encode(icon))
val json = Json.encodeToString(UploadRequest.serializer(), request)
println(json) // {"name":"icon.png","payload":"iVBORw=="}
}
यहाँ Base64 क्यों: JSON में बायनेरी टाइप नहीं है, इसलिए पेलोड टेक्स्ट होना ज़रूरी है, और Base64 वह सबसे कम-आश्चर्यजनक बायनेरी ग्रामर है जिसे API उपभोक्ता डॉक्यूमेंटेशन के बिना पहचान लेगा। फँसाव स्केल है। 4/3 अतिरिक्त शुल्क हर रिक्वेस्ट और हर रिस्पॉन्स पर चुकाया जाता है, और 10 मेगाबाइट का अपलोड 13.3 मेगाबाइट की JSON स्ट्रिंग बन जाता है जिसे आपके पार्सर को मेमोरी में थामना, escape करना और वैलिडेट करना पड़ेगा। बड़ी फ़ाइलों के लिए multipart/form-data या बायनेरी बॉडी लगभग हमेशा बेहतर वायर फ़ॉर्मैट है; base64-in-JSON thumbnails, आइकॉन्स, सिग्नेचर्स और छोटे ब्लॉब्स के लिए ही रखिए, जहाँ सुविधा कर से ज़्यादा होती है।
फ़ील्ड नोट्स: कॉन्फ़िग और कमांड लाइन
आख़िरी दो पैटर्न वो छोटे हैं जो हर कोडबेस में दिखते हैं। कॉन्फ़िग वैल्यूज़, टोकन, लाइसेंस कीज़, कभी-कभी छोटे सीक्रेट्स, अक्सर base64 के रूप में एनवायरनमेंट वेरिएबल्स और प्रॉपर्टी फ़ाइलों के रास्ते सफ़र करते हैं, क्योंकि ट्रांसपोर्ट टेक्स्ट-ओन्ली है और वैल्यू में कोट्स या नई लाइन्स हो सकती हैं। उन्हें वापस पढ़ना वही दो-कदमी नृत्य उल्टी दिशा में है: System.getenv या प्रॉपर्टी लुकअप, फिर डिकोड। कमांड लाइन पर, सफ़र या इंसपेक्शन के लिए फ़ाइल एन्कोड करना एक दस-लाइन का प्रोग्राम है:
import java.io.File
import kotlin.io.encoding.Base64
fun main(args: Array<String>) {
require(args.isNotEmpty()) { "usage: b64encode <file>" }
val bytes = File(args[0]).readBytes()
val encoded = Base64.encode(bytes)
File(args[0] + ".b64").writeText(encoded)
println("Wrote ${encoded.length} characters to ${args[0]}.b64")
}
दस बाइट्स hello file वाली फ़ाइल पर चलाइए, तो वह 16 चर लिखता है, aGVsbG8gZmlsZQ==। दोनों केस के फँसाव वही दो हैं: कॉन्फ़िग में Base64 कोई ख़ज़ानाख़ाना नहीं है, वैल्यू प्लेनटेक्स्ट से एक कदम दूर है और वायर पर किसी भी हालत में उसे सीक्रेट की तरह ही समझना चाहिए, और हाथ से बने CLI टूल को अपनी वर्णमाला जान-बूझ कर तय करनी चाहिए, क्योंकि वह यूज़र जिसने आपका आउटपुट URL में पाइप किया, उसे Default नहीं UrlSafe चाहिए।
एन्कोडिंग के समय क्या ख़राब हो सकता है
एन्कोडिंग कंटेंट के लिए सहनशील है: कोई भी बाइट सिक्वेंस वैध इनपुट है, इसलिए डिकोडर्स वाली तरह का "invalid symbol" फेल तो यहाँ नहीं। जो थ्रो करता है वह ज्योमेट्री है, और मैसेज इतने सटीक हैं कि उपयोगी भी:
| हाल | एक्ससेप्शन | मैसेज |
|---|---|---|
endIndex अरेय़ के अंत के आगे | IndexOutOfBoundsException | startIndex: 0, endIndex: 100, size: 5 |
startIndex जो endIndex से आगे हो | IllegalArgumentException | startIndex: 3 > endIndex: 2 |
encodeIntoByteArray के लिए डिस्टिनेशन अरेय़ बहुत छोटा | IndexOutOfBoundsException | The destination array does not have enough capacity, destination offset: 0, destination size: 2, capacity needed: 8 |
इनके साथ बैठे दो Kotlin-खास फँसाव हैं। पहला क्लासिक int + String ग़लती है: bytes.size + " bytes" कंपाइल नहीं होता, क्योंकि Int पर प्लस स्ट्रिंग्स को जोड़ता नहीं है; इंटरपोलेशन शक्ल "${bytes.size} bytes" ही Kotlin का तरीका है। दूसरा कैरेक्टर सेट लुकअप है, जो UnsupportedCharsetException थ्रो करता है जब नाम JVM को पहचान न आए, जैसे Charset.forName("utf-9"), इसलिए कैरेक्टर सेट नाम में टाइपो एक कम्पाइल एरर नहीं, रनटाइम एक्ससेप्शन है, और वह एन्कोडर चलने की जगह सतह पर आता है, नाम टाइप किए जाने की जगह पर नहीं।
फँसाव, Kotlin शैली में
नीचे के फँसाव वही हैं जो ख़ास तौर पर उन Kotlin डेवलपर्स को काटते हैं जो पहली बार स्टैंडर्ड लाइब्रेरी की तरफ़ हाथ बढ़ा रहे हैं:
encodeToByteArray()को आपके लिए कैरेक्टर सेट चुनने देना। वह हमेशा UTF-8 होता है, चुपचाप, और Latin-1 या UTF-16 सोर्स ऐसे बाइट्स में पैक हो जाएगा जो डिकोडर वापस पढ़ ही नहीं पाएगा। कैरेक्टर सेट जान-बूझ कर तय कीजिए, UTF-8 न होने पर JVM परtoByteArray(charset)के साथ।- मस्कल मेमोरी में
java.util.Base64की तरफ़ हाथ बढ़ाना। उसकाgetUrlEncoder()डिफ़ॉल्ट रूप से पैड करता है, जो JWTs के लिए ग़लत शक्ल है जब तक आप.withoutPadding()न याद रखें; Kotlin प्रीसेट दोनों तरफ़ यह चुनाव एक्सप्लिसिट बनाता है। MimeयाPemरैप्ड आउटपुट को सिंगल-लाइन उपभोक्ता को खिलाना। CRLFs रिप्रिज़ेंटेशन का हिस्सा हैं और एक लाइन की उम्मीद रखने वाले सख़्त डिकोडर को फेल कर देंगे; रैप तभी कीजिए जब चैनल रैपिंग की उम्मीद करे।- Kotlin स्ट्रिंग पर
text.getBytes()लिखना। Java की मेटोडkotlin.Stringपर नज़र नहीं आती; इनलाइनtoByteArray(charset)एक्सटेंशन, जो Kotlin 1.0 से मौजूद है, ही विकल्प है। - पुराने टूलचेन चलाना। कुछ डिस्ट्रीब्यूशन पर सिस्टम Kotlin आज भी 1.3 है, जो स्टैंडर्ड लाइब्रेरी
Base64से पूरी तरह पहले की है; क्लास के अस्तित्व के लिए 1.8.20 चाहिए, पैडिंग कंट्रोल के लिए 2.0.20, और स्टेबल के लिए 2.2। - Base64 को सिक्योरिटी लेयर मानना। वह एक ट्रांसपोर्ट एन्कोडिंग है जिसकी एक-स्टेप, पब्लिक उल्टी है। जो भी सीक्रेट है, उसे पैक होने से पहले एनक्रिप्ट करना चाहिए, बस पैक करना कभी नहीं।
सही चुनाव: एक तेज़ डिसीज़न गाइड
अगर संदेह हो, तो फैसला लगभग हमेशा चैनल करता है, कंटेंट नहीं। छोटा संस्करण:
Base64.DefaultAPIs, JSON, डेटा URLs और हर उस चीज़ के लिए जो असल में एक ही लाइन की टेक्स्ट है। पैडेड आउटपुट वायर पर सबसे कम्पैटिबल शक्ल है।Base64.UrlSafeABSENTपैडिंग के साथ टोकन, JWTs और हर उस चीज़ के लिए जो URL सेगमेंट या क्वरी पैरामीटर में उतरती है।Base64.Mimeईमेल बॉडी और अटैचमेंट्स के लिए, जहाँ 76-चरों की लाइन्स फ़ॉर्मैट की कड़ी माँग हैं।Base64.Pemसर्टिफ़िकेट्स और प्राइवेट कुंजियों के लिए, जहाँ 64-चरों की लाइन्स हर PKI टूल की उम्मीद हैं।
फिर दो क्रॉस-कटिंग आदतें: जब भी इनपुट टेक्स्ट हो, कैरेक्टर सेट एक्सप्लिसिट कीजिए, और 4/3 अतिरिक्त शुल्क पर नज़र रखिए ताकि बड़े पेलोड को base64 चैनल के बजाय बायनेरी चैनल मिले।
स्टैंडर्ड तक का रास्ता
स्टैंडर्ड लाइब्रेरी में Base64 तक का रास्ता इतना हाल का है कि आपको उससे रहने वाले पुराने Kotlin भी मिलेंगे। क्लास पहली बार अप्रैल 2023 में Kotlin 1.8.20 में आई, एक्सपेरिमेंटल चिह्नित, तीन इंस्टेंस और एक साधारण सतह के साथ: एन्कोडिंग हमेशा पैडेड, और कम माँगने का कोई रास्ता नहीं। अगर आपको 1.8 युग का ऐसा कोड मिला है जो removeSuffix से स्ट्रिंग के आख़िर का = काटता है, तो वह उस युग का बिना पैडिंग वाले आउटपुट का एकमात्र टूल था, और अब छोड़ने लायक आदत है। Kotlin 2.2, जो जून 2025 में रिलीज़ हुआ, ने API को स्टेबल किया और आख़िरी टुकड़ा जोड़ी, Pem इंस्टेंस। PaddingOption डायल और withPadding पहले से 2.0 लाइन में आ चुके थे, बिल्कुल सटीक 2.0.20 में, पैडिंग को दोनों दिशाओं में कंट्रोल करने का पहला first-class तरीका। स्ट्रीमिंग हेल्पर्स encodingWith और decodingWith अभी भी एक्सपेरिमेंटल और JVM-only हैं, और यही वह तरीका है जिससे स्टैंडर्ड लाइब्रेरी उन APIs को चिह्नित करती है जिन पर फ्रीज़ करने से पहले ज़्यादा फ़ील्ड अनुभव चाहिए। 2.4.0 लाइन से, भाषा ने स्टैंडर्ड लाइब्रेरी के लिए 18-महीने के सपोर्ट विंडो पर भी हस्ताक्षर किए, इसलिए 2.4.x कंपाइलर पर पिन प्रोजेक्ट - जैसे 2.4.10 स्टेबल रिलीज़ जो इस लेख के लिखते समय वर्तमान है - उस सपोर्ट साइकिल की पूरी उम्र तक पूरा Base64 API पाता है।
छोटे-छोटे आश्चर्य
कुछ बारीकियाँ जो जानने के बाद क्लास को और दिलचस्प बना देती हैं:
- बिना इंस्टेंस के
Base64.encode(bytes)इसलिए काम करता है क्योंकिBase64.Defaultकॉम्पैनिऑन ऑब्जेक्ट पर परिभाषित है; कॉम्पैनिऑन ही डिफ़ॉल्ट स्कीम है, इसलिए sugar और नामित रूप हकीकत में वही ऑब्जेक्ट हैं। encodeToAppendableफ़ंक्शन बिल्डर स्टाइल है: वह डिस्टिनेशन ऐपेंडेबल लौटता है, इसलिए डॉक्यूमेंटेड पैटर्न है रिटर्न वैल्यू को अनदेखा करके अपने बिल्डर का इस्तेमाल जारी रखना।- पैडिंग कभी पूरा ग्रुप नहीं भरती: base64 स्ट्रिंग शून्य, एक या दो
=चरों पर ख़त्म होती है, और पैड गिनने से बिल्कुल पता चलता है कि मूल के आख़िरी ट्रिपल में कितनी बाइट्स बची थीं। Pemचारों प्रीसेट में सबसे नया है, 2.2 में शामिल हुआ; 64-चरों की रैपिंग वह PKI परंपरा है जो उसके बगल बैठे RFC 2045 नियम से पुरानी है।- JVM पर स्टैंडर्ड लाइब्रेरी जान-बूझ कर
java.util.Base64को डिलीगेट नहीं करती; दोनों इम्प्लीमेंटेशन अलग हैं, जिससे व्यवहार हर प्लेटफ़ॉर्म पर समान रहता है, और कीमत है एक कॉमेंट-आउट ऑप्टिमाइज़ेशन की जिसे Kotlin टीम तब के लिए ट्री में रखी है जब Java API उसे मान ले। - वही 2.2 रिलीज़ जो
Base64को स्टेबल करती है, उसनेHexFormatको भी स्टेबल किया,kotlin.textकी वह hex फ़ॉर्मैटिंग क्लास जो Kotlin 1.9 से एक्सपेरिमेंटल है, ताकि बाइट-लेवल टेक्स्टुअल एन्कोडिंग्स का अब स्टैंडर्ड लाइब्रेरी में बसा हुआ घर हो।
निष्कर्ष और आगे कहाँ
Kotlin में Base64 बनाना कुछ चुनिंदा, जान-बूझ कर की गई तयारियों की छोटी सूची है: चैनल के हिसाब से प्रीसेट चुनिए, पैडिंग जान-बूझ कर तय कीजिए, इनपुट टेक्स्ट होने पर कैरेक्टर सेट एक्सप्लिसिट रखिए, और पेलोड बड़ा होने पर 4/3 अतिरिक्त शुल्क का सम्मान कीजिए। बाक़ी सब - फ़ाइलें, बफ़र, ऐपेंडेबल्स, स्ट्रीम्स - वही चारों इंस्टेंस के चारों ओर पतला रैपर हैं। दूसरी दिशा, उस पैक्ड टेक्स्ट को वापस बाइट्स में ले जाना, अपने सख़्ताई नियम, अपने फेल मोड्स और अपने फँसाव रखती है, और बहन साइट का संबंधित लेख Kotlin में Base64 डिकोडिंग को गहराई से कवर करता है।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: Kotlin में Base64 डिकोडिंग: एक सम्पूर्ण गाइड