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 PHP: een complete gids

Je hebt data die een kanaal moet overleven dat die niet ziet zitten. Een binaire blob die in een JSON-veld moet zitten. Een afbeelding die in een HTML-tag moet leven. Een certificaat dat in een config-bestand thuishoort. Een token dat door URLs, headers en cookies zal reizen. Dit is het dagelijks leven van Base64-codering: het schrijft elke drie bytes ruwe data om naar vier tekens uit een alfabet van 64 letters, met één of twee =-tekens die de staart afmaken, zodat het resultaat platte tekst is die alles kan dragen. De startpagina van deze site legt het formaat volledig uit, dus dit artikel richt zich op wat PHP je geeft, wat het stilletjes voor je beslist, en waar de vallen zitten.

Aan de PHP-kant begint het verhaal met goed nieuws: base64_encode() woont al sinds PHP 4 in de core, neemt één argument aan, geeft altijd een tekenreeks terug, en kan niet falen. Geen strict-modus, geen foutpad, geen configuratie. De codering is deterministisch: dezelfde bytes leveren altijd dezelfde letters op. Jouw werk als ontwikkelaar is niet de functie aan het werk krijgen, maar de wereld eromheen in goede banen leiden: kies het juiste alfabet voor de bestemming, voeg de juiste regeleindes toe, zet de juiste tekenset om, en draag de groottefactuur van 33 procent met open ogen. (Base64 vergroot data normaal ongeveer met een derde, vier tekens per drie invoerbytes; houd dat in je achterhoofd, want het komt steeds terug.)

Aan het einde van dit artikel weet je hoe je elke variant van Base64 maakt die een PHP-ontwikkelaar daadwerkelijk tegenkomt: uitvoer op één regel, MIME-omwikkelde e-mail, PEM-omwikkelde sleutels, URL-safe tokens en data-URIs, plus de streaming-trucs voor wanneer de data te groot is om in het geheugen te houden.

Één functie, nul opties

De hele API, exact zoals moderne PHP die rapporteert:

base64_encode(string $string): string

Lees het nog een keer. Eén parameter, één terugkeerwaarde, geen flags. De handleiding beschrijft het als MIME base64, 'ontworpen om binaire data te laten overleven bij transport via transportlagen die niet 8-bit clean zijn, zoals e-maillichamen'. Let op wat die bewoording niet belooft: geen regeleindes, geen omwikkeling, geen mening over waar de uitvoer zal wonen. De functie geeft één lange regel uit, en welke omwikkeling de bestemming ook wil, is dat jouw werk met een tweede aanroep. Sinds PHP 8.0 draagt de signatuur native types; sinds PHP 8.1 geeft het doorgeven van null een verouderingswaarschuwing, dus coalesceer elke nullable waarde eerst naar ''.

De uitvoergrootte volgt een vast patroon dat je vóór de aanroep kunt voorspellen:

Invoerbytes Uitvoertekens Vulling
0 0 geen
1 4 twee =
2 4 één =
3 4 geen
3,000,000 4,000,000 geen
100,000 133,336 twee =

Het patroon: vier tekens voor elke volledige groep van drie bytes, plus een laatste deeltgroep met één of twee =-tekens als vulling. Een gevolg dat je moet kennen: één byte en drie bytes leveren allebei vier tekens op, dus de gecodeerde lengte verbergt de exacte invoergrootte. Je kunt die schatten (delen door vier, vermenigvuldigen met drie, de vultekens aftrekken), maar je kunt die niet exact aflezen.

Waar de regeleindes terechtkomen

Omdat base64_encode() zelf nooit omwikkelt, is de omwikkelbeslissing een kwestie van bestemming. In de praktijk zijn er drie antwoorden.

Geen regeleindes. De ruwe functie-uitvoer, exact één regel. Dat wil je voor URLs, JSON-payloads, headers, database-waarden en alles waar een regeleinde een bug zou zijn. Dat is ook wat de meeste mensen bedoelen met 'geef me gewoon de Base64'.

MIME-omwikkeling: 76 tekens plus CRLF. De e-mailconventie uit RFC 2045, sectie 6.8: gecodeerde regels mogen niet langer dan 76 tekens zijn, en decoders moeten de regeleindes negeren. De klassieke metgezel is chunk_split(), die de handleiding in de 'See Also'-lijst met base64_encode() paart, precies om deze reden:

