Base64-codering in Ruby: een complete gids
Je data heeft een bestemming die de huidige vorm niet accepteert. Een afbeelding die in een JSON-document moet leven. Een token dat een URL moet oversteken. Een bijlage die een protocol moet overleven dat is ontworpen voor 7-bit-tekst. Een geheim dat in een omgevingsvariabele moet zitten zonder de quoting te breken. Op elk van deze plekken staat er halverwege iets op af om je binaire data te vernietigen - en de oplossing heeft een naam: Base64.
In Ruby zit de hele klus in één module die met de taal meegeleverd wordt. Drie encoders, niets te installeren, en uitvoer die je tot op het exacte teken kunt voorspellen voordat je de code ooit draait. Die voorspelbaarheid is de helft van het verhaal die de meeste gidsen overslaan, want coderen is waar de verrassingen hun tol innen: een regeleinde achteraan gluipt je JSON binnen, een regeleinde waar je niet om vroeg hakt een token in twee, en één verkeerde alfabetkeuze verpest een URL. Deze gids loopt alle drie de encoders door, de wiskunde van de uitvoer, en elke payload die een Ruby-ontwikkelaar daadwerkelijk encodeert, zodat de verrassingen ophouden verrassingen te zijn.
Even herhalen voordat we beginnen: Base64 schrijft data om in stappen van drie bytes: elke stap levert vier tekens uit een alfabet van 64 symbolen, met een of twee =-tekens vulling wanneer de invoer niet exact door drie deelbaar is - en daarom komt de uitvoer ook zo'n derde groter uit dan de invoer. De startpagina van deze site dekt het formaat grondig, dus houdt dit artikel de formatpraat bij één adem en gaat direct aan de slag.
Welke encoder heb je nodig?
Ruby geeft je drie encoders, en de keuze ertussen is een quiz van drie vragen: mag de uitvoer regeleinden bevatten? Mag hij + of / bevatten? Mag hij vulling bevatten? Hier is het hele opstel:
| Encoder | Uitvoervorm | Regeleinden | Vulling | Wanneer je ernaar grijpt |
|---|---|---|---|---|
Base64.strict_encode64(bin) |
één regel, standaardalfabet | nooit | altijd aanwezig | JSON, tokens, API's, bestanden - de veilige standaard |
Base64.encode64(bin) |
meerdere regels, standaardalfabet | na elke 60 tekens, plus er een achteraan | altijd aanwezig | e-maillichamen en andere regel-georiënteerde tekstprotocollen |
Base64.urlsafe_encode64(bin, padding: true) |
één regel, koppelteken-underscore-alfabet | nooit | aan jou, standaard aan | alles wat in een URL, cookie of identifier belandt |
Besliss je onder tijdsdruk, dan is het korte antwoord: strict_encode64 als standaard, urlsafe_encode64 wanneer het resultaat binnen een URL zal reizen, en encode64 alleen wanneer de ontvangende kant een tekstprotocol is dat korte regels wilt. Alles hieronder legt uit waarom, en waar elke keuze je stilletjes iets kost.
strict_encode64: het werkpaard
Base64.strict_encode64 is de encoder die je in het veruit grootste deel van je code daadwerkelijk zult gebruiken. Hij produceert exact één regel uitvoer, altijd met de correcte vulling, uit het standaardalfabet:
require "base64"
Base64.strict_encode64("hello world")
# => "aGVsbG8gd29ybGQ="
Base64.strict_encode64("s")
# => "cw=="
En omdat het algoritme deterministisch is, kun je de exacte lengte van de uitvoer voorspellen uit de invoer - geen gokken, geen off-by-one-bugs in je database-kolommen. De tabel hieronder is de hele rekenkunde:
| Invoerlengte | Uitvoerlengte | Vulling aan het einde |
|---|---|---|
| 3n bytes (exact deelbaar) | 4n tekens | geen |
| 3n + 1 bytes | 4n + 4 tekens | twee = |
| 3n + 2 bytes | 4n + 4 tekens | één = |
Dus 11 bytes worden 16 tekens, 100 bytes worden 136, en een bestand van 1 megabyte wordt zo'n 1,33 megabyte aan tekst. Die derde-groei is de toelating die je betaalt voor elke Base64-payload die je verstuurt, en het is het getal dat je in je achterzak moet houden wanneer een kolom, een cache of een API-rate-limit begint te knellen.
Base64.strict_encode64("123")
# => "MTIz" 3 bytes binnen, 4 tekens buiten
Base64.strict_encode64("1234")
# => "MTIzNA==" 4 bytes binnen, 8 tekens buiten, twee vultekens
Base64.strict_encode64("12345")
# => "MTIzNDU=" 5 bytes binnen, 8 tekens buiten, één vulteken
encode64: degene die regeleinden toevoegt
Base64.encode64 is de klassieker, en het heeft één gedrag dat meer dan één middag heeft gekost: het wikkelt zijn uitvoer af. Elke 60 tekens begint het een nieuwe regel, en het eindigt altijd met een regeleinde achteraan:
Base64.encode64("hello world")
# => "aGVsbG8gd29ybGQ=\n"
Base64.encode64("*" * 46)
# => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"
De omwikkeling is geen bug - het is een feature geërfd van de oorspronkelijke thuisbasis van de methode in de MIME-wereld, waar lange regels een protocolovertreding waren. Rubys mail-gem leunt er opzettelijk op - zijn Base64-encoder draagt zelfs een commentaar in de zin dat Rubys regelomwikkeling de uitvoer binnen de SMTP-regellengtelimieten houdt. Als je e-maillichamen encodeert, doet encode64 je een plezier.
Maar in elke andere context is de omwikkeling een belasting. Het meest voorkomende ongeluk is een JSON-document waarin een Base64-waarde plotseling over twee regels loopt:
payload = { "logo" => Base64.encode64(File.binread("logo.png")) }
puts payload.to_json
# de logo-waarde draagt regeleinden waar niemand om vroeg
En de kleinere tweeling van dezelfde bug is het regeleinde achteraan op korte tekenreeksen: Base64.encode64("s") geeft "cw==\n" terug, dus een token dat je in een URL plakt of vergelijkt met een verwachte waarde faalt om redenen waar je twintig minuten over zult zoeken. De genezing is een strip - maar de betere genezing is strict_encode64, die nooit een enkel teken toevoegt dat je niet hebt verdiend. Er is ook één charmante asymmetrie die het kennen waard is: een lege invoer produceert een lege tekenreeks zonder regeleinde achteraan, dus Base64.encode64("") is gewoon "".
urlsafe_encode64: het link-veilige alfabet
Twee tekens in het standaardalfabet veroorzaken problemen overal waar een URL-parser meekijkt: + (een spatie in query-strings) en / (een pad-scheidingsteken). RFC 4648 loste dit op met een ruil - - neemt de plaats van + in, _ de plaats van / - en Ruby implementeert dit in Base64.urlsafe_encode64:
Base64.urlsafe_encode64("\xfb\xef\xbe".b)
# => "----"
Base64.urlsafe_encode64("\xff\xff\xff".b)
# => "____"
Die twee voorbeelden zijn het alfabet in actie: dezelfde bytes die de standaardencoder weergeeft als ++++ of //// komen eruit als ---- en ____, tekens die URLs, paden, bestandsnamen en velden van formulieren overleven zonder enige percent-codering. De uitvoer is één regel, net zoals strict_encode64.
De enige optie van de methode is het padding:-keyword, toegevoegd in Ruby 2.3, en dat is degene om te kennen. De specificatie voor JSON Web Tokens eist base64url zonder vulling, en dat doen veel andere token-schemata ook:
Base64.urlsafe_encode64("*")
# => "Kg=="
Base64.urlsafe_encode64("*", padding: false)
# => "Kg"
Met vulling uit, verschuift de lengtewiskunde: 3n + 1 bytes leveren nu 4n + 2 tekens op en 3n + 2 bytes leveren 4n + 3 op. De decoderzijde komt eraan toe - Rubys urlsafe_decode64 voegt de ontbrekende vulling zelf toe - dus is ongevulde uitvoer veilig om uit te geven, maar is gevulde uitvoer de vriendelijker standaard wanneer de andere kant een strikte RFC 2045-lezer is. Eén waarschuwing: zet vulling uit alleen wanneer een specificatie het vraagt. Het spaart één of twee tekens en koopt je een klasse decoderklachten in.
Wat Ruby daadwerkelijk encodeert: tekenreeksen zijn bytes
Voor de gebruiksscenario's, één Ruby-specifiek feit dat alles beïnvloedt: een Ruby-tekenreeks is een reeks bytes met een coderingslabel, en de encoders kijken alleen naar de bytes. Het label vertelt Ruby hoe de tekenreeks te tonen en te vergelijken; het verandert niet wat er wordt geëcodeerd:
require "base64"
s = "h\u{e9}llo"
puts s.encoding
# => UTF-8
puts s.bytes.length
# => 6 de accentletter e is twee bytes
Base64.strict_encode64(s)
# => "aMOpbGxv"
Dat is de val achter "waarom is mijn uitvoer langer dan ik verwachtte": de tekenreeks die je typt is in karakters meestal korter dan in bytes, en Base64 rekent per byte. De omgekeerde richting is net zo stil - een ongeldige UTF-8-tekenreeks wordt zonder klacht geëcodeerd, omdat de encoder niets heeft om te valideren:
broken = "h\u{e9}llo".b.force_encoding("UTF-8")
broken.setbyte(1, 0xFF)
puts broken.valid_encoding?
# => false
Base64.strict_encode64(broken)
# => wat base64, geen fout, bytes zijn bytes
Voor écht binair sla je de textemachine helemaal over en bouw je bytes met pack of lees je ze met File.binread. Een bevredigend voorbeeld is de PNG-handtekening - de acht bytes die elk PNG-bestand op aarde openen:
png_magic = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
Base64.strict_encode64(png_magic)
# => "iVBORw0KGgo="
JWT's: data ondertekenen die ook leesbaar is
JSON Web Tokens zijn de meest prominente consument van Rubys URL-veilige encoder. Een token is drie base64url-segmenten die met punten aan elkaar zijn gekoppeld - header, payload, handtekening - en de specificatie is expliciet: het alfabet moet het URL-veilige zijn, en de vulling moet uit. De jwt-gem doet het hele werk:
# In het Gemfile: gem "jwt"
require "jwt"
token = JWT.encode(
{ sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
"my-secret-key",
"HS256"
)
puts token
# => eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIi...
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts header
# => {"alg"=>"HS256"}
Je kunt ook de Base64-laag aan het werk zien binnen het token, want de segmenten zijn gewoon base64url van JSON:
require "base64"
require "json"
payload_json = JSON.generate({ "sub" => "1234567890", "name" => "Alice" })
segment = Base64.urlsafe_encode64(payload_json, padding: false)
puts segment
# => eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0
Twee regels horen bij dit gebruiksscenario. Bouw nooit zelf een JWT in productie - de handtekening is wat een token iets meer maakt dan een bekentenis - en pin bij het decoderen met de gem het algoritme vast in de opties-hash zoals hierboven getoond, zodat de eigen header van het token de verificatiemethode niet voor jou kiest.
HTTP Basic auth: de header bouwen
De oudste manier om in HTTP te zeggen "wie ben ik" is nog steeds de simpelste: encodeer de aanmeldgegevens met Base64, zet ze na het woord Basic, en verstuur de header. Bouwen in Ruby is één regel:
require "base64"
credentials = Base64.strict_encode64("alice:s3cr3t!")
puts "Basic #{credentials}"
# => Basic YWxpY2U6czNjcjN0IQ==
De standaardbibliotheek van Ruby doet dit exact voor je in Net::HTTP, en roept daarvoor de core pack-template rechtstreeks aan - ["user:pass"].pack("m0") is waar basic_auth onder de motorkap op neerkomt:
require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==
En de beveiligingskanttekening, één keer gezegd zodat het gezegd is: Base64 is een vertaler, geen slot. De aanmeldgegevens in een Basic auth-header zijn leesbaar voor iedereen die het packet kan lezen. Deze header is alleen aanvaardbaar via HTTPS, waar het transport het echte werk doet.
Data-URI's: inline afbeeldingen en fonts
Een data-URI is het antwoord van het web op "ik wil deze afbeelding zonder apart bestand": een media-type, het woord base64, een komma, en de bytes. Zo verschepen single-file HTML-demos hun logos, zo verbergen favicons zich in CSS, en zo kan een gegenereerde afbeelding volledig in een template-string leven:
require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
css = "background-image: url(#{data_uri});"
puts css.length
# => jouw stylesheet, minus één HTTP-request
Gebruik hier strict_encode64 - de payload is één schone regel, geen omwikkeling, geen regeleinde. En let op de grootte: de afbeelding die je inline zet groeit met ongeveer een derde, dus data-URI's stralen voor kleine assets (favicons, logos, icon fonts) en bloaten voor grote. Een hero-afbeelding van twee megabyte wordt 2,7 megabyte van je HTML-document, en je gebruikers voelen dat bij hun eerste 4G-scroll.
E-mail: waar Base64 vandaan komt
Elk ander gebruiksscenario in dit artikel is een nazaat van deze. SMTP is in de jaren tachtig ontworpen voor korte regels van 7-bit-tekst, met andere woorden: het kon geen JPEG aan. De oplossing - Privacy-Enhanced Mail, daarna MIME in 1993 - was om binair om te schrijven als tekst met een alfabet van 64 symbolen, en dat is precies het formaat dat je vandaag gebruikt. De littekens zijn nog steeds zichtbaar in Rubys uitvoer: encode64 wikkelt af op 60 tekens - een breedte die geen specifiek protocol dient, zoals je later zal zien, maar kort genoeg om e-mail beleefd te houden.
In de praktijk laat je de mail-gem het MIME-werk doen. Knip een binair bestand erbij en de gem kiest de Base64-encoder, wikkelt de regels af, en schrijft de headers:
# In het 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
# het bijlage-deel draagt Content-Transfer-Encoding: base64
Niet-ASCII-tekst in headers krijgt dezelfde behandeling in een iets ander kostuum: RFC 2047-gecodeerde woorden, die Base64 verpakken in een tekenset-label tussen vraagtekens, zoals =?UTF-8?B?w7wgc2VjcmV0cw==?=. Als je ze ooit handmatig bouwt of parst, is de Base64 erin het gewone type, gedecodeerd met decode64 en daarna opnieuw gelabeld met de tekenset die het woord declareert.
PEM-pantsering voor sleutels en certificaten
Sleutels en certificaten dragen PEM-pantsering, en het pantser is Base64 met een frame: een BEGIN-regel, de gecodeerde bytes in regels van 64 tekens, en een END-regel. Als je ooit een PEM-bestand moet produceren uit ruwe DER-bytes, is de constructie een omwikkeling in twee stappen:
require "base64"
der_bytes = File.binread("server.der")
body_lines = Base64.strict_encode64(der_bytes).scan(/.{1,64}/)
pem = (["-----BEGIN PRIVATE KEY-----"] + body_lines +
["-----END PRIVATE KEY-----"]).join("\n") + "\n"
File.write("server.key", pem)
Twee opmerkingen. Eerst: je zult dit bijna nooit nodig hebben, omdat de openssl-gem PEM voor je schrijft (key.to_pem), en het label tussen de BEGIN- en END-regels moet overeenkomen met wat erin zit - het fout halen levert een bestand op dat elk tool op internet weigert. Ten tweede: de regellengte hier is 64, de klassieke PEM-breedte; Rubys encode64 wikkelt in plaats daarvan af op 60, en elke degelijke PEM-parser negeert regellengtes helemaal, dus beide breedtes decoderen prima.
Bestanden: de .b64-conventie
Het meest voorkomende bestandsformaat in de Base64-wereld is een gewoon tekstbestand met een .b64- (of .base64-)extensie dat één gecodeerde payload bevat - denk eraan als "het bestand, maar veilig om overal in te plakken". Eén produceren uit Ruby is een one-liner:
require "base64"
File.write("payload.b64", Base64.strict_encode64(File.binread("payload.bin")))
puts File.size("payload.b64")
# => ongeveer 1,33 keer de oorspronkelijke grootte
Gebruik strict_encode64 zodat het bestand één schone regel bevat - dat is de conventie die de meeste decodertools (en Rubys strikte decoder) verwachten. Het teruglezen is het spiegelbeeld: lees, decodeer, en schrijf de bytes binair weg zodat er niets ze onderweg muteert:
encoded = File.read("payload.b64")
bytes = Base64.strict_decode64(encoded)
File.binwrite("restored.bin", bytes)
Als je .b64-bestanden van tools komen die regels omwikken - sommige base64-CLI-varianten doen dat - strip dan de regeleinden vóór de strikte decode, of gebruik de tolerante decoder, die ze gratis overslaat.
Config-bestanden, omgevingsvariabelen en databases
Wanneer binair data maar in een tekstdocument moet leven, is Base64 de brug. Het patroon komt in drie plaatsen voor, met kleine variaties.
Omgevingsvariabelen en .env-bestanden kunnen ruwe bytes niet houden, dus de bytes worden gecodeerd voordat ze de machine verlaten die ze heeft:
require "base64"
# ergens waar je de app provisioneert
ENV["APP_LOGO"] = Base64.strict_encode64(File.binread("logo.png"))
# ergens waar de app opstart
b64 = ENV.fetch("APP_LOGO")
File.binwrite("logo.png", Base64.decode64(b64))
YAML heeft een native binaire type, en Psych handelt de Base64 voor je af - een BINARY-tekenreeks gedumpt naar YAML komt eruit als een !binary-scalar en laadt byte-identiek terug:
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
In databases is de vraag het opslagtype, niet de codering. Als je database een echte binaire kolom heeft - BLOB, BYTEA, VARBINARY - gebruik die dan, en laat de driver de bytes dragen. Base64-in-een-TEXT-kolom is het patroon voor wanneer de opslaglaag alleen strings spreekt: sommige document stores, JSON-achtige API's, of een legacy-schema dat je niet kunt veranderen. De prijs is de groottebelasting van een derde op de kolom, en de discipline om onderweg naar binnen te coderen en onderweg naar buiten te decoderen, bij elke grens, zonder uitzondering.
Checksums die als tekst reizen
Hashes zijn binair, maar checksums reizen grotendeels als tekst: bestandsintegriteitslijsten, cache-sleutels, vingerafdrukken, logregels. Elke digest-klasse van Ruby heeft een base64digest-methode die de codering in één aanroep doet:
require "digest"
Digest::SHA256.base64digest("hello")
# => "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
De uitvoer is gevulde standaard-Base64 - hetzelfde wat je zou krijgen van Base64.strict_encode64(Digest::SHA256.digest("hello")) - dus is het veilig om op te slaan, te vergelijken en te plakken. De enige beslissing is consistentie: een checksumlijst gegenereerd met Base64 moet gecontroleerd worden tegen Base64-uitvoer, en hex- en Base64-weergaves van dezelfde hash zijn verschillende tekenreeksen, dus kies er één en houd je eraan.
Grote dingen coderen in kleine chunks
Net als de decoders zijn de encoders buffer-gebaseerd: ze lezen de hele invoer en geven de hele uitvoer. Er zit geen streaming encoder in de standaardbibliotheek, dus voor grote payloads is het plan geheugen, en is er een aangename symmetrie in de wiskunde. Coderen groeit je data met een derde, dus is de uitvoer - niet de invoer - je grootste allocatie, en bij een bestand van 1 gigabyte moet je zo'n 1,33 gigabyte aan tekst voor je neus verwachten.
Als dat te veel is om tegelijk te houden, kun je in chunks coderen, want het Base64-alfabet is zelf-synchroniserend op drie-byte-grenzen: codeer elke 3-byte-slice onafhankelijk en de concatenatie is identiek aan het coderen van het geheel:
require "base64"
require "securerandom"
bin = SecureRandom.random_bytes(10_001)
whole = Base64.strict_encode64(bin)
chunked = bin.scan(/.{1,3}/m).map { |slice| Base64.strict_encode64(slice) }.join
puts chunked == whole
# => true
Dezelfde trick geeft je een zelfgemaakte regelomwikkelaar die exact overeenkomt met encode64: 45 bytes coderen altijd exact naar 60 tekens, dus de invoer op 45 bytes snijden en de stukken met regeleinden verbinden reproduceert de klassieke MIME-uitvoer, regel voor regel, met telkens maar één slice in het geheugen:
def wrap_like_encode64(bin)
lines = bin.scan(/.{1,45}/m).map { |slice| Base64.strict_encode64(slice) }
lines.join("\n") + "\n"
end
bin = SecureRandom.random_bytes(10_001)
puts wrap_like_encode64(bin) == Base64.encode64(bin)
# => true
Vanuit de command line
Coderen heeft ook geen scriptbestand nodig. De one-liner-vorm leest een bestand en schrijft de Base64 ervan naar stdout:
ruby -rbase64 -e 'print Base64.strict_encode64(File.binread(ARGV[0]))' payload.bin > payload.b64
En de pipe-vorm leest stdin, en zo wikkel je een stroom bytes van een ander willekeurig commando:
some_command | ruby -rbase64 -e 'print Base64.strict_encode64(STDIN.read)'
Houd print in beide - een dwalend puts voegt een regeleinde aan je Base64 toe, en voor strict_encode64-uitvoer maakt dat van een schone token een kapotte. Dezelfde vuistregel als aan de decoderingszijde: als de volgende consument van je uitvoer strikt is, mag er niets anders dan de Base64 zelf meereizen.
De valkuilen die Ruby-ontwikkelaars extra bytes kosten
- Het regeleinde achteraan in JSON.
Base64.encode64eindigt elk niet-lege resultaat met een regeleinde, dus een waarde die een schone token zou moeten zijn, arriveert in je JSON met een verrassing\naan het eind. Gebruikstrict_encode64voor alles wat in één regel wordt opgeslagen, vergeleken of verzonden. - De 60-teken-omwikkeling in tokens en URLs. Dezelfde methode wikkelt lange uitvoer af naar meerdere regels. Een omwikkelde tekenreeks in een URL is twee URLs, en een omwikkelde token is een kapotte. Opnieuw:
strict_encode64, ofstrip/deletede regeleinden als je vastzit opencode64-uitvoer. - Plus en slash in URLs. Standaard-Base64 in een query-string betekent percent-coderen van
%2B,%2Fen%3Donderweg naar buiten en hopen dat de andere kant ze decodeert.urlsafe_encode64haalt het probleem bij de bron weg. - Vulling op de verkeerde plek. JWT's en andere token-schemata willen vulling uit; MIME-lezers kunnen misschien niet omgaan met ontbrekende vulling. Geef
padding: falseuit alleen waar een specificatie het vraagt, en weet aan welke kant van dat hek elk van je consumenten zit. - Karakters zijn geen bytes. Een tekenreeks van vijf karakters met één accentletter is zes bytes in UTF-8, en de uitvoerlengte-wiskunde draait op bytes. Wanneer het gecodeerde resultaat "te lang" is, tel dan bytes, niet karakters.
- De derde-belasting in schema-ontwerp. Een BLOB van 16 KB wordt zo'n 22 KB Base64-tekenreeks in een TEXT-kolom. Dimensioneer je kolommen, caches en API-payloads voor de gecodeerde vorm, niet de binaire vorm.
- Twee alfabetten, twee verschillende tekenreeksen. Dezelfde bytes coderen anders in het standaard- en het URL-veilige alfabet, dus is een gecodeerde waarde alleen vergelijkbaar met een andere waarde uit hetzelfde alfabet. Vergelijk of meng ze nooit.
- Base64 is geen slot. Een geheim coderen maakt het niet geheim. Iedereen met de tekenreeks heeft je data; Base64 controleert alleen hoe de bytes eruitzien, niet wie ze kan lezen.
Gewoontes die bytes en bugs besparen
- Maak van
strict_encode64je standaard. Wissel naarurlsafe_encode64zodra de uitvoer in een URL, cookie of identifier zal leven, en naarencode64alleen wanneer de bestemming een regel-georiënteerd tekstprotocol is zoals e-mail. - Houd het alfabet consistent tussen encoder en decoder aan beide kanten van de draad. De meest voorkomende "Base64 is kapot"-bug is een producent van het standaardalfabet die een URL-veilige consument tegenkomt, of andersom.
- Geef de encoders de bytes die je daadwerkelijk wilt coderen:
File.binreadvoor bestanden,packvoor geconstrueerd binair, en een UTF-8-tekenreeks wanneer de tekenreeks de data is. De encoder twijfelt niet aan je keuzes - het telt gewoon bytes. - Budgetteer de groei. Altijd wanneer een Base64-tekenreeks een grens oversteekt naar een container met een grootte, vermenigvuldig met 4/3 en voeg wat marge toe voor vulling.
- Gebruik Base64 voor draagbaarheid, nooit voor geheimhouding. Als het doel is de data privé te houden, is het gereedschap versleuteling, en is Base64 alleen wat je daarna met de ciphertext doet.
Hoe Base64 een gem werd
Het grootste deel van zijn leven was de Base64-module gewoon een bestand in de standaardbibliotheek, net als veel van Rubys oudste helpers. De strikte en URL-veilige methoden kwamen bij het oorspronkelijke paar tijdens de 1.9-ontwikkelinglijn - de hele base64-bibliotheek, met alle vier methoden, werd in september 2008 aan trunk toegevoegd en verscheen voor het eerst in 1.9.1 (2009), en het padding:-keyword arriveerde met Ruby 2.3 in 2015. Alles aan de API die je vandaag ziet was toen al vastgelegd - de rest van het verhaal gaat over hoe de module wordt uitgebracht.
In 2020, met Ruby 3.0, begon het core-team standaardbibliotheken in hun eigen gems te extraheren, en base64 werd er een van: versie 0.1.0, onderhouden in de ruby/base64-repository door de core-bijdragers. Hij werd uitgebracht als default gem - meegeleverd met Ruby en altijd beschikbaar, zodat require "base64" zonder ceremonie bleef werken. Versie 0.2.0 volgde met Ruby 3.3 in 2023, met de Base64::VERSION-constant en een veel rijkere documentatiereeks.
Daarna tekende Ruby 3.4 in december 2024 een nieuwe lijn: base64 verhuisde van de default-gem-lijst naar de bundled gem-lijst, hetzelfde plankje als csv en drb. Bundled gems worden nog steeds meegeleverd met de taal, maar van Bundler-gebaseerde projecten wordt verwacht dat ze ze declareren, dus als je op Ruby 3.4 of later zit en je app is Bundler-gedreven, voeg gem "base64" toe aan je Gemfile (of voer gem install base64 uit) en ben je gedekt. Ruby 4.0 in 2025 bracht versie 0.3.0, met RBS-typesignaturen zodat statische checkers de module goed kunnen zien.
Door de hele reis heen bleef de implementatie wat die altijd was: een paar dozijn regels zuivere Ruby verpakt rond de core pack- en unpack-templates. Geen C-extensie, geen afhankelijkheden, en - met een downloadaantal in de honderden miljoenen op rubygems.org - een van de meest geïnstalleerde gems op het platform.
Grappige Ruby-feiten
- De hele module, encoders inbegrepen, is kort genoeg om in één koffiepauze te lezen.
encode64is letterlijk[bin].pack("m"),strict_encode64is[bin].pack("m0"), enurlsafe_encode64is de strikte encoder met een tweetekensruil erbovenop, minus de vulling als je om die vraagt. - De 60-teken-omwikkeling van
encode64komt noch overeen met het 76-teken-maximum van MIME noch met de klassieke 64 van PEM. Het is simpelweg wat dem-pack-template altijd al deed, en de Base64-encoder van demail-gem commenteert het goedkeurend: Rubys automatische regelomwikkeling houdt de uitvoer binnen de SMTP-limieten. - Rubys
Net::HTTPgebruikt voor Basic auth niet eens deBase64-module - het roept depack-template rechtstreeks aan, een mooie herinnering dat de module een gemaklaag bovenop de core is, niet andersom. - Elke digest-klasse draagt een
base64digest-methode, dusDigest::SHA256.base64digestis een volwaardige burger naasthexdigest- checksums als tekst zonder een tweede aanroep. - De YAML-
!binary-tag is Base64 in vermomming. Psych doet de codering het moment dat je een BINARY-tekenreeks dumpt, en daarom zien config-bestanden vol binaries eruit zoals ze eruitzien. - De module die je gebruikt was niet altijd de module die je je herinnert. Oude Ruby had
b64encode(omwikkeling op een gekozen breedte) endecode_b(RFC 2047 headerdecodering); beide verdwenen in de 1.9-reeks, dus elke code van vóór 2010 die je overneemt en die ze aanroept, sterft met eenNoMethodError. - YouTube-video-ID's zijn base64url zonder vulling - elf tekens, geen plus, geen slash, geen gelijkteken - precies het soort korte, link-veilige identifier waar het URL-veilige alfabet voor ontworpen is.
De andere kant
Je hebt nu het complete coderingsbeeld: een werkpaard als standaard dat je nooit zal verrassen, een klassieker die regels omwikelt voor de protocollen die dat eisen, een link-veilig alfabet met een vullingschakelaar, en de byte-niveau-regels die precies bepalen hoe je uitvoer eruitziet. De omgekeerde richting - een Base64-tekenreeks uit elkaar halen, kiezen tussen Rubys drie decoders, en de resulterende bytes omzetten in iets bruikbaars - heeft zijn eigen stille valkuilen, beginnend met een decoder die nooit nee zegt. Die kant van de straat wordt in diepte behandeld in het artikel over Base64-decoderen, hieronder gelinkt.
Laatst bijgewerkt: 2026-10-06
Gerelateerd artikel: Base64-decodering in Ruby: een complete gids