Ruby में Base64 डिकोडिंग: एक सम्पूर्ण गाइड
आप और मूल डेटा के बीच कहीं अक्षरों की एक दीवार खड़ी है: बड़े और छोटे केस के अक्षर, अंक, शायद एक प्लस या स्लैश या हाइफ़न, और शायद आख़िर में पार्क किया हुआ एक इक्वल्स चिह्न। आपका एडिटर इसका फ़ाइल टाइप ही समझ नहीं पा रहा। आपका डेटाबेस ने इसे एक टेक्स्ट कॉलम में ठूँस दिया। यह किसी HTTP हेडर में आया, किसी URL में, किसी YAML की में, या किसी सपोर्ट टिकट में जिसमें .b64 एटैचमेंट लगा था। आप इसे पल भर में पहचान लेते हैं - Base64 - और अब आपको बाइट्स वापस चाहिए। Ruby में वह ज़रूरत सिर्फ़ एक require स्टेटमेंट और एक मेथड कॉल दूर है।
यदि यह फ़ॉर्मेट आपको नया है, तो यह रहा तीस सेकंड का संस्करण। Base64 कच्चे डेटा को तीन-तीन बाइट्स के हिसाब से फिर से लिखता है: हर तीन बाइट्स का ग्रुप 64-चिह्न वर्णमाला के चार चरों में बदल जाता है, और जब इनपुट तीन से बराबर नहीं बटता, तो एक या दो = चर पैडिंग के रूप में जोड़े जाते हैं, ताकि आउटपुट हमेशा चार का गुणज बनकर आराम करे। डिकोडिंग उल्टा सफ़र है - चार चर अंदर, तीन बाइट्स बाहर - इसलिए नतीजा हमेशा इनपुट से छोटा होता है, आकार में इनपुट का करीब तीन-चौथाई। इस साइट का होम पेज इस फ़ॉर्मेट के हर पहलू को विस्तार से समझाता है, इसलिए यह गाइड अपनी मेहनत वहीं लगाती है जहाँ वह चाहिए: काम के Ruby पक्ष पर।
अच्छी बात: हर Ruby इंस्टॉलेशन पूरा डिकोडिंग टूलकिट के साथ शिप होता है। Base64 मॉड्यूल को कुछ इंस्टॉल करने की ज़रूरत नहीं, और इसके तीन डिकोडर इतने छोटे हैं कि आप एक ही बैठक में उनके पूरा सोर्स पढ़ सकते हैं। सावधानी: जिस डिकोडर को आप सबसे पहले हाथ लगाते हैं, वही वह भी है जो कभी शिकायत नहीं करता - ईमेल के लिए यह एक खूबसूरत गुण है और सिक्योरिटी के लिए एक खराब गुण। इस गाइड के अंत तक आपको बिल्कुल साफ़ पता हो जाएगा कि हर डिकोडर क्या-क्या स्वीकार करता है, उसके वापस दिए बाइट्स को ऐसे टेक्स्ट में कैसे बदलें जिसका इस्तेमाल Ruby आपको करने दे, और ऐसे हर पेलोड से कैसे निपटें जिसे कोई Ruby डेवलपर वाकई डिकोड करता है - JWT, ऑथेंटिकेशन हेडर, data URI, ईमेल बॉडी, PEM कवच, फ़ाइलें, कॉन्फ़िग ब्लाब और बड़े-से-बड़े एक।
टूलबॉक्स से मिलिए
सब कुछ एक require से शुरू होता है। कोई इंस्टॉलेशन स्टेप नहीं, कोई प्लेटफ़ॉर्म की विचित्रता नहीं, कोई नेटिव एक्सटेंशन बनाने को नहीं:
require "base64"
puts Base64::VERSION
# => 0.2.0, stock Ruby 3.3 पर, for example
यहाँ टूलकिट का पूरा डिकोडिंग पक्ष एक ही टेबल में है, हर मेथड के इस्तेमाल की तादाद के क्रम में:
| डिकोडर | वर्णमाला के बाहर के चरों का इलाज | पैडिंग के नियम | जब कुछ ग़लत हो |
|---|---|---|---|
Base64.decode64(str) |
स्टैंडर्ड वर्णमाला में न होने वाला हर चर नज़रअंदाज़ करता है, लाइन ब्रेक और स्पेस समेत | कुछ भी, ग़लत पैडिंग भी | कुछ नहीं - यह कभी एरर नहीं उठाता, बस जो डिकोड कर सके, वही लौटाता है |
Base64.strict_decode64(str) |
स्टैंडर्ड वर्णमाला के बाहर का कोई भी चर रिजेक्ट करता है | होनी ज़रूरी है और बिल्कुल सही होनी चाहिए | ArgumentError उठा देता है |
Base64.urlsafe_decode64(str) |
URL-safe वर्णमाला और स्टैंडर्ड दोनों स्वीकार करता है, बाकी सब रिजेक्ट करता है | वैकल्पिक, पर मौजूद हो तो सही होनी चाहिए | ArgumentError उठा देता है |
अगर आप जानना चाहते हैं कि आपके टूल्स अंदरों-अंदर क्या कर रहे हैं, तो मॉड्यूल का पूरा डिकोडिंग पक्ष कोर pack/unpack मशीनरी के दो टेम्पलेट की पतली परत है, जो C में, Ruby कोर के अंदर, लिखी गई है:
# मॉड्यूल का पूरा डिकोडिंग पक्ष, संक्षिप्त रूप में
def decode64(str)
str.unpack1("m")
end
def strict_decode64(str)
str.unpack1("m0")
end
m टेम्पलेट शालीन पाठक है, m0 सख़्त वाला, और उस एक चर के फ़र्क़ ने ही पहले दो डिकोडरों के बीच का पूरा मिज़ाज समझा दिया। क्योंकि भारी-भरकम काम कोर की रफ़्तार पर होता है, मॉड्यूल शुद्ध Ruby ही रहता है और फिर भी मेगाबाइट्स को एक अंकों के मिलीसेकंड में चबा लेता है।
decode64: रंगबदल
Base64.decode64 वह डिकोडर है जो हर चीज़ को हाँ कहता है। इसे साफ़ पेलोड दें, वह डिकोड कर देगा। लाइन ब्रेकों से भरा MIME-शैली का ब्लाब दें, वह कंधे उछाल लेगा। ऐसी स्ट्रिंग दें जो Base64 ही नहीं है, वह भी जो निकाल सके वही लौटा देगा - एक भी चेतावनी के बिना:
require "base64"
Base64.decode64("aGVsbG8gd29ybGQ=")
# => "hello world"
Base64.decode64("Zm9vCmJh\ncgptYW4=\n")
# => "foo\nbar\nman"
वही दूसरी लाइन एक ही उदाहरण में पूरा मिज़ाज कह देती है। डिकोडर स्टैंडर्ड वर्णमाला का हिस्सा न होने वाला सब कुछ छोड़ देता है - लाइन ब्रेक, स्पेस, कोई अजीब कंट्रोल चर - और बाकी को डिकोड करता है। यही तो MIME Base64 का व्यवहार होना चाहिए, इसीलिए ईमेल से सफ़र की किसी भी चीज़ के लिए decode64 सही टूल है।
उल्टा पक्ष ही इसको ख़तरनाक बनाता है। क्योंकि डिकोडर कभी शिकायत नहीं करता, वह यह भी कभी नहीं बताता कि इनपुट ग़लत था:
Base64.decode64("not base64 at all!")
# => 10 बाइट्स का बिल्कुल पक्के लगने वाला कचरा
Base64.decode64("====")
# => ""
पहले उदाहरण में वे चर जो बस-बस वैध वर्णमाला के अक्षर निकले, वह उन्हें ढूँढकर डिकोड कर देता है, और बाइट्स लौटा देता है जिन्हें सीधे फ़ाइल में लिख देने की लालसा जाग सकती है। दूसरा उदाहरण चार पैडिंग चरों की स्ट्रिंग के लिए ख़ाली स्ट्रिंग लौटाता है। कुछ एरर नहीं उठता, कुछ लॉग नहीं होता। अगर आपका इनपुट अविश्वसनीय है, तो वह ख़ामोशी एक ऐसी ख़ूबी है जिसे आप बंद करवाना चाहेंगे - यही तो अगले दो डिकोडरों का काम है।
एक और विचित्रता जानने लायक है, क्योंकि यही वह चीज़ है जो प्रोडक्शन में महीनों छिपकर रह जाती है: डिकोडिंग पहले = चर पर ही रुक जाती है। पैडिंग के बाद जो कुछ भी है, वह एरर नहीं है; वह बस कभी पढ़ा ही नहीं जाता:
Base64.decode64("aGVsbG8=Zm9vYmFy")
# => "hello" "Zm9vYmFy" वाला हिस्सा डिकोडर के लिए अदृश्य है
strict_decode64: द्वारपाल
Base64.strict_decode64 क्लिपबोर्ड थामे खड़ा डिकोडर है। वह सिर्फ़ स्टैंडर्ड वर्णमाला स्वीकार करता है (A से Z, a से z, 0 से 9, प्लस, स्लैश), किसी भी पैडिंग का बिल्कुल सही होना मांगता है, और अगर कोई भी नियम टूटा तो एक बाइट तक नहीं निकालने के लिए इनकार कर देता है:
Base64.strict_decode64("aGVsbG8gd29ybGQ=")
# => "hello world"
Base64.strict_decode64("aGVsbG8gd29ybGQ")
# => ArgumentError फेंकता है
Base64.strict_decode64("Zm9vCmJh\ncgptYW4=")
# => ArgumentError फेंकता है
आख़िरी लाइन ही असली बात कहती है: वही पेलोड जो decode64 खुशी-खुशी डिकोड कर चुका था, अब सिर्फ़ एक लाइन ब्रेक की वजह से एरर उठा देता है। पैडिंग की कमी, पैडिंग का ज़्यादा होना, हाइफ़न, अंडरस्कोर, स्पेस - सबमें से कोई भी एक अपराध है, और पूरा पेलोड उसके साथ डूब जाता है:
begin
Base64.strict_decode64("aGVsbG8")
rescue ArgumentError => e
puts e.message
end
# => invalid base64
द्वारपाल उन कोनों की भी निगरानी करता है जिनकी जाँच आप सोच भी न पाएँ। जब कोई Base64 स्ट्रिंग पैडिंग के साथ ख़त्म होती है, तो आख़िरी चर के कुछ बिट्स कभी इस्तेमाल नहीं होते, और RFC के अनुसार मानक-अनुसार एन्कोडर को उन बिट्स को शून्य रखा होना चाहिए। Ruby जाँचता है:
Base64.strict_decode64("QQ==")
# => "A"
Base64.strict_decode64("QR==")
# => ArgumentError फेंकता है (पैड बिट्स ज़ीरो नहीं हैं)
अगर डिकोडर लापरवाह होता, तो दूसरी स्ट्रिंग पहले वाली से वही बाइट डिकोड होती। Ruby लापरवाह नहीं है। हकीकत में इसीलिए strict_decode64 हर ऐसे इनपुट के लिए सही डिफ़ॉल्ट है जिसे आपने खुद एन्कोड नहीं किया: यह टाइपिंग ग़लतियों, कटौती और ग़लत वर्णमाला को तेज़, पकड़ने योग्य एररों में बदल देता है, ख़ामोश कोरोप्शन के बजाय।
urlsafe_decode64: राजनयिक
Base64.urlsafe_decode64 उन पेलोड्स के लिए मौजूद है जिनका सफ़र उन जगहों से होता है जहाँ + और / रिज़र्वड शब्द हैं: URL, टोकन, डेटाबेस पहचान-चिह्न। अंदरों-अंदर वह URL-safe वर्णमाला (हाइफ़न और अंडरस्कोर) को वापस स्टैंडर्ड वर्णमाला में बदलता है, पैडिंग को सुधारता है, और नतीजे को सख़्त डिकोडर के हाथों सौंपता है:
Base64.urlsafe_decode64("SGVsbG8gd29ybGQ")
# => "Hello world"
Base64.urlsafe_decode64("SGVsbG8gd29ybGQ==")
# => ArgumentError फेंकता है (पंद्रह चरों को एक पैड चर चाहिए, दो नहीं)
पहला उदाहरण उसकी सबसे काम की ख़ूबी दिखाता है: पैडिंग रहित इनपुट बिल्कुल ठीक है। अगर स्ट्रिंग में पैडिंग नहीं है और उसकी लंबाई चार का गुणज नहीं है, तो डिकोडर आपके लिए ग़ायब = चर ख़ुद जोड़ देता है - और यही तो JSON Web Tokens, URL-safe Base64 के सबसे बड़े उपभोक्ता, निकालते हैं। लेकिन अगर पैडिंग मौजूद है, तो वह सही होनी चाहिए, ठीक जैसा सख़्त डिकोडर के साथ होता है।
एक ऐसी विचित्रता है जो दस्तावेज़ीकरण चिल्लाकर नहीं बताता: यह राजनयिक दोनों भाषाएँ बोलता है। क्योंकि यह मेथड सख़्त डिकोड करने से पहले हाइफ़न और अंडरस्कोर को फिर से लिख देता है, इसलिए वह स्टैंडर्ड वर्णमाला की स्ट्रिंग्स को भी स्वीकार करता है:
Base64.urlsafe_decode64("aGVsbG8=")
# => "hello" स्टैंडर्ड वर्णमाला भी मंज़ूर है
यह नरमी सुविधाजनक है, लेकिन इसका मतलब यह भी है कि आप इस मेथड से यह पहचान नहीं सकते कि पेलोड किस वर्णमाला से आया है। अगर यह आपके लिए मायने रखता है, तो डिकोड करने से पहले चरों की जाँच खुद करें।
और decode64 के उलट, यह राजनयिक व्हाइटस्पेस के लिए कोई दया नहीं रखता। URL-safe पेलोड में कहीं भी लाइन ब्रेक होना ArgumentError उठाने का कारण बनता है, इसलिए अगर आपका इनपुट किसी लपेटे हुए फ़ाइल से आ रहा है, तो पहले लाइन ब्रेक हटा लें।
बाइट्स टेक्स्ट नहीं हैं: एन्कोडिंग का कदम
यह वह कदम है जिसमें अनुभवी डेवलपर्स भी फँस जाते हैं, क्योंकि Ruby इसे खुलकर दिखाता है। डिकोड की गई Base64 स्ट्रिंग हमेशा ASCII-8BIT एन्कोडिंग (अर्थात् BINARY) के टैग के साथ आती है, चाहे मूल डेटा PNG हो, JWT पेलोड हो, या UTF-8 में लिखा प्यार भरा पत्र:
bin = Base64.decode64(Base64.strict_encode64("h\u{e9}llo"))
puts bin.encoding
# => ASCII-8BIT
puts bin.bytes
# => [104, 195, 169, 108, 108, 111]
अगर पेलोड बाइनरी है - तस्वीर, zip फ़ाइल, हैश - तो उसे बिल्कुल वैसा ही रखें और File.binwrite से लिखें। न कन्वर्ज़न, न सवाल। अगर पेलोड टेक्स्ट है, तो बाइट्स ज़्यादातर UTF-8 ही होंगे, और आपको Ruby को यह बताना होगा:
text = Base64.decode64(payload)
text.force_encoding("UTF-8")
if text.valid_encoding?
puts text
else
puts "not valid UTF-8 after all"
end
इन दो कॉल का काम अलग-अलग है। force_encoding बस बाइट्स को नया लेबल देता है; valid_encoding? फिर जाँचता है कि वे असली UTF-8 बनाते हैं। उन्हें इसी क्रम में चलाएँ, क्योंकि BINARY स्ट्रिंग की पहले वैलिडेशन करने को कुछ वैलिडेट करने को ही नहीं मिलता। और एक छोटी सी तुलना-फ़सा, जिसे जीवनभर याद रखने की ज़रूरत है: Ruby BINARY स्ट्रिंग को UTF-8 स्ट्रिंग के बराबर तभी मानता है जब दोनों शुद्ध ASCII हों, इसलिए अपने मूल टेक्स्ट से डिकोड किए टेक्स्ट की तुलना करने से पहले लेबल बदल लें:
decoded = Base64.decode64("aMOpbGxv")
puts decoded == "h\u{e9}llo"
# => false वही बाइट्स, अलग टैग्स
decoded.force_encoding("UTF-8")
puts decoded == "h\u{e9}llo"
# => true
JWT: बिना की के टोकन पढ़ना
JSON Web Token तीन Base64 स्ट्रिंग्स हैं जिन्हें डॉट्स से आपस में पिन किया गया है: हेडर, पेलोड, सिग्नेचर। पहली दो JSON दस्तावेज़ों की URL-safe, बिना-पैडिंग वाली Base64 हैं, जिसका मतलब है कि टोकन को जो भी इसे कभी देखे वह पढ़ सकता है - आप समेत, बिना किसी लाइब्रेरी के:
require "base64"
require "json"
token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0.dW5zaWduZWQ"
header_part, payload_part = token.split(".")[0, 2]
JSON.parse(Base64.urlsafe_decode64(payload_part))
# => {"sub"=>"1234567890", "name"=>"Alice"}
असली काम के लिए आप jwt gem इस्तेमाल करेंगे, जो वह हिस्सा संभालता है जो आपको वाकई सुरक्षित रखता है - सिग्नेचर - और क्लेम वैलिडेशन:
# Gemfile में: gem "jwt"
require "jwt"
token = JWT.encode(
{ sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
"my-secret-key",
"HS256"
)
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts payload["name"]
# => Alice
यहाँ दो सिक्योरिटी नोट्स ज़रूर पढ़ने हैं, क्योंकि दोनों ने लोगों को असली इंसिडेंटों की कीमत चुकानी पड़ी है। पहला, पेलोड एन्क्रिप्टेड नहीं है; उसे डिकोड करना पढ़ना है, क्रैक करना नहीं, और सिग्नेचर ही एकमात्र सुरक्षा है, इसलिए कभी भी डिकोड किए पेलोड को भरोसेमंद इनपुट की तरह मत मानिए। दूसरा, JWT.decode में अल्गोरिदम को बिल्कुल वैसे ही तय करें जैसा ऊपर दिखाया गया है। उसे छोड़ देने से टोकन का अपना हेडर तय कर लेता है कि उसे कैसे वेरिफ़ाई करना है, और वही एक टुकड़ी लचीलापन वही चीज़ है जो मशहूर JWT अल्गोरिदम-कन्फ़्यूज़न एटैक्स का फ़ायदा उठाते हैं।
Basic Auth: सबकी नज़रों के सामने छिपा पासवर्ड
वेब पर सबसे पुराना ऑथेंटिकेशन हेडर खुद Base64 है। HTTP Basic auth क्रेडेंशियल्स को Basic शब्द के बाद, एन्कोड किए हुए user:password रूप में भेजता है - और यह हेडर हर रिक्वेस्ट के साथ सफ़र करता है, इसलिए वह हर लॉग में सामने आता है जिसे आप कभी भी डीबग करेंगे। इसे डिकोड करना बस हटाने और कटने का काम है:
require "base64"
header_value = "Basic YWxpY2U6czNjcjN0IQ=="
b64 = header_value.sub("Basic ", "")
decoded = Base64.decode64(b64)
user, password = decoded.split(":", 2)
puts user
# => alice
puts password
# => s3cr3t!
split में 2 की सीमा मायने रखती है: पासवर्ड में कानूनी तौर पर कॉलन भी हो सकते हैं, और आपको कभी भी सिर्फ़ पहले वाले पर ही कटना चाहिए। Ruby की खुद की स्टैंडर्ड लाइब्रेरी इसी हेडर को उल्टी दिशा में बनाती है, Net::HTTP में, कोर pack टेम्पलेट का सीधा इस्तेमाल करके:
require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==
और वह सिक्योरिटी नोट जो भले ही सबकी नज़रों के सामने हो, फिर भी बोलना ज़रूरी है: Base64 अनुवादक है, ताला नहीं। Basic auth सिर्फ़ HTTPS के ऊपर ही स्वीकार्य है। इस एन्कोडिंग का अस्तित्व इसलिए है ताकि क्रेडेंशियल्स प्रिंट होने वाले टेक्स्ट के रूप में वायर पर सफ़र कर सकें, इसलिए नहीं ताकि वे गुप्त रहें।
Data URIs: जो इमेज है, फ़ाइल नहीं
data URI एक URL के अंदर पूरी फ़ाइल छुपाता है: एक मीडिया टाइप, base64 शब्द, एक कॉमा, और एन्कोड किए बाइट्स। ब्राउज़र इन्हें img टैग और CSS में रेंडर करते हैं, और सिंगल-फ़ाइल वाले HTML ऐप्स इन्हें प्यार से अपनाते हैं क्योंकि दूसरी रिक्वेस्ट करने की ज़रूरत नहीं पड़ती। Ruby में इसे बनाने में एक ही लाइन लगती है:
require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
इसे डिकोड करना उल्टा सफ़र है, जिसमें दो ऐसी बातें हैं जो लोगों को फँसाती हैं। कॉमा ही सैपरेटर है, इसलिए बिल्कुल एक ही बार split करें, और मीडिया टाइप वाला हिस्सा कुछ भी हो सकता है, ख़ाली भी:
data_uri = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg=="
media_part, b64 = data_uri.split(",", 2)
puts media_part
# => data:image/png;base64
bytes = Base64.strict_decode64(b64)
File.binwrite("restored.png", bytes)
यहाँ strict_decode64 इस्तेमाल करें, decode64 नहीं: data URI का पेलोड एक साफ़, एकल लाइन होता है, और अगर वह ख़राब है तो आपको तेज़ एरर चाहिए। साथ ही साइज़-टैक्स को भी याद रखें - जो भी इमेज आप इनलाइन करते हैं, वह करीब एक-तिहाई बढ़ जाती है - इसलिए data URI फ़ेविकॉन्स, छोटे लोगो और फ़ॉन्ट्स के लिए बिल्कुल सही हैं, और हीरो फ़ोटो के लिए बुरा फ़ैसला।
ईमेल: छप्पन चर की लाइनें और mail gem
Base64 ईमेल के लिए ही बना था, और इसके निशान साफ़ दिखते हैं। SMTP का डिज़ाइन सात-बिट टेक्स्ट की छोटी-छोटी लाइनों के लिए था, इसलिए MIME Base64 अपने आउटपुट को छोटी लाइनों में लपेट लेता है, और मानक-अनुसार डिकोडर को उन लाइन ब्रेक को नज़रअंदाज़ करना ही होता है। Ruby का decode64 बिल्कुल वैसा ही व्यवहार करता है, इसलिए लपेटा हुआ MIME बॉडी इसके लिए आसान भोजन है:
body = "Zm9vCmJh\ncgptYW4=\n"
Base64.decode64(body)
# => "foo\nbar\nman"
आप उस काम को हाथ से बहुत कम लिखेंगे। mail gem पूरा MIME काम आपके लिए कर देता है: एटैचमेंट अपने-आप Base64 एन्कोड हो जाते हैं, लाइनें 60 चर पर लपेटी जाती हैं, जो MIME की 76-चर की सीमा के आराम से अंदर है, और सही हेडर साथ जुड़ जाते हैं:
# Gemfile में: gem "mail"
require "mail"
message = Mail.new do |m|
m.from = "dev@example.org"
m.to = "ops@example.org"
m.subject = "Binary report"
m.add_file("report.bin")
end
puts message.encoded
# एटैचमेंट वाला भाग Content-Transfer-Encoding: base64 के साथ चलता है
वही तरकीब ईमेल हेडर के अंदर भी छिपी है। एक ग़ैर-ASCII सब्जेक्ट लाइन RFC 2047 एन्कोडेड वर्ड के रूप में आती है: एक चरसेट, चर B, और प्रश्न चिह्न के बीच Base64। इसे हाथ से डिकोड करना स्ट्रिंग सर्जरी का एक छोटा सा अभ्यास है:
header_value = "=?UTF-8?B?w7wgc2VjcmV0cw==?="
charset, kind, b64 = header_value.sub(/\A=\?/, "").sub(/\?=$/, "").split("?")
text = Base64.decode64(b64).force_encoding(charset)
puts text
# => ü secrets
PEM: कवच में कीज़ और सर्टिफिकेट्स
कीज़ और सर्टिफिकेट्स अपना ज़्यादातर जीवन PEM कवच के अंदर बिताते हैं: एक BEGIN लाइन, Base64 का ब्लॉक, और एक END लाइन। यह कवच 1980 के दशक से आया है - Privacy-Enhanced Mail वही जगह है जहाँ पूरा Base64 वंश शुरू होता है - लेकिन आज भी आपके .crt और .key फ़ाइलें यही फ़ॉर्मेट पहनती हैं।
PEM फ़ाइल को हाथ से डिकोड करना बस कवच उतारना है और शालीन डिकोडर को लाइन ब्रेक चबाते हुए छोड़ देना है:
require "base64"
pem = File.read("server.key")
body = pem.lines
.reject { |line| line.start_with?("-----") || line.strip.empty? }
.join
key_bytes = Base64.decode64(body)
असली इस्तेमाल के लिए आप आमतौर पर हाथ का काम छोड़ देंगे और पूरी PEM स्ट्रिंग को OpenSSL को सौंप देंगे, जो कवच खुद पढ़ लेता है:
require "openssl"
key = OpenSSL::PKey.read(File.read("server.key"))
puts key.class
# => OpenSSL::PKey::RSA, या जो भी की निकलती है
इंटरऑपरेबिलिटी की एक ही बात जानने लायक है: PEM लाइनें पारंपरिक रूप से 64 चर लंबी होती हैं, और डिकोडर लाइन ब्रेक को भले कितने भी हों नज़रअंदाज़ कर ही देता है, इसलिए 60-चर की लपेट हो या एक भारी-भरकम लाइन, दोनों बराबर अच्छे से डिकोड होते हैं।
फ़ाइलें और .b64 का रीवाज़
Base64 की दुनिया में सबसे आम फ़ाइल फ़ॉर्मेट एक प्लेन टेक्स्ट फ़ाइल है जिसका एक्सटेंशन .b64 (कभी-कभी .base64) होता है, और जिसमें एक एन्कोड किया हुआ पेलोड रहता है। इसे पढ़ना तीन कदमों का सफ़र है:
require "base64"
encoded = File.read("payload.b64")
bytes = Base64.decode64(encoded)
File.binwrite("payload.bin", bytes)
बाहर निकालते समय File.binwrite इस्तेमाल करें - डिकोड किया हुआ PNG या zip बाइनरी होता है, और उन प्लेटफ़ॉर्म्स पर जहाँ लाइन एंडिंग्स बदल दी जाती हैं, टेक्स्ट मोड में लिखना उसे ख़राब कर देगा। अगर आपकी .b64 फ़ाइल किसी ऐसे टूल से आई है जो लाइनें लपेटता है, तो decode64 लाइन ब्रेक बिल्कुल निःशुल्क संभाल लेता है। अगर स्वीकार करने के बजाय वैलिडेट करना चाहते हैं, तो फ़ाइल को बाइनरी मोड में पढ़ें और सख़्त डिकोड से पहले लाइन ब्रेक हटा लें:
encoded = File.binread("payload.b64")
clean = encoded.delete("\r\n")
bytes = Base64.strict_decode64(clean)
बाइनरी मोड में पढ़ना Windows पर मायने रखता है, जहाँ टेक्स्ट मोड CRLF लाइन एंडिंग्स को LF में फिर से लिख देता है - ठीक वही बदलाव, जो आप वैलिडेट करने जा रही स्ट्रिंग के अंदर नहीं चाहते।
URL-Safe Base64: लिंक्स में सफ़र करने वाले पेलोड्स
यह URL-safe संस्करण पर डिकोडर की नज़र है, क्योंकि यहाँ जो चयन आप करते हैं, उससे तय होता है कि तीन डिकोडरों में से किसको आप हाथ लगाते हैं। URL-safe Base64 (RFC 4648, सेक्शन 5) उन दो चरों को बदल देता है जो URL को पसंद नहीं: + बनकर -, / बनकर _ - और आमतौर पर पैडिंग भी छोड़ देता है। Ruby में आप इसे क्वेरी पैरामीटर्स, कूकी वैल्यूज़, API पहचान-चिह्न, YouTube-शैली वीडियो ID, और ज़ाहिर है JWT में देखेंगे।
यहाँ तीन डिकोडर एक ही इनपुट पर कैसे व्यवहार करते हैं, यह टेबल इसलिए दिख रही है क्योंकि फ़र्क़ बस वही जगह है जहाँ बग पैदा होते हैं:
| इनपुट | decode64 | strict_decode64 | urlsafe_decode64 |
|---|---|---|---|
aGVsbG8= (स्टैंडर्ड, पैडिंग के साथ) |
"hello" |
"hello" |
"hello" |
aGVsbG8 (बिना पैडिंग के) |
"hello" |
ArgumentError |
"hello" |
SGVsbG8gd29ybGQ- (आख़िरी ग्रुप में हाइफ़न) |
"Hello world" (एक बाइट कम!) |
ArgumentError |
12 बाइट्स, सही जवाब |
aGVsbG8=\n (ट्रेलिंग लाइन ब्रेक) |
"hello" |
ArgumentError |
ArgumentError |
aGVs!bG8= (ग़लती से घुसा हुआ विस्मयादिबोधक चिह्न) |
"hello" |
ArgumentError |
ArgumentError |
तीसरी पंक्ति ही वही पंक्ति है जो लोगों को काटती है। URL-safe पेलोड को स्टैंडर्ड डिकोडर से डिकोड करने पर वह ख़ामोशी से अपना आख़िरी बाइट गंवा देता है, कुछ एरर उठाए बिना, क्योंकि decode64 बस हाइफ़न को नज़रअंदाज़ कर देता है। अगर पेलोड URL से आ सकता है, तो उसे urlsafe_decode64 से डिकोड करें।
एक व्यावहारिक नोट: अगर कभी आपको URL-safe पेलोड को ऐसे संदर्भ में ले जाना पड़े जो सिर्फ़ स्टैंडर्ड वर्णमाला समझता है (कोई लाइब्रेरी, कोई दूसरी प्रणाली), तो क्लासिक इंटरऑप ट्रिक - वर्णमाला बदलें और पैडिंग खुद जोड़ें - सिर्फ़ तीन लाइनों की है:
def standardize_urlsafe(b64)
b64 = b64.tr("-_", "+/")
b64 += "=" * ((4 - b64.length % 4) % 4)
b64
end
Base64.strict_decode64(standardize_urlsafe("SGVsbG8gd29ybGQ"))
# => "Hello world"
आपको यह बहुत कम चाहिए होगा - urlsafe_decode64 आपके लिए पैडिंग पहले ही जोड़ देता है - लेकिन यह वह पैटर्न है जिसे दूसरों के कोड में पहचानना चाहिए, और वह पैटर्न जिसका सहारा तब लेना चाहिए जब दूसरे सिरे पर स्टैंडर्ड वर्णमाला ही उम्मीद हो।
कॉन्फ़िग, एनवायरनमेंट वेरिएबल्स और डेटाबेस
जब भी बाइनरी डेटा को टेक्स्ट दस्तावेज़ के अंदर बैठना पड़ता है, Base64 कॉन्फ़िग में सामने आ जाता है। .env फ़ाइल, YAML कॉन्फ़िग, या JSON सेटिंग्स ब्लाब - कोई भी कच्चे बाइट्स को सुरक्षित ढंग से रख नहीं सकता, इसलिए बाइट्स एन्कोड हो जाते हैं, और आपकी ऐप्लीकेशन का कोई न कोई हिस्सा उन्हें स्टार्टअप पर डिकोड करता है:
require "base64"
b64 = ENV.fetch("APP_LOGO")
bytes = Base64.decode64(b64)
File.binwrite("logo.png", bytes)
YAML का ज़िक्र खासतौर पर करने लायक है, क्योंकि इस फ़ॉर्मेट में बाइनरी का अपना टैग है। जब आप BINARY स्ट्रिंग को dump करते हैं, Psych उसे !binary स्केलर के रूप में लिखता है जिसमें Base64 बसा होता है, और उसे load करने पर आपके बाइट्स ताल-बताल लौट आते हैं - हाथ से एन्कोडिंग का नामोनिशान नहीं:
require "yaml"
yaml_text = YAML.dump({ "logo" => File.binread("logo.png") })
puts yaml_text.lines.first(2)
# => "---"
# => "logo: !binary |-"
data = YAML.load(yaml_text)
puts data["logo"].encoding
# => ASCII-8BIT
डेटाबेस का सरल नियम यह है: अगर आपके डेटाबेस में असली बाइनरी टाइप है, तो उसे इस्तेमाल करें। Base64-in-a-TEXT-column वह पैटर्न है जिसे आप तब अपनाते हैं जब स्टोरेज लेयर सिर्फ़ स्ट्रिंग्स की भाषा समझती है - कुछ डॉक्यूमेंट स्टोर, JSON-आकार के API, या वह लीगेसी स्कीमा जिसे आप बदल नहीं सकते - और कीमत कॉलम पर एक-तिहाई का साइज़-टैक्स है, साथ ही हर बाउंडरी पर अंदर आते समय डिकोड और बाहर जाते समय दोबारा एन्कोड करने का अनुशासन।
बड़े इनपुट, स्थिर मेमोरी
यह मॉड्यूल बफ़र-आधारित है: एक डिकोड कॉल पूरी स्ट्रिंग को एक साथ पढ़ लेता है और पूरा नतीजा लौटा देता है। स्टैंडर्ड लाइब्रेरी में कोई स्ट्रीमिंग डिकोडर नहीं है, इसलिए बड़े पेलोड्स के लिए ईमानदार सलाह है कि मेमोरी की प्लानिंग करें। अच्छी बात यह है कि डिकोडिंग हमेशा चीज़ों को छोटा ही करती है - आउटपुट इनपुट से ज़्यादा होकर भी तीन-चौथाई से ऊपर नहीं जा सकता - इसलिए आपकी बड़ी अलोकेशन सिर्फ़ इनपुट स्ट्रिंग है।
अगर कोई पेलोड उतना बड़ा है कि वह आपकी चिंता का विषय बने, तो आप उसे चार-चार चर के ग्रुप में डिकोड कर सकते हैं, क्योंकि Base64 के चार-चर वाले ग्रुप आत्म-संपूर्ण होते हैं और आख़िरी अधूरा ग्रुप अपनी पैडिंग अपने साथ लाता है:
require "base64"
def decode_in_chunks(b64)
b64.scan(/.{1,4}/).reduce("") do |result, group|
result + Base64.strict_decode64(group)
end
end
restored = decode_in_chunks(Base64.strict_encode64("a" * 1_000_000))
puts restored.length
# => 1000000
यह साफ़, बिना-लपेटे इनपुट पर काम करता है - वही नियम जो strict_decode64 ज़ोर देता है - क्योंकि अकेला ट्रेलिंग ग्रुप तभी वैध है जब उसकी पैडिंग मौजूद हो। सचमुच विशाल फ़ाइलों के लिए, कई गिगाबाइट के आर्काइव और इसी तरह की चीज़ों के लिए, पैटर्न यह है कि फ़ाइल को स्लाइस-स्लाइस पढ़ें, हर स्लाइस डिकोड करें, और बाइट्स को डिस्क पर स्ट्रीम करें, ताकि एक बार में मेमोरी में सिर्फ़ एक स्लाइस हो।
टर्मिनल के लिए वन-लाइनर
शेल में चीज़ें डिकोड करने के लिए स्क्रिप्ट फ़ाइल की ज़रूरत नहीं है। Ruby मॉड्यूल को बिना रुके require कर सकता है:
ruby -rbase64 -e 'puts Base64.decode64(ARGV[0])' "aGVsbG8gd29ybGQ="
# => hello world
और फ़ाइलों के लिए, पेलोड के बजाय फ़ाइल का पाथ पास करें:
ruby -rbase64 -e 'print Base64.decode64(File.read(ARGV[0]))' payload.b64 > payload.bin
यहाँ दो फ़सें बसी हुई हैं। पहली, अगर आप echo या किसी भी टेक्स्ट कमांड के ज़रिए पाइप करते हैं, तो एक ट्रेलिंग लाइन ब्रेक भी साथ चढ़ जाता है, और strict_decode64 उस पर एरर उठा देगा - decode64 इस्तेमाल करें, या इनपुट को chomp करें:
echo "aGVsbG8gd29ybGQ=" | ruby -rbase64 -e 'print Base64.strict_decode64(STDIN.read.chomp)'
दूसरी, बाइनरी आउटपुट के लिए print को ही रखें, puts की जगह, क्योंकि puts अपनी तरफ़ से एक लाइन ब्रेक जोड़ देता है और आपकी बहाल की गई फ़ाइल के अंत को ख़राब कर देगा।
वे गड्ढे जिनमें Ruby डेवलपर्स वाकई गिरते हैं
- decode64 कभी एरर नहीं उठाता। कूड़ा अंदर, कूड़ा बाहर। अगर आपका इनपुट अविश्वसनीय है और आप ख़ामोशी से ख़राब बाइट्स स्वीकार कर लेते हैं, तो वह बग डिकोड वाली लाइन पर नहीं, हफ़्तों बाद किसी ख़राब फ़ाइल में सामने आएगा। जो भी आपने खुद एन्कोड नहीं किया, उसके लिए डिफ़ॉल्ट रूप से सख़्त डिकोडर रखें।
- strict_decode64 और ट्रेलिंग लाइन ब्रेक। टेक्स्ट फ़ाइलें, echo पाइप और कॉपी-पेस्ट - सब न्यूलाइन के साथ ख़त्म होने के शौकीन हैं, और सख़्त डिकोडर उस पर
ArgumentErrorउठा देता है। पहले इनपुट कोchompकरें - या उसे बाइनरी मोड में पढ़कर लाइन ब्रेक हटा लें। - एन्कोडिंग का कदम भूल जाना। डिकोड की गई स्ट्रिंग तब तक BINARY रहती है जब तक आप कुछ और न कह दें। नतीजे को टेक्स्ट की तरह मानने से पहले उसे UTF-8 पर force करें (और वैलिडिटी जाँचें), वरना उसे UTF-8 स्ट्रिंग्स के साथ मिलाने की ही पल भर में आपको मोजिबैके और
Encoding::CompatibilityErrorमिलेगा। - BINARY की तुलना UTF-8 से। वही बाइट्स, अलग टैग, और
==false कहता है - जब तक स्ट्रिंग बस-बस शुद्ध ASCII न हो। तुलना करने से पहले लेबल बदल लें। - URL-safe इनपुट ग़लत डिकोडर से।
decode64हाइफ़न और अंडरस्कोर को ख़ामोशी से गिरा देता है, इसलिए URL-safe पेलोड एक बाइट कम और ख़राब लौट आता है, एक एरर के बिना भी।urlsafe_decode64इस्तेमाल करें। - पैडिंग के बाद का डेटा अदृश्य है।
decode64पहले=पर रुक जाता है। MIME के लिए शानदार, लेकिन ऐसे पेलोड को पकड़ने के लिए बिल्कुल बुरा, जो कटकर छोटा किया गया हो और फिर किसी दूसरे टूल ने उसे दोबारा पैड कर दिया हो। - ग़ैर-कैनोनिकल पैडिंग ख़ामोशी से स्वीकार होती है।
QR==जैसी स्ट्रिंग में वे पैड बिट्स होते हैं जिन्हें सही एन्कोडर ने शून्य कर दिए होते;decode64उसे खुशी-खुशी डिकोड करता है, जबकिstrict_decode64उसे रिजेक्ट करता है। कभी आपको यह भी नहीं बताया जाएगा कि आपका एन्कोडर झूठ बोल रहा था। - Windows पर टेक्स्ट मोड में फ़ाइलें पढ़ना लाइन एंडिंग्स को आप उन्हें देखने से पहले ही फिर से लिख देता है।
.b64फ़ाइलें तब बाइनरी मोड में पढ़ें जब आप उन्हें वैलिडेट करने वाले हों।
डिकोड पक्ष के लिए अच्छी आदतें
- डिकोडर का चुनाव डेटा के स्रोत से करें: जो कुछ भी अविश्वसनीय है, उसके लिए
strict_decode64(औरArgumentErrorको rescue करके ग़लत इनपुट की शाखा बनाएँ), URL-जन्म के पेलोड्स के लिएurlsafe_decode64, औरdecode64केवल उन फ़ॉर्मेट के लिए जो सचमुच शालीन हों, जैसे MIME बॉडी। - जैसे ही बाइट्स डिकोड हों, उनकी पहचान तय करें: बाइनरी (ASCII-8BIT रखें,
File.binwriteसे लिखें) या टेक्स्ट (UTF-8 परforce_encoding, फिर इस्तेमाल से पहलेvalid_encoding?)। - डिकोड करके भरोसा कभी न करें। JWT पेलोड बस इसलिए पढ़ा जा सकता है क्योंकि वह Base64 है; असली है या नहीं, यह सिग्नेचर तय करती है। कॉन्फ़िग फ़ाइल में कोई Base64 स्ट्रिंग डेटा है, सबूत नहीं।
- जब आप वेलिडेटर लिखें, तो उन्हें बोरिंग केसेस पर ट्राई करें: ख़ाली स्ट्रिंग, बिना-पैडिंग वाला इनपुट, लपेटा हुआ इनपुट, URL-safe इनपुट, और ग़लत पैडिंग। बस यही वे केसेस हैं जो तीन डिकोडरों को एक-दूसरे से अलग करते हैं।
Ruby में Base64 की छोटी सी कथा
Base64 मॉड्यूल Ruby की स्टैंडर्ड लाइब्रेरी का हिस्सा पंद्रह साल से ज़्यादा समय से है, और इसका शिप होने का तरीका आपके सोचने से ज़्यादा बदल चुका है:
- 2008, Ruby 1.8.7: मॉड्यूल
encode64,decode64के साथ शिप होता है, साथ में दो ऐसे मेथड भी जो अब मौजूद नहीं हैं -b64encode(चुनी हुई लाइन लंबाई पर लपेटता है) औरdecode_b(RFC 2047 ईमेल हेडर डिकोडिंग)। पुरानी किताबें और कुछ पुराने gems आज भी इनका ज़िक्र करते हैं, और आज उनमें से किसी को कॉल करनाNoMethodErrorहै। - 2009, 1.9 लाइन:
strict_encode64,strict_decode64,urlsafe_encode64औरurlsafe_decode64आते हैं, और दो लीगेसी मेथड की सेवानिवृत्ति हो जाती है (1.9.1 ने दोनों बदलाव जनवरी 2009 में ही शिप कर दिए थे)। - 2015, Ruby 2.3:
urlsafe_encode64कोpadding:कीवर्ड मिलता है, जिससे टोकन और URL के लिए बिना-पैडिंग वाला आउटपुट निकाला जा सकता है। - 2020, Ruby 3.0: base64 को स्टैंडर्ड लाइब्रेरी से अलग करके अपना gem बना दिया जाता है, वर्ज़न 0.1.0,
ruby/base64रिपॉज़िटरी के तहत। यह डिफ़ॉल्ट gem के तौर पर शिप होता है, इसलिएrequire "base64"बस काम करता रहता है। - 2023, Ruby 3.3: वर्ज़न 0.2.0 में
Base64::VERSIONऔर एक बहुत ज़्यादा भरपूर दस्तावेज़ीकरण समूह जुड़ता है। - 2024, Ruby 3.4: gem को डिफ़ॉल्ट gem की जगह बंडल्ड gem में पुनर्वर्गीकृत कर दिया जाता है। व्यावहारिक परिणाम: Ruby 3.4 और बाद में, Bundler-आधारित प्रोजेक्ट में
gem "base64"को अपने Gemfile में लिखें (याgem install base64से इंस्टॉल करें)। - 2025, Ruby 4.0: वर्ज़न 0.3.0 आती है, जिसमें RBS टाइप सिग्नेचर समेत और भी रख-रखाव काम होता है।
इस पूरे सफ़र के दौरान एक बात कभी नहीं बदली: मॉड्यूल कोर pack और unpack टेम्पलेट पर बैठे कुछ दर्जन लाइनों का शुद्ध Ruby है। कोई C एक्सटेंशन नहीं, कोई डिपेंडेंसी नहीं, बिल्कुल कुछ भी बनाने को नहीं - और rubygems.org पर डाउनलोड संख्या सैकड़ों मिलियन की।
जिज्ञासुओं के लिए Ruby के मज़ेदार तथ्य
- मॉड्यूल का डिकोडिंग पक्ष दो एक-लाइन के मेथड बॉडीज़ से बना है -
str.unpack1("m")औरstr.unpack1("m0")- साथ में urlsafe वेरिएंट, जो सख़्त वाले पर सिर्फ़ एक अक्षर-बदलाव और पैडिंग-सुधार है। आप require हटाकर यह खुद भी लिख सकते हैं। - Ruby की खुद
Net::HTTPBasic auth के लिएBase64मॉड्यूल का इस्तेमाल भी नहीं करती - वह सीधेpackटेम्पलेट को कॉल करती है:["user:pass"].pack("m0")। - Rails के साइन्ड और एन्क्रिप्टेड कूकीज़ अंदरों-अंदर Base64 स्ट्रिंग्स हैं: ActiveSupport का मैसेज कोडेक साधारण कूकीज़ के लिए
strict_encode64चुनता है और URL-safe साइन्ड ID के लिएurlsafe_encode64padding: falseके साथ। शायद आपने पहले से ही एक को डिकोड कर लिया है, बिना जाने के। - हर डिजस्ट क्लास में
base64digestमेथड है -Digest::SHA256.base64digest("hello")- एक वन-लाइनर, उन चेकसम के लिए जो टेक्स्ट में रहने चाहिए। - YAML का
!binaryटैग Base64 है। Psych से BINARY स्ट्रिंग डंप करें और यह फ़ॉर्मेट ख़ामोशी से एन्कोडिंग आपके लिए कर देता है। decode64को इसकी परवाह नहीं है कि आपकी लाइनें 60, 64 या 76 चर की हों, या एक भारी-भरकम लाइन हो।mटेम्पलेट लाइन ब्रेक छोड़ देता है, इसलिए लपेटे और बिना-लपेटे इनपुट एक जैसा ही डिकोड होते हैं।
आगे बढ़ते रहें
अब आपके पास पूरा डिकोडिंग टूलकिट है: MIME-आकार के ब्लाब्स के लिए शालीन पाठक, हर अविश्वसनीय चीज़ के लिए सख़्त द्वारपाल, टोकन और लिंक्स के लिए URL-safe राजनयिक, और वह एन्कोडिंग का कदम जो नतीजे के बाइट्स को ऐसे टेक्स्ट में बदलता है जो Ruby आपको इस्तेमाल करने दे। उल्टा दिशा - कि Ruby के तीन एन्कोडरों में से किसको अपने बाइट्स देना है, और वर्णमाला, पैडिंग और लाइन ब्रेक पर कब्ज़ा कैसे रखना है - अपने साथ एक पूरा समूह आश्चर्य लाती है, शुरुआत किसी ने नहीं मांगी उस ट्रेलिंग न्यूलाइन से। उस पक्ष की गली को Base64 एन्कोडिंग आर्टिकल में विस्तार से कवर किया गया है, नीचे लिंक है।
अंतिम अपडेट: 2026-09-08
संबंधित लेख: Ruby में Base64 एन्कोडिंग: एक सम्पूर्ण गाइड