Werk je met Base64-indeling? Dan is deze site perfect voor jou! Gebruik onze handige online tool om je gegevens te coderen of te decoderen.

Base64-codering in Python: een complete gids

Hier is de andere kant van de munt. Je hebt data in je handen - een bestand, een wachtwoordpaar, een binair klompje, een alinea Unicode - en ergens stroomafwaarts moet die reizen via een kanaal dat niets accepteert behalve letters. Dat is de hele klus van Base64: schrijf drie bytes data om naar vier tekens uit een alfabet van 64 tekens, vul de laatste groep aan met = zodat alles in vieren uitkomt, en lever de letters over. De startpagina van deze site legt het formaat volledig uit, het alfabet inbegrepen, dus houden we dat tot één adem en besteden we de rest van de tijd aan wat Python er daadwerkelijk mee doet.

De economie verdient eerlijk één zin vóórdat we beginnen, want het getal komt in elk gesprek hierover terug: de prijs van die tekstveiligheid is omvang. Base64 maakt je data met zo'n derde groter, vier tekens voor elke drie invoerbytes, dus een megabyte binair wordt een megabyte en een derde aan letters. Voor een token of een configwaarde is dat geen probleem; voor een videobestand is het de reden om na te denken over je opties.

En het goede nieuws: Pythons antwoord op al dat is één import en één functie. base64.b64encode zit al decennia in de standaardbibliotheek, hoeft niet geïnstalleerd te worden, en draait onder de motorkap met C-snelheid. De rest van deze gids is de lange staart die de eenregelaar bruikbaar maakt in de echte wereld: de bytes-alleen-muur die de helft van alle TypeError-bugs stopt, het URL-veilige alfabet, de MIME-regelomwikkeltools, en de protocollen - JWT's, HTTP-koppen, WebSocket-handshakes, e-mail, PEM, data-URL's - waarin Base64 stilletjes zijn werk doet.

Maak kennis met b64encode

Het contract past in vier zinnen. Eén: de invoer is een bytes-achtig object - bytes, bytearray, memoryview - en een gewone tekenreeks wordt geweigerd. Twee: de uitvoer is een bytes-object, nooit een str. Drie: de uitvoer wordt altijd opgevuld tot een veelvoud van vier tekens, dus zelfs één enkele invoerbyte produceert Zg==. Vier: de uitvoer is één enkele regel, nooit omgebroken, hoe groot de invoer ook is. Alles andere in dit artikel is commentaar op die vier zinnen:

import base64
encoded = base64.b64encode(b"foobar")
print(encoded)
# b'Zm9vYmFy'
print(len(encoded))
# 8

Om een echte tekenreeks te krijgen, voor een URL of een kop of een JSON-veld, decodeer het resultaat als ASCII. Het alfabet garandeert dat er niets anders in kan zitten, en dat maakt deze stap veilig en goedkoop:

import base64
text = base64.b64encode(b"foobar").decode("ascii")
print(text)
# Zm9vYmFy

Er is nog één argument in de signatuur, altchars, en het wisselt de + en / van het standaardalfabet om naar een ander paar tekens. Precies die schakelaar zit achter de URL-veilige variant, dus houd die gedachte vast - je ontmoet hem over een paar secties, als we het hebben over tokens en querystrings.

De typemuur: str is geen bytes

De eerste Python-specifieke muur in dit artikel is het typesysteem, en het is de moeite waard om die te leren voelen. b64encode weigert tekenreeksen met een van de minst vergevingsgezinde foutmeldingen van de taal:

import base64
try:
  base64.b64encode("hello")
except TypeError as caught:
  print(caught)
# a bytes-like object is required, not 'str'

De oplossing is de belangrijkste gewoonte van deze hele gids: zet je tekst eerst om naar bytes, en kies de codering bewust in plaats van te hopen:

import base64
print(base64.b64encode("été".encode("utf-8")))
# b'w6l0w6k='
print(base64.b64encode("été".encode("utf-16")))
# b'//7pAHQA6QA='