$binary = file_get_contents('/var/www/uploads/report.pdf');
$wrapped = chunk_split(base64_encode($binary), 76, "\r\n");

PEM-omwikkeling: 64 tekens plus LF. Sleutels en certificaten gebruiken de oudere Privacy-Enhanced Mail-conventie (RFC 1421): kortere regels van 64 tekens. Zelfde tool, andere getallen:

$der = file_get_contents('/etc/ssl/raw-key.der');
$armor = "-----BEGIN PRIVATE KEY-----\n"
  . chunk_split(base64_encode($der), 64, "\n")
  . "-----END PRIVATE KEY-----\n";

Één chunk_split()-valkuil geldt voor de twee omwikkelde varianten: de functie voegt de separator aan het eind van het resultaat toe, zelfs als de invoerlengte een exact veelvoud van de regellengte is. Als een afnemer struikelt over een lege regel aan het eind, is dit de reden; een rtrim()-aanroep op de separator lost het op. Let ook op de asymmetrie die je ooit zal redden: decoders negeren regeleindes volledig, dus een MIME-omwikkelde payload en een niet-omwikkelde payload decoderen naar dezelfde bytes. Omwikkeling is een beleefdheid voor regelgebaseerde tools en voor mensen, geen semantisch verschil.

De uitvoer URL-safe maken

Het standaardalfabet bevat + en /, en beiden zijn buiten een tekstbestand lastig. Een + in een form-gecodeerde query string wordt een spatie voordat je applicatie het ooit ziet, en / is in URLs een padseparator. Bestandsnamen en tokens hebben elk hun eigen klacht. RFC 4648, sectie 5, lost dit op met het URL- en bestandsnaam-veilige alfabet: + wordt -, / wordt _, en de vulling met = aan het eind wordt meestal weggelaten. De RFC benadrukt dat dit 'niet als hetzelfde als de base64-codering mag worden gezien', dus behandel het als een apart formaat, algemeen bekend als base64url.

Het produceren ervan is twee tekenreeks-operaties:

function base64url_encode(string $data): string
{
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
var_dump(base64url_encode("hi>?there\x00bin")); // string(18) "aGk-P3RoZXJlAGJpbg"

Wanneer gebruiken: JSON Web Token-delen, OAuth state- en nonce-parameters, API-IDs die je in URL-paden zet, en alles wat in een adresbalk of een bestandsnaam zal worden gekopieerd. Wanneer níét gebruiken: e-maillichamen, PEM-omhulling, en overal waar aan de andere kant een afnemer van het standaardalfabet zit, want - en _ staan niet in hun vocabularium. En meng de twee alfabetten niet stilletjes: een token dat URL-safe is gecodeerd, moet URL-safe gedecodeerd worden, overal, altijd. Dat is de hele interoperabiliteitsregel van base64url.

Unicode en de bytes die je bedoelde

PHP-tekenreeksen zijn reeksen van bytes, en base64_encode() codeert welke bytes je hem ook geeft, zonder te vragen wat ze betekenen. Dat is een feature tot de dag dat je 'tekst' in een andere codering zit dan je denkt. De klassieke mislukking: een tekenreeks die in je editor als UTF-8 eruitziet, maar van een oude bron als Windows-1252 is aangekomen. Codeer die bytes ongewijzigd en de ontvanger, die decodeert en UTF-8 aannemt, krijgt mojibake in plaats van je letters met accenten.

De oplossing is om te normaliseren vóórdat je codeert, met de mbstring-extensie (die zit in de PHP-bron maar is niet standaard ingeschakeld):

$fromLegacy = "caf\xE9 au lait"; // Windows-1252-bytes: de é is 0xE9
$utf8 = mb_convert_encoding($fromLegacy, 'UTF-8', 'Windows-1252');
$encoded = base64_encode($utf8);
var_dump($utf8); // string(13) "café au lait": de é is nu twee UTF-8-bytes

Als de bron al UTF-8 is, kun je de omzetting overslaan, en een goedkope sanity check is mb_check_encoding($utf8, 'UTF-8'). Eén zin advies: probeer nooit een al gecodeerde Base64-tekenreeks 'te repareren' door haar als tekst opnieuw te coderen. Dat is de dubbele-codering-val uit de valkuilensectie hieronder, en de meest voorkomende Base64-bug in PHP-codebases, bijlange van.

Bestanden, blobs en de .b64-conventie

De meest voor de hand liggende codertaak: een bestand wordt tekst. PHP-tekenreeksen zijn bytes, dus er is geen 'binaire modus' waar je rekening mee hoeft te houden; file_get_contents() reikt je de exacte bytes aan en base64_encode() reikt je de exacte tekst aan:

$binary = file_get_contents('/var/www/uploads/photo.png');
$encoded = base64_encode($binary);
file_put_contents('/var/www/uploads/photo.png.b64', $encoded);
var_dump(strlen($binary), strlen($encoded)); // de 33%-factuur, elke keer

Twee gewoontes houden dit veilig. Eerst: weet wat je codeert. De finfo-klasse (de fileinfo-extensie, opgenomen in standaard PHP-builds) vertelt je het echte type uit de bytes, niet uit de bestandsnaam:

$mime = (new finfo(FILEINFO_MIME_TYPE))->file('/var/www/uploads/photo.png');
var_dump($mime); // string(9) "image/png"

Ten tweede: vergeet de groottefactuur niet als je opslag plant: een afbeelding van 500 KB wordt een tekstbestand van 670 KB, en een video van 1 GB wordt een tekstbestand van 1.33 GB. Daarom bestaat de sectie over grote data hieronder.

Data-URIs: een afbeelding in de pagina

Een data-URI plaatst de payload direct in de URL, dus er is geen tweede aanvraag nodig om die op te halen. RFC 2397 definieert de vorm: data:, een optionele media type, een optionele ;base64-flag, een komma, en dan de data. Voor binaire media zoals afbeeldingen is de flag aanwezig, dus de payload is exact wat base64_encode() heeft geproduceerd:

function data_uri_for(string $path): string
{
  $mime = (new finfo(FILEINFO_MIME_TYPE))->file($path);
  $payload = base64_encode(file_get_contents($path));
  return 'data:' . $mime . ';base64,' . $payload;
}
$uri = data_uri_for('/var/www/uploads/avatar.png');
echo '<img src="' . $uri . '" alt="avatar" />' . "\n";

Waarom hier Base64? Omdat een URI geen ruwe bytes of komma's veilig kan bevatten, en het Base64-alfabet helemaal geen escapering nodig heeft. De afwegingen zijn er wel echt. De gecodeerde payload is ongeveer 33 procent groter dan het bestand, waardoor het HTML-document zelf groter wordt. Browsers cachen een data-URI niet zoals ze een bestands-URL cachen, dus bij elk paginabezoek worden de bytes opnieuw gedownload. En de RFC zelf zegt dat data-URIs alleen nuttig zijn voor korte waarden; oude HTML-parsers hadden harde limieten op de attribuutlengte, en moderne browsers zijn veel nageeflijker, maar houden ook niet van megabytes in een tag. Gebruik ze voor avatars, icons en kleine inline-graphics; gebruik echte bestanden voor alles anders.

JWTs en API-tokens

JSON Web Tokens zijn het vlaggenschip onder de Base64-afnemers in moderne APIs, en ze gebruiken de URL-safe, niet-gevulde dialect uit de sectie hierboven. Volgens RFC 7519 is een compacte JWT drie puntgescheiden base64url-delen: header, payload, signatuur. De header en de payload zijn platte JSON; de signatuur is ruwe bytes. Handmatig een exemplaar bouwen is een leuke manier om elk bewegende deel te zien:

function base64url_encode(string $data): string
{
  return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
$header = base64url_encode(json_encode(['alg' => 'HS256', 'typ' => 'JWT']));
$payload = base64url_encode(json_encode([
  'sub' => '1234567890',
  'name' => 'John Doe',
  'iat' => 1516239022,
]));
$signingInput = $header . '.' . $payload;
$signature = base64url_encode(hash_hmac('sha256', $signingInput, $secret, true));
$token = $signingInput . '.' . $signature;

Twee dingen op te merken. De signatuur is de base64url-codering van ruwe HMAC-bytes, daarom wordt hash_hmac() met true aangeroepen voor ruwe uitvoer. En de header en de payload zijn door iedereen te lezen, en dat is met opzet: een JWT is een ondertekend bonnetje, geen geheim. Voor productie: bouw ondertekenen en verifiëren niet zelf. Het community-pakket is firebase/php-jwt (v7, vereist PHP 8.0 of nieuwer), geïnstalleerd met Composer:

composer require firebase/php-jwt
use Firebase\JWT\JWT;
$secret = 'correct-horse-battery-staple-long-enough-secret';
$token = JWT::encode([
  'sub' => '1234567890',
  'name' => 'John Doe',
  'exp' => time() + 3600,
], $secret, 'HS256');
var_dump(substr_count($token, '.')); // int(2): header, payload, signatuur

Versie-opmerking voor de v7-lijn van de library: de HMAC-algoritmes handhaven een minimale sleutellengte, dus een HS256-secret van minder dan 32 bytes wordt afgewezen vóórdat er überhaupt gecodeerd wordt. Lange secrets zijn sowieso de norm; dit zorgt er alleen voor dat de library niet lichtzinnig met die norm omgaat.

De library doet de base64url-omzetting, het ondertekenen en de vervalcontroles voor je, en hij gooit getypeerde excepties uit in plaats van half-vertrouwde data terug te geven. Als je met tokens schrijft, raak je base64_encode() nooit direct aan, en dat is precies zoals het moet zijn.

HTTP: Basic Auth en de WebSocket-handshake

Twee header-bouwtaakjes waar PHP de Base64 doet en het protocol de rest.

HTTP Basic auth (RFC 7617): de client stuurt Authorization: Basic plus de Base64 van username:password. Bouwen is één tekenreeks-concatenatie:

function basic_authorization_header(string $username, string $password): string
{
  return 'Basic ' . base64_encode($username . ':' . $password);
}
$headers[] = 'Authorization: ' . basic_authorization_header('alice', 'secret123');

Zeg het hardop één keer, want de RFC dwingt je ertoe: dit is codering, geen bescherming. Iedereen met een packetcapture herstelt beide helften met één toetsaanslag, dus Basic auth hoort alleen thuis op HTTPS-verbindingen.

De WebSocket-handshake (RFC 6455): de server bewijst dat hij de client hoorde door een getransformeerde sleutel terug te echoën. Die voegt de Sec-WebSocket-Key van de client samen met een vaste magic GUID, neemt de SHA-1 van het resultaat en codeert de digest in Base64. Dit is standaard Base64, vulling incluis, want het woont in een header, niet in een URL:

function websocket_accept_key(string $clientKey): string
{
  return base64_encode(hash('sha1', $clientKey . '258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
}
$accept = websocket_accept_key('dGhlIHNhbXBsZSBub25jZQ==');
var_dump($accept); // string(28) "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="

Dit voorbeeld is diegene uit de RFC zelf, wat hem een handige zelftest maakt: als je implementatie dezelfde 28 tekens produceert, dan spreekt de WebSocket-laag correct.

E-mail: het oorspronkelijke gebruik

Alles andere in dit artikel is een afstammeling van één feit: SMTP was ontworpen om 7-bit ASCII te dragen, en mensen wilden binaire data versturen. Het antwoord van de MIME-standaard, in RFC 2045 sectie 6.8, was Base64 als Content-Transfer-Encoding, met de twee huisregels die je al hebt ontmoet: regels van maximaal 76 tekens, en decoders die elk teken buiten het alfabet negeren. Zo reist een PDF-bijlage dan:

$pdf = file_get_contents('/var/www/uploads/report.pdf');
$attachment = chunk_split(base64_encode($pdf), 76, "\r\n");
// de mail-library legt nu $attachment in het MIME-deel,
// met Content-Transfer-Encoding: base64

De praktische getallen: de codering zelf kost 33 procent, en de CRLF elke 76 tekens kost er een beetje bij, dus een bijlage van 100 KB reist af als ongeveer 137 KB tekst. Als je e-mail stuurt vanuit PHP, doen de libraries (PHPMailer en zijn stabiele verwanten) de omwikkeling voor je, en reik je hen de ruwe binary aan. Als je ooit een muur van letters, 76 tekens breed, ziet in een rauw .eml-bestand, weet je nu het exacte algoritme dat die produceerde.

PEM-omhulling voor sleutels en certificaten

Sleutels en certificaten hebben meer nodig dan een muur van letters; ze hebben labels nodig. PEM-omhulling is een BEGIN-regel, een Base64-blok omwikkeld in regels van 64 tekens, en een END-regel, een conventie geërfd van Privacy-Enhanced Mail (RFC 1421) en in leven gehouden door OpenSSL. De openssl-extensie van PHP produceert en consumeert deze vorm direct:

$res = openssl_pkey_new([
  'private_key_bits' => 2048,
  'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($res, $pem);
// $pem is al omhuld: BEGIN-label, regels van 64 tekens, END-label

Het interessante geval is wanneer de omhulling handmatig moet worden herbouwd, bijvoorbeeld wanneer je ruwe DER-bytes van een API ontvangt en een PEM-bestand nodig hebt voor een tool die alleen PEM leest. De conventie is 64 tekens per regel, LF-regeleindes, en een label dat de inhoud benoemt:

$rearmored = "-----BEGIN CERTIFICATE-----\n"
  . chunk_split(base64_encode($der), 64, "\n")
  . "-----END CERTIFICATE-----\n";
var_dump(openssl_x509_parse($rearmored) !== false); // bool(true): de omhulling is geldig

Krijg het label fout en het bestand is rommel, hoe perfect de Base64 ook is. En krijg de regellengte fout en de meeste tools lezen het nog steeds, want decoders negeren regeleindes, maar diff-tools en mensen lijden. Zesenvierentig is het getal.

Config-bestanden, omgevingsvariabelen en databases

Base64 is een tekstcontainer, en dat maakt hem een smokkeltool voor waarden die anders hun container zouden breken. Een database-DSN vol met puntkomma's en aanhalingstekens, een JWT in een .env-bestand, een binaire blob in een TEXT-kolom: ze worden allemaal één lange veilige tekenreeks.

De omgevingsvariabele-variant is een ritueel in twee stappen. Een keer, op de machine die de config bouwt, codeer je:

echo 'DB_DSN_B64=' . base64_encode('pg:host=db;password=qu"ote') . PHP_EOL;

Daarna, bij elke opstart van de applicatie, decodeer en valideer je, zodat een half geplakte config luid faalt in plaats van cryptisch:

$dsn = base64_decode(getenv('DB_DSN_B64') ?: '', true);
if ($dsn === false) {
  exit('DB_DSN_B64 is not valid Base64.');
}

Voor databases slaat dezelfde gedachte binaire data op in tekstkolommen. De groottefactuur geldt: de opgeslagen waarde is ongeveer 33 procent groter dan de blob, dus een bestand van 1 MB belegt ruwweg 1.33 MB in de kolom, en je kiest het kolentype met die factor in gedachten. En dezelfde waarschuwing als overal: dit is formatveiligheid, geen geheimhouding. Iedereen die de config kan lezen of de kolom kan opvragen, kan het in één aanroep terugdraaien. Als de waarde gevoelig is, encrypteer hem; Base64 maakt hem alleen draagbaar.

Grote data en stabiel geheugen

Coderen is de richting die je kost: de uitvoer is een derde groter dan de invoer, dus een binaire van 2 GB wil 2.66 GB aan gecodeerde tekenreeks in het geheugen. Op een langlevend webproces of een host met krappe geheugenlimieten is dat reden om te streamen in plaats van te slurpen, en PHP geeft je twee manieren.

De eerste manier is de convert.base64-encode-streamfilter, de streaming-twin van de functie. Die ondersteunt parameters als een assocatieve array: line-length voor de omwikkelbreedte en line-break-chars voor de separator, wat het effect van chunk_split() reproduceert zonder de hele tekenreeks te hoeven houden:

$in = fopen('/var/www/uploads/big.bin', 'rb');
$out = fopen('/var/www/uploads/big.b64', 'wb');
stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, [
  'line-length' => 64,
  'line-break-chars' => "\n",
]);
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);

De tweede manier is de klassieke 57-byte-truc, en die is een klein stukje PHP-legende. Een MIME-regel van 76 tekens bevat exact 57 bytes oorspronkelijke data, dus als je het invoerbestand leest in chunks van een veelvoud van 57 bytes, codeert elke chunk onafhankelijk, zonder resterende bits die je tussen chunks moet dragen. Lezen in chunks van 8151 bytes (57 keer 143: 143 volledige regels van 76 tekens uitvoer, dicht bij de traditionele I/O-buffer van 8192 bytes van PHP) houdt het geheugen vlak terwijl het bestand MIME-perfect uitgestroomd wordt:

$in = fopen('/var/www/uploads/big.bin', 'rb');
$out = fopen('/var/www/uploads/big.b64', 'wb');
while (!feof($in)) {
  $plain = fread($in, 57 * 143);
  $encoded = chunk_split(base64_encode($plain), 76, "\r\n");
  fwrite($out, $encoded);
}
fclose($in);
fclose($out);

Welke kies je? De filter wanneer je PHP het binnenwerk wilt laten bezitten en je niet om de exacte chunkranden geeft; de 57-byte-loop wanneer je deterministische MIME-uitvoer, voortgangshooks of een harde kap op de buffergrootte wilt. In beide gevallen blijft de geheugenvoetafdruk bij één chunk, niet bij één bestand.

Valkuilen met een PHP-accent

De vallen die je in echte PHP-codebases tegenkomt, op één plek verzameld:

  • Dubbele codering. De klassieker: een waarde die al Base64 is (uit een omgevingsvariabele, een database, een eerder script) gaat nog een keer door base64_encode(), omdat niemand gecontroleerd heeft. Het resultaat decodeert één keer en levert op... weer Base64. De genezing is een round-trip-check aan de grens, of één bekende functie die alle codering in de codebase in handen heeft.
  • De + in URLs. De standaarduitvoer bevat + en /. In een form-gecodeerde query string wordt de plus een spatie voordat je code hem ziet; in een pad is hij een separator. Voor alles wat aan een URL gebonden is: geef base64url uit, of percent-codeer de hele waarde met rawurlencode().
  • De separator aan het eind. chunk_split() eindigt zijn resultaat met de separator, ook bij exacte veelvouden van de regellengte. Een lege regel aan het eind is meestal onschuldig (decoders negeren hem), maar hij laat naïeve regel-tellers en diff-tools struikelen. rtrim() de separator als de afnemer kieskeurig is.
  • Omwikkel-mismatch. Schrijven met MIME-regels van 76 tekens terwijl de afnemer PEM-regels van 64 tekens verwacht (of andersom), is geen decoding-probleem, want decoders negeren regeleindes, maar het is wel een regeltools- en leesbaarheidsprobleem voor mensen. Kies de conventie die je bestemming verwacht en houd je eraan.
  • Het regeleinde achteraf is data. base64_encode() codeert elke byte, inclusief een regeleinde aan het eind van een tekstbestand. Wanneer twee systemen voor dezelfde ogenschijnlijk tekst 'anders' Base64 produceren, is een regeleinde \n aan het eind de gebruikelijke verdachte.
  • Base64 is geen encryptie. Een wachtwoord coderen vóórdat het de database bereikt, beschermt het niet; het formatteert het. De 'geëncrypteerde' kolom ligt voor iedereen met query-toegang op één functie-aanroep van plaintext. Encrypteer of hash echte secrets; Base64 is een transport-kostuum.
  • Geheugen is een derde groter. Op een 32-bit PHP-build of een host met krappe geheugenlimieten kan het coderen van een grote binary ronduit falen. Stream het, zoals hierboven getoond, vóórdat je aan memory_limit gaat sleutelen.
  • Witergels worden nooit toegevoegd, nooit. 'MIME base64' in de functieomschrijving betekent niet 'MIME-omwikkelde uitvoer'. Als je uitvoer regels van 76 tekens nodig heeft, voeg ze dan toe met chunk_split() of de filter.

Een korte geschiedenis van base64_encode

De coderingskant van het PHP-verhaal is bijna verfrissend saai, in de beste zin van het woord. base64_encode() arriveerde in PHP 4 als kernfunctie met één parameter en geen opties, en het heeft er sindsdien niet één bijgekregen. Strict-modus was nooit nodig (als jij de data zelf produceert, is er niets waar je streng over kunt zijn), een vullingsoptie is nooit toegevoegd, en het omwikkelwerk werd vanaf dag één aan chunk_split() uitbesteed. Daarom zitten de twee functies nog steeds samen in de 'See Also'-lijsten van de handleiding.

De handleiding draagt het cijfer van 33 procent zo lang als iemand zich herinnert: 'Base64-gecodeerde data neemt ongeveer 33% meer ruimte in dan de oorspronkelijke data.' Die zin staat er vandaag nog, en dat is de reden dat het getal in dit artikel überhaupt verschijnt. De streamfilter convert.base64-encode kwam er later bij, en die had een eigen bug waaruit het moest groeien: in 2015 fixede PHP een defect (bug #68532) waarbij de filter, in leesmodus op geheugenstreams, het laatste vulteken kon laten vallen. Dat is precies het soort stille corruptie waar de functie-gebaseerde route nooit last van heeft. PHP 8.0 voegde de native string-parameter- en teruggeeftypes toe, en daar eindigt de changelog. Eén functie, één parameter, twintig jaar, nul opties: een monument voor het oppervlak de eerste keer goed krijgen.

Grappige PHP-feiten

Omdat een referentie onvolledig is zonder zijn rare hoekjes:

  • De lege identiteit. base64_encode('') is ''. Geen vulling, geen uitvoer, geen verrassingen: leeg erin, leeg eruit.
  • Een vreemd adres. De PHP-handleiding archiveert base64_encode() onder 'URLs' in het 'Overige basisextensies'-boek. Er is geen 'encoding'-hoofdstuk; 'URLs' is waar je het vindt, naast parse_url().
  • Het alfabet is nooit verhuisd. Dezelfde 64 tekens komen al sinds PHP 4 uit de encoder van PHP. Een Base64-tekenreeks die in 2001 door een PHP 4-script op een Windows-machine werd geproduceerd, decodeert vandaag identiek op PHP 8.4 op Linux. Dat is interoperabiliteit met een track record van 25 jaar.
  • Één byte en drie bytes zien er qua lengte hetzelfde uit. Beide leveren vier tekens op; alleen de vulling houdt ze uit elkaar. Daarom bestaat de groottetabel in dit artikel.
  • Vijfenzeventig is een magisch getal. Een MIME-regel van 76 tekens bevat exact 57 bytes oorspronkelijke data, en dat toeval is wat de streaming chunk-loop in de grote-data-sectie mogelijk maakt zonder status te hoeven dragen tussen de leestjes.
  • Er is een dial-up-broer. De 'See Also' van base64_encode() vermeldt zelfs convert_uuencode(), de PHP-wrapper voor uuencode, het formaat dat binaries voor e-mail codeerde vóórdat MIME Base64 standaardmaakte. Die woont in het String Functions-hoofdstuk, maar het is het fossiele spoor van het oorspronkelijke doel van deze functie.
  • Oude browsers waren kieskeurig over vulling. Een php.net-notitie uit 2004 rapporteert dat Internet Explorer cookie-namen met een = weigerde, daarom schraapt veteraan-code soms de vultekens aan het eind weg uit Base64 dat in cookies wordt opgeslagen. Moderne opstellingen hebben die truc niet nodig, maar die verklaart de rare rtrim($x, '=')-aanroepen die je kunt overerven.

De keerzijde

Dat is de coderingskant, en het is de makkelijker van de twee: de functie kan niet falen, de uitvoer is deterministisch, en het formaat is iets wat je van begin tot eind zelf controleert. De moeilijkere richting is die waarin je andermans Base64 ontvangt: hun vullingskeuzes, hun regeleindes, hun URL-safe dialecten, hun beschadigde plaksels. Daar heeft de decoder strict-modus, een validatiepijplijn en een gezonde scepsis jegens alles nodig. Base64-decodering in PHP, gelinkt vanaf deze pagina, behandelt de decoderkant in dezelfde diepte.

Laatst bijgewerkt: 2026-10-06

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