Zelfde tekens, twee verschillende bytestekenreeksen, twee verschillende Base64-uitvoeren. De coderingskeuze is een beslissing, geen detail. UTF-8 is de standaard voor alles wat over een draad, een database of een API gaat. UTF-16 duikt op wanneer je praat met Windows-API's, en hij brengt een byte-order mark vooraan mee die je misschien niet wilt coderen, en die je kunt weggooien door utf-16-le te gebruiken of te knippen met lstrip("\ufeff"). Latin-1 schuilt nog steeds in oude Europese bestanden, waar één teken precies één byte is en de hele vraag zich nooit voordoet. Het mentale model om vast te houden: de encoder kijkt nooit naar je tekst, hij ziet alleen maar bits. Het moment dat de bytes de muur passeren, is de tekensetvraag gesloten - en dat is ook de reden waarom de decoderingskant later moet vragen wie die tekenset bezat.

base64url: wissel twee letters, gooi de opvulling weg

Het standaardalfabet verbergt twee tekens die URLs en bestandssystemen haten. Het +-teken wordt door elke formulierdecoder stilletjes als een spatie gelezen, en het /-teken is een pad-scheidingsteken, dus een lading in het standaardalfabet in een querystring of een bestandsnaam is een tikbom. Sectie 5 van RFC 4648 definieert de oplossing: een variant waarin + wordt tot - en / tot _, waarin de opvulling wordt weggegooid wanneer de datalengte uit de context bekend is, en die de RFC erop aandringt base64url te noemen en niet gewoon "base64". Je ontmoet hem in JSON Web Tokens, OAuth-tokens en API-cursorparameters, en dat wil zeggen, in het grootste deel van het moderne web.

Python levert zowel een eigen functie als de altchars-schakelaar uit de eerste sectie, en ze produceren identieke uitvoer:

import base64
data = b"\xfb\xff\xfe"
print(base64.b64encode(data))
# b'+//+'
print(base64.urlsafe_b64encode(data))
# b'-__-'
print(base64.b64encode(data, altchars=b"-_"))
# b'-__-'

In tokens en querystrings gaat de opvulling meestal ook weg, want een afsluitend = zou percent-encoding nodig hebben, en sommige tussenboxen verkrusen het er toch:

import base64
padded = base64.urlsafe_b64encode(b"fooba")
print(padded)
# b'Zm9vYmE='
print(padded.rstrip(b"="))
# b'Zm9vYmE'

Knip, verstuur, en de ontvanger voegt de opvullingstekens terug toe met de modulo-truc, "=" * (-len(s) % 4), die precies zo veel opvullingstekens produceert als de lengte vereist. De vuistregel: als de data in een URL, een bestandsnaam of een JWT gaat zitten, gebruik dan de urlsafe-variant en gooi de opvullingstekens weg; als hij in een e-maillichaam of een tekstbestand gaat zitten, is het standaardalfabet met zijn opvulling de norm.

Wanneer je lezer regels wil: MIME en de 76-tekensregel

De één eindeloze regel van b64encode is perfect voor JSON-velden, koppen en URLs, maar e-mail heeft meningen. RFC 2045, de MIME-standaard, eist dat Base64-uitvoer wordt gebroken in regels van maximaal 76 tekens, en de ouderwetse tools van Python zijn gebouwd om precies dat te produceren. encodebytes, toegevoegd in Python 3.1, doet de omwikkeling voor een bytes-object:

import base64
wrapped = base64.encodebytes(b"x" * 100)
for line in wrapped.splitlines():
  print(len(line), line[:12])
# 76 eHh4eHh4eHh4
# 60 eHh4eHh4eHh4

De werkwijze is een beetje schattig. De module codeert in fragmenten van 57 bytes, de constante MAXBINSIZE, want 57 bytes worden precies 76 tekens, en in moderne CPython eindigt elke omwikkelde regel met een gewone line feed. RFC 2045 vroeg om CRLF, maar Pythons LF-uitvoer wordt geaccepteerd door elke decoder in het ecosysteem, inclusief Pythons eigen. De ouderwetse bestand-naar-bestand-functie encode doet dezelfde omwikkeling rechtstreeks van het ene bestand naar het andere, en dat maakt het een nette tool voor grote bestanden die je niet tweemaal in het geheugen wilt houden.

Welke tool wanneer, kort gezegd: b64encode voor alles wat in een JSON-veld, een URL, een kop of een databaskolom gaat; encodebytes voor e-maillichamen en PEM-stijl-pantsering; de ouderwetse encode wanneer je een groot bestand streamt en de omwikkeling voor gratis wilt. De verkeerde kiezen is een klassieke bug, want één los regeleinde in een JSON-veld is genoeg om een strenge decoder aan de andere kant een exceptie te laten gooien.

Het uitgebreide gezin

De base64-module is eigenlijk de base-N-module, en hij draagt het hele RFC 4648-gezin plus een paar verwanten uit andere hoeken van de wereld van de computation. De meeste zijn vervangers van één regel die direct in te plaatsen zijn, voor hetzelfde bytes-in-bytes-uit-contract:

Functies Alfabet Wanneer Je Het Ontmoet
b16encode / b16decode 0-9A-F "Base16" is gewoon hexadecimaal; de snelste rondtrip in de module, prima voor hashes en UUID's
b32encode / b32decode A-Z2-7 licentiesleutels en activeringscodes; geen 0, O, 1 of I, dus het overleeft het hardop voorlezen
b32hexencode / b32hexdecode 0-9A-V Base32 met een hex-alfabet, toegevoegd in Python 3.10; houdt gecodeerde data lexicografisch sorteerbaar
a85encode / a85decode 85 afdrukbare tekens ASCII85 uit PostScript en PDF, de nakomeling van het Unix-btoa-hulpmiddel; in de module sinds Python 3.4
b85encode / b85decode 85 afdrukbare tekens het Base85-formaat dat git en Mercurial gebruiken voor binaire diffs; eveneens sinds Python 3.4
z85encode / z85decode 85 afdrukbare tekens de Z85 van ZeroMQ, toegevoegd in Python 3.13; raamt data in groepen van vier bytes

Geen van hen verandert de regels die je al hebt geleerd: bytes in, bytes uit, een alfabet om uit te kiezen, en een passende decode-functie die aan de andere kant wacht. In de praktijk grijp je naar b16 wanneer een mens de waarde zou moeten kunnen lezen, naar b32 wanneer de waarde met de hand getypt of uitgesproken wordt, en naar de 85-tekens-neefjes alleen wanneer een specificatie het je vertelt. Voor alles anders is het Base64-paar van het begin van dit artikel de juiste tool, en het is het waar elk ander deel op bouwt.

Beelden in de pagina: data-URL's

De meest zichtbare Base64 op het web is de data:-URI: media dat direct in HTML of CSS is ingebouwd, zodat de browser geen tweede verzoek hoeft af te vuren. Het formaat is data:, het mediatype, het woord base64, een komma, en de gecodeerde bytes. Eentje bouwen vanuit een bestand op schijf kost drie regels:

import base64
with open("logo.png", "rb") as handle:
  encoded = base64.b64encode(handle.read()).decode("ascii")
uri = "data:image/png;base64," + encoded
print(uri[:40])
# data:image/png;base64,iVBORw0KGgoAAAAN...

Twee waarschuwingen, allebei goedkoop in acht te nemen. Ten eerste zal de browser graag een data-URI renderen, en zal hij graag megabytes daarvan in het document vasthouden: voor alles groter dan een paar kilobyte wint een gewone afbeeldingsaanvraag met een nette cache-kop op elke meting die telt. Ten tweede is het mediatype na de punt een belofte. Als de bytes een JPEG zijn, zegt de URI image/jpeg, want sommige tooling valideert het paar en sommige renderers weigeren simpelweg om te raden. De .decode("ascii")-stap is ook geen versiering; zonder het pluis je een bytes-object aan een tekenreeks en krijg je een TypeError, de typemuur die zijn ronde maakt.

Tokens die je uit kunt reiken: JWT's

Een JSON Web Token is drie base64url-stukjes, aan elkaar gekoppeld door punten: een kop, een lading, en een handtekening. Als je échte tokens uitgeeft, roll ze dan niet zelf. Installeer PyJWT (pip install pyjwt) en laat hem de base64url-delen, de opvulling, en de handtekening in één aanroep bouwen:

import jwt
# Een sleutel van minder dan 32 bytes loopt PyJWT's InsecureKeyLengthWarning op, een eerlijke knor voor een demo-sleutel.
token = jwt.encode(
  {"sub": "1234567890", "name": "John Doe"},
  "super-secret-key",
  algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
print(type(token))
# <class 'str'>

Onder de motorkap doet PyJWT precies wat dit artikel beschrijft: serialiseer naar JSON, stuur het door de urlsafe-encoder, en knip de opvullingstekens weg, volgens de JWS-definitie in RFC 7515. Als je ooit een stukje handmatig moet samenstellen, voor een testfixture of een debugsessie, is het recept overal dezelfde wiskunde:

import base64
import json
payload = json.dumps({"sub": "1234567890"}).encode("ascii")
part = base64.urlsafe_b64encode(payload).rstrip(b"=")
print(part)
# eyJzdWIiOiAiMTIzNDU2Nzg5MCJ9

Eén opmerking over de richting van vertrouwen: een token bouwen is de makkelijke helft. De ontvanger moet de handtekening verifiëren vóórdat hij één enkele claim vertrouwt, en PyJWT 2.x decodeert geen token zonder een expliciete algorithms-lijst, en dat is een kenmerk, want de "elk algoritme" fout is een van de duurste regels authenticatiecode die ooit zijn geschreven.

HTTP: Basic-authenticatie en de WebSocket-handshake

Twee HTTP-momenten leven of sterven op Base64. Het eerste is het oudste authenticatieschema in het protocol: Basic-authenticatie (RFC 7617), waarbij de client user:pass stuurt, base64-gecodeerd, achter het woord Basic:

import base64
credentials = base64.b64encode(b"jane:pa:ss").decode("ascii")
header = "Basic " + credentials
print(header)
# Basic amFuZTpwYTpzcw==

Als requests al in je stack zit, bouwt hij deze kop voor je met auth=("jane", "pa:ss"), en dat is de moeite waard om te gebruiken, want het houdt het coderingsdetail buiten je code. En wees eerlijk over wat er gebeurt, zolang je er toch bent: RFC 7617 stelt scherp dat het schema "geen veilige methode van gebruikersauthenticatie is, en de entiteit op geen enkele manier beschermt, die in onversleutelde tekst wordt overgezonden". De inloggegevens zijn in één regel code te herstellen door iedereen die het verkeer ziet, dus dit is een gemak voor TLS-beschermde verbindingen, geen beveiligingsgrens.

Het tweede moment is de WebSocket-handshake (RFC 6455), waarbij de server bewijst dat hij de willekeurige sleutel van de client gelezen heeft door te antwoorden met Base64 van een SHA-1-hash van de sleutel die is geplakt aan een magische GUID:

import base64
import hashlib
key = "dGhlIHNhbXBsZSBub25jZQ=="
magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
accept = base64.b64encode(
  hashlib.sha1((key + magic).encode("ascii")).digest()
).decode("ascii")
print(accept)
# s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

De uitvoer is de exacte waarde uit het werkvoorbeeld in de RFC zelf, en dat is een fijne manier om een implementatie van bij nul te controleren. In productie doet de websockets-bibliotheek deze stap voor je aan beide uiteinden, en je maakt hem alleen handmatig wanneer je de kleine testserver schrijft die je begrip bewijst.

E-mail: de oorspronkelijke klant

Base64 werd in 1993 gestandaardiseerd voor één klus, en die klus was e-mail: binair over laten leven in de wereld van SMTP die alleen tekst kent, volgens RFC 2045's Content-Transfer-Encoding: base64. Pythons email-pakket bouwt het bericht, kiest de codering, en wikkelt het lichaam om op de standaardregellengte, zonder dat je zelf een regel Base64 hoeft te schrijven:

import email.mime.multipart
import email.mime.application
msg = email.mime.multipart.MIMEMultipart()
msg["Subject"] = "binary payload"
part = email.mime.application.MIMEApplication(
  b"\x00\x01\x02", _subtype="octet-stream"
)
msg.attach(part)
text = msg.as_string()
print(text)
# ...
# Content-Transfer-Encoding: base64
#
# AAEC
# --...

Het MIMEApplication-deel is het interessante: het wikkelt de bytes in de juiste regels van 76 tekens en stempelt de transfer-encoding-kop erop, en dat is precies het encodebytes-gedrag van eerder, nu toegepast door het framework. Als je een kale snippet samenstelt in plaats van een volledig bericht, dan doet email.encoders.encode_base64(obj) één keer coderen-en-omwikkeling direct op een berichtobject. En wanneer het bericht aan de andere kant aankomt, de decoderingskant van het verhaal, van get_payload(decode=True) tot kopdecodering, wordt dat behandeld in het gerelateerde decoderingsartikel.

PEM-pantsering voor sleutels en certificaten

PEM-bestanden - de certificaten, private keys en CRL's die beginnen met -----BEGIN ...----- - zijn niets dan pantsering rond Base64: een labelregel, omwikkelde Base64, een afsluitend label. De pantsering is makkelijk door te zien, want het lichaam is gewoon de omwikkelde uitvoer die je al hebt ontmoet:

import base64
der = b"\x30\x03\x02\x01\x05"   # een klein DER-klompje ter illustratie
body = base64.encodebytes(der).decode("ascii")
armor = ("-----BEGIN CERTIFICATE-----\n"
         + body
         + "-----END CERTIFICATE-----")
print(armor)
# -----BEGIN CERTIFICATE-----
# MAMCAQU=
# -----END CERTIFICATE-----

Knip de twee labelregels weg, voeg de rest samen, en b64decode levert je de DER-bytes terug. In productie doe je dit bijna nooit handmatig: het cryptography-pakket (pip install cryptography) genereert de pantsering met public_bytes en parst het met load_pem_x509_certificate en met maten, en doet de Base64-stap voor je onder de motorkap. De manuele route verdient zijn kost in het specifieke moment wanneer de ruwe DER-bytes al in je handen zitten - een databaskolom, een configuratiebestand, een buffer uit een protocol - en de specificatie vóór je zegt "PEM, alsjeblieft".

Bestanden versturen: uploads, downloads en de .b64-gewoonte

Het oudste gebruiksgeval op het internet is een binair bestand dat een kanaal moet oversteken dat alleen tekst kan vervoeren: een FTP die regeleindes verkrast, een formulier dat uploads weigert, een chatvenster dat binair op eet. Het recept is lezen, coderen, versturen, en de andere kant laten decoderen, en de interessante helft ervan zijn de middelste twee stappen:

import base64
with open("photo.png", "rb") as src:
  data = src.read()
wrapped = base64.encodebytes(data)
with open("photo.b64", "wb") as dst:
  dst.write(wrapped)
print(len(wrapped), "bytes on disk for", len(data), "in the photo")
# ongeveer een derde groter dan het origineel

Twee opmerkingen. De .b64-extensie is een gemeenschapsconventie, geen standaard, dus moet de ontvangende kant de conventie ook kennen - en daarom wikkelen JSON-API's de lading meestal in in een genoomd veld als "image_base64" en zeggen ze dat in hun documentatie. En de omwikkelde regels van 76 tekens van encodebytes zijn het formaat van keuze voor het bestand op schijf, want ze overleven schoon het kopiëren en plakken door elk teksttool dat mensen hebben, van mailclients tot PDF-lezers. De omgekeerde richting, zo'n bestand teruglezen naar bytes, is één aanroep in het gerelateerde decoderingsartikel; hier ben je alleen de afzender, en het werk van de afzender is consistent zijn.

De opslagvraag: configuratiebestanden, omgevingsvariabelen en databases

Ontwikkelaars houden ervan om Base64 te leggen op plekken die alleen tekst accepteren: een .env-bestand, een .ini-instelling, een TEXT-kolom. De coderingsstap is triviaal, en de meest voorkomende vorm is JSON-in-Base64:

import base64
import json
config = {"api_user": "svc-bot", "api_pass": "hunter2-not-really"}
packed = base64.b64encode(
  json.dumps(config).encode("utf-8")
).decode("ascii")
print(packed)
# eyJhcGlfdXNlciI6ICJzdmMtYm90IiwgImFwaV9wYXNzIjogImh1bnRlcjItbm90LXJlYWxseSJ9

En dan de waarschuwing, want hier zit het duurste misverstand van dit hele artikel. Base64 is geen verduistering die standhoudt, en het is geen versleuteling. Sectie 12 van RFC 4648 zegt het zo plat als een RFC kan: base-codering "verbergt visueel informatie die anders makkelijk te herkennen is, zoals wachtwoorden, maar biedt geen enkele computationele vertrouwelijkheid". Een .env-bestand met Base64-geheimen beschermt je tegen de persoon die er even naar kijkt, niet tegen de persoon die het leest, en één commando van base64 -d later zit het "geheim" in klarte in hun terminal. Als de data écht gevoelig is, versleutel het eerst - het cryptography-pakket levert Fernet precies hiervoor - en pas daarna de versleutelde tekst nog in Base64, als je opslag tekst eist.

Na een miljoen bytes: big data en opknipping

b64encode is een C-snelheidsfunctie - op een gewone laptop verwerkt hij een megabyte in zo'n milliseconde - maar het is geen stroomfunctie. Ergens in de standaardbibliotheek bestaat er geen update-en-finish-paar, dus data coderen dat groter is dan je in het geheugen wilt houden betekent dat je de grenswiskunde zelf doet. Drie invoerbytes maken vier uitvoer-tekens, dus elke fragmentgrens moet op een drie-bytes-naad landen:

import base64
def encode_chunks(chunks):
  out = []
  leftover = b""
  for chunk in chunks:
    buffer = leftover + chunk
    whole = len(buffer) // 3 * 3
    if whole:
      out.append(base64.b64encode(buffer[:whole]))
    leftover = buffer[whole:]
  if leftover:
    out.append(base64.b64encode(leftover))
  return b"".join(out)
with open("video.mp4", "rb") as handle:
  encoded = encode_chunks(iter(lambda: handle.read(65536), b""))

De uitvoer is byte voor byte identiek aan het coderen van het hele bestand in één aanroep, want de drie-bytes-naad is de enige plek waar de groepering kan breken. De opvulling verschijnt precies één keer, in het laatste fragment, en dat is wat een strenge decoder aan de andere kant zal verwachten. De iter(lambda: handle.read(65536), b"")-regel is het standaardidioom om een bestand in stukjes van vaste grootte te lezen, en de leftover-variabele is het hele algoritme. De decoderingskant houdt een vier-tekens-naad in plaats van een drie-bytes-naad, dus delen de twee artikelen de wiskunde onder elkaar in plaats van hem te herhalen.

Waar encoders fout gaan

De coderingskant heeft minder valkuilen dan de decoderingskant, want er is minder wat mis kan gaan wanneer jij de bent die de letters produceert. Toch duiken deze elke week op, en elk van ze heeft een fix van twee minuten als je het vroeg genoeg herkent:

  • Een tekenreeks aan de encoder voeren. De TypeError uit de typemuur-sectie. Herstel het aan de bron met .encode("utf-8"), en denk na over welke tekenset je écht bedoelt vóórdat je het typt.
  • Vergeten dat de uitvoer bytes is. b64encode levert bytes op; de str is wat in een URL of een JSON-veld gaat, dus de .decode("ascii")-stap is onderdeel van het recept, geen nagelaten werk.
  • De URL-veilige wissel zelf rollen. str.replace("+", "-").replace("/", "_") werkt, maar het is onderhoudsschuld van twee letters waar urlsafe_b64encode één aanroep is. Erger: een half geëindigde wissel, plussen hersteld en schuine strepen vergeten, levert een alfabet op dat niet aan enige specificatie beantwoordt.
  • De opvullingstekens in een URL laten zitten. Een afsluitend = in een querystring wordt door de ene tool percent-gecodeerd en door de andere weggeknipt, en de opvulwiskunde van de ontvanger breekt op de meest verwarrende manier. Knip ze weg; de lengte vertelt de decoder alles wat hij nodig heeft.
  • Omwikkeling waar ze niet gewenst is. De regeleindes van encodebytes zijn correct voor e-mail en PEM, en giftig voor een JSON-veld of een URL. Eén los regeleinde is genoeg om een strenge decoder aan de andere kant een exceptie te laten gooien over je data, niet over je opmaak.
  • Tweemaal coderen. De data was stroomopwaarts al Base64 - een veld dat vooraf gecodeerd aankomt vanuit een andere API, een bestand dat tweemaal de .b64-behandeling kreeg - en de tweede ronde produceert een tekenreeks die terugdecodeert naar de eerste codering. Rondtrip één keer, check de magische bytes, en stop.
  • Base64 vertrouwen met geheimen. De waarschuwing uit de opslagsectie, herhaald omdat het écht geld kost: als het dreigingsmodel iedereen die het bestand leest omvat, heb je een cilfer nodig, geen alfabet.

Een wijzigingslog die je écht kunt lezen

De leeftijd van de module toont zich in stille, gedateerde verbeteringen in plaats van revoluties. De korte versie, in de volgorde waarin de stukken landden, vanuit het perspectief van de encoder:

Versie Wat Er Gebeurde
Python 2.4 (2004) de volledige RFC 3548-ondersteuning van Barry Warsaw verschijnt: de b16, b32 en b64-families, plus de standard_*- en urlsafe_*-varianten die vandaag worden gebruikt
Python 3.1 (2009) encodebytes arriveert en encodestring wordt afgeschreven, een hernoeming waar oude tutorials nog steeds over struikelen
Python 3.4 (2014) elke encoder accepteert elk bytes-achtig object, en a85encode en b85encode sluiten aan bij de module
Python 3.6 (2016) binascii.b2a_base64 leert een newline-schakelaar kennen, en dat is wat b64encode laat blijven bij één eindeloze regel
Python 3.9 (2020) de ouderwetse namen encodestring en decodestring worden eindelijk verwijderd
Python 3.10 (2021) b32hexencode en b32hexdecode, de sorteerbare hex-alfabetneefjes
Python 3.13 (2024) z85encode en z85decode, het alfabet van ZeroMQ, sluiten aan bij het gezin
Python 3.14 (2025) snellere imports door de hele standaardbibliotheek heen, base64 inbegrepen, en een b16decode die tot zes keer sneller is, omdat de validatie nu draait op bytes.translate in plaats van een reguliere expressie

De rode draad, als je er één wilt: de module werd in 1995 herschreven om zijn werk af te staan aan de C-level-module binascii, en die overdracht is vandaag nog steeds van kracht. De eerste verandering uit het bytes-tijdperk, een commit uit 2007 tijdens de Python 3-ontwikkeling die alles overal bytes laten gebruiken, is waar de typemuur in dit artikel vandaan komt, en het is de reden waarom een moderne encoder bytes neemt en bytes teruggeeft, met alles anders een omhulling rond dat ene contract.

Dingen die de module je niet vertelt

Het serieuze werk is klaar, dus hier zijn de kleine genoegens aan de coderingskant van de balans:

  • Het eigen voorbeeld van de documentatie voert dezelfde demonstratie al meer dan een decennium uit: b'data to be encoded' gaat erin, b'ZGF0YSB0byBiZSBlbmNvZGVk' komt eruit. Je hebt dit paar eerder al ontmoet, of je het weet of niet.
  • De C-functie onder b64encode voegt zijn afsluitende regeleinde toe met een comment die luidt "voeg een beleefd regeleinde toe". Een hele cultuur, in één regel broncode.
  • Het woord password wordt gecodeerd naar cGFzc3dvcmQ=, en daarom ziet Base64 in een logbestand voor een scanner eruit als een geheim, en is het voor een lezer op één commando af van dat ook te zijn.
  • b64encode wikkelt nooit. Nooit. Een gigabyte aan invoer produceert één enkele regel van 1,3 gigabyte, en de functie knippert niet. Als je regels wilde, had je om encodebytes moeten vragen.
  • De docstring van de module noemt nog steeds RFC 3548, de editie van 2003 van de specificatie. RFC 4648 nam in 2006 over; de docstring merkte dat simpelweg nooit.
  • Python 2 had helemaal geen typemuur: b64encode accepteerde graag een str en leverde er een terug. De bytes-hervorming van 2007 beëindigde dat, en de oude Python 2-tutorials zijn waar de meeste "waarom crasht mijn encode"-threads nog steeds naar wijzen.
  • z85encode, het jongste lid van het gezin (Python 3.13), is het kieskeurigste: ZeroMQ raamt data in groepen van vier bytes, dus de specificatie eist dat de gecodeerde uitvoer een veelvoud van vijf tekens is - en de documentatie legt de opvulling bij jou: de invoer moet als een veelvoud van 4 bytes aankomen (de encoder vult hem niet voor je op; een invoer van 3 bytes levert een frame van 4 tekens op dat geen ZeroMQ-pair accepteert).

Dus de filosofie van de encoder in drie regels. Beslis eerst over de bytes en de codering daarna, want de typemuur is waar de meeste Python Base64-bugs worden geboren. Kies het alfabet voor het kanaal, niet voor de data: standaard met opvullingstekens voor e-mail en bestanden, base64url zonder opvullingstekens voor URLs en tokens, en improviseer nooit een derde variant aan het toetsenbord. En houd de uitvoer in de vorm die zijn lezer verwacht, één regel voor JSON en koppen, regels van 76 tekens voor MIME en PEM, want de decoder aan de andere kant houdt je eraan vast.

Wanneer die letters aan de andere kant aankomen, begint het échte plezier: ontbrekende opvullingstekens, stille wegwissingen, ladingen die niet helemaal Base64 zijn, en een decoder met twee stemmingen om te navigeren. Al dat wordt in detail behandeld in het gerelateerde Base64-decoderingsartikel onderaan deze pagina, en de twee gidsen lezen goed als een paar. Veel plezier met het coderen.

Laatst bijgewerkt: 2026-10-06

Gerelateerd artikel: Base64-decodering in Python: een complete gids