Codificação Base64 em PHP: um guia completo
Você tem dados que precisam sobreviver a um canal que não os quer. Um blob binário que precisa morar num campo JSON. Uma imagem que precisa viver dentro de uma tag HTML. Um certificado que pertence a um arquivo de configuração. Um token que vai viajar por URLs, headers e cookies. Esta é a vida diária da codificação Base64: ela reescreve cada três bytes de dados brutos como quatro caracteres de um alfabeto de 64 letras, com um ou dois sinais = terminando a cauda, para que o resultado seja texto puro que qualquer coisa possa carregar. A página inicial deste site explica o formato por completo, então este artigo foca no que o PHP te dá, no que ele decide em silêncio por você e em onde estão as armadilhas.
No lado PHP, a história começa com boa notícia: o base64_encode() mora no núcleo desde o PHP 4, ele recebe um argumento, sempre devolve uma string e não pode falhar. Não há modo strict, não há caminho de erro, não há configuração. A codificação é determinística: os mesmos bytes sempre produzem as mesmas letras. O seu trabalho como desenvolvedor não é fazer a função funcionar, é fazer o mundo ao redor se comportar: escolher o alfabeto certo para o destino, adicionar as quebras de linha certas, converter o charset certo e carregar a conta de 33 por cento de tamanho com os olhos abertos. (O Base64 normalmente expande os dados em mais ou menos um terço, quatro caracteres para cada três bytes de entrada; guarde isso no fundo da sua cabeça, porque ele volta sempre.)
Até o fim deste artigo você vai saber como produzir cada sabor de Base64 que um desenvolvedor PHP de fato encontra: saída em linha única, e-mail embrulhado em MIME, chaves embrulhadas em PEM, tokens URL-safe e data URIs, mais os truques de streaming para quando os dados são grandes demais para caber na memória.
Uma função, zero opções
A API inteira, exatamente como o PHP moderno a reporta:
base64_encode(string $string): string
Leia de novo. Um parâmetro, um valor de retorno, nenhum flag. O manual a descreve como base64 MIME, "desenhado para fazer dados binários sobreviverem ao transporte por camadas de transporte que não são 8-bit clean, como corpos de e-mail". Note o que essa redação não promete: sem quebras de linha, sem embrulho, sem opinião sobre onde a saída vai morar. A função emite uma linha longa, e qualquer embrulho que o destino queira é seu trabalho numa segunda chamada. Desde o PHP 8.0, a assinatura carrega tipos nativos; desde o PHP 8.1, passar null gera um aviso de deprecação, então coalesça qualquer valor nullável para '' antes.
O tamanho da saída segue um padrão fixo que você pode prever antes de chamar:
| Bytes de entrada | Caracteres de saída | Padding |
|---|---|---|
| 0 | 0 | nenhum |
| 1 | 4 | dois = |
| 2 | 4 | um = |
| 3 | 4 | nenhum |
| 3,000,000 | 4,000,000 | nenhum |
| 100,000 | 133,336 | dois = |
O padrão é quatro caracteres para cada grupo completo de três bytes, mais um grupo parcial final preenchido com um ou dois sinais =. Uma consequência que vale conhecer: um byte e três bytes ambos produzem quatro caracteres, então o comprimento codificado esconde o tamanho exato da entrada. Você pode estimar (divida por quatro, multiplique por três, subtraia os pads), mas não consegue lê-lo com exatidão.
Para onde vão as quebras de linha
Como o base64_encode() nunca quebra a linha sozinho, a decisão do embrulho é um problema de destino. Na prática, existem três respostas.
Sem quebras de linha. A saída crua da função, exatamente uma linha. É isso que você quer para URLs, payloads JSON, headers, valores de banco de dados e qualquer outra coisa onde uma quebra de linha seria um bug. É também o que a maioria das pessoas quer dizer com "me dá só o Base64".
Embrulho MIME: 76 caracteres mais CRLF. A convenção de e-mail do RFC 2045, seção 6.8: linhas codificadas não devem passar de 76 caracteres, e os decodificadores devem ignorar as quebras de linha. A chamada companheira clássica é o chunk_split(), que o manual junta ao base64_encode() na lista "See Also" exatamente por esse motivo:
$binary = file_get_contents('/var/www/uploads/report.pdf');
$wrapped = chunk_split(base64_encode($binary), 76, "\r\n");
Embrulho PEM: 64 caracteres mais LF. Chaves e certificados usam a convenção mais antiga de Privacy-Enhanced Mail (RFC 1421): linhas mais curtas de 64 caracteres. A mesma ferramenta, números diferentes:
$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";
Uma pegadinha do chunk_split() vale para os dois sabores embrulhados: a função acrescenta o separador no final do resultado mesmo quando o comprimento da entrada é um múltiplo exato do comprimento da linha. Se um consumidor a jusante tropeçar numa linha vazia final, é por isso; uma chamada rtrim() no separador resolve. Note também a assimetria que vai te salvar um dia: decodificadores ignoram quebras de linha por completo, então um payload embrulhado em MIME e um sem embrulho decodificam para os mesmos bytes. O embrulho é uma cortesia para ferramentas baseadas em linha e para humanos, não uma diferença de semântica.
Tornando a saída segura para URL
O alfabeto padrão inclui + e /, e os dois causam problema fora de um arquivo de texto. Um + numa query string form-encoded vira espaço antes da sua aplicação vê-lo, e / é um separador de caminho em URLs. Nomes de arquivo e tokens têm suas próprias reclamações. O RFC 4648, seção 5, resolve isso com o alfabeto seguro para URL e nome de arquivo: + vira -, / vira _, e o padding = no final costuma ser descartado. O RFC insiste que isso "não deve ser considerado o mesmo que a codificação base64", então trate como um formato distinto, comumente chamado de base64url.
Produzi-lo é duas operações de string:
function base64url_encode(string $data): string
{
return rtrim(strtr(base64_encode($data), '+/', '-_'), '=');
}
var_dump(base64url_encode("hi>?there\x00bin")); // string(18) "aGk-P3RoZXJlAGJpbg"
Quando usar: partes de JSON Web Token, parâmetros de state e nonce do OAuth, IDs de API que você coloca em caminhos de URL e qualquer coisa que vai ser copiada numa barra de endereço ou num nome de arquivo. Quando não usar: corpos de e-mail, armadura PEM e qualquer lugar onde um consumidor de alfabeto padrão está do outro lado, porque - e _ não estão no vocabulário deles. E não misture os dois alfabetos em silêncio: um token codificado URL-safe deve ser decodificado URL-safe, em todo lugar, para sempre. Essa é a regra inteira de interoperabilidade do base64url.
Unicode e os bytes que você quis
Strings do PHP são sequências de bytes, e o base64_encode() codifica os bytes que recebe sem perguntar o que significam. Isso é um recurso até o dia em que o seu "texto" não é, na verdade, a codificação que você pensa que é. A falha clássica: uma string que parece UTF-8 no seu editor, mas chegou de uma fonte legado como Windows-1252. Codifique esses bytes como estão e o receptor, que vai decodificar e assumir UTF-8, recebe mojibake no lugar das suas letras acentuadas.
A correção é normalizar antes de codificar, com a extensão mbstring (ela vem com o fonte do PHP, mas não é habilitada por padrão):
$fromLegacy = "caf\xE9 au lait"; // bytes Windows-1252: o é é 0xE9
$utf8 = mb_convert_encoding($fromLegacy, 'UTF-8', 'Windows-1252');
$encoded = base64_encode($utf8);
var_dump($utf8); // string(13) "café au lait": agora o é é dois bytes UTF-8
Se a fonte já for UTF-8, você pode pular a conversão, e um cheque de sanidade barato é mb_check_encoding($utf8, 'UTF-8'). Uma frase de conselho: nunca tente "consertar" uma string Base64 já codificada re-codificando-a como texto. Essa é a armadilha de codificação em dobro da seção de armadilhas abaixo, e é o bug de Base64 mais comum em codebases PHP.
Arquivos, blobs e a convenção .b64
O trabalho de codificação mais direto: um arquivo vira texto. Strings do PHP são bytes, então não há "modo binário" para se preocupar; o file_get_contents() te entrega os bytes exatos e o base64_encode() te entrega o texto exato:
$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)); // a conta dos 33%, sempre
Dois hábitos mantêm isso seguro. Primeiro, saiba o que você está codificando. A classe finfo (extensão fileinfo, que vem nos builds padrão do PHP) te diz o tipo real a partir dos bytes, não do nome do arquivo:
$mime = (new finfo(FILEINFO_MIME_TYPE))->file('/var/www/uploads/photo.png');
var_dump($mime); // string(9) "image/png"
Segundo, lembre da conta de tamanho ao planejar o armazenamento: uma imagem de 500 KB vira um arquivo de texto de 670 KB, e um vídeo de 1 GB vira um arquivo de texto de 1,33 GB. É por isso que a seção de dados grandes abaixo existe.
Data URIs: colocando uma imagem na página
Uma data URI embute o payload direto na URL, então nenhuma segunda requisição é necessária para buscá-lo. O RFC 2397 define o formato: data:, um media type opcional, um flag ;base64 opcional, uma vírgula e os dados. Para mídias binárias como imagens, a flag está presente, então o payload é exatamente o que o base64_encode() produziu:
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";
Por que Base64 aqui? Porque uma URI não pode conter com segurança bytes crus ou vírgulas, e o alfabeto Base64 não precisa de nenhum escapamento. As desvantagens são reais, porém. O payload codificado é cerca de 33 por cento maior que o arquivo, o que deixa o próprio documento HTML maior. Navegadores não fazem cache de uma data URI do mesmo jeito que fazem com uma URL de arquivo, então cada visualização de página baixa os bytes de novo. E o próprio RFC diz que data URIs só são úteis para valores curtos; parsers HTML antigos tinham limites duros no comprimento de atributo, e navegadores modernos, embora muito mais generosos, também não agradecem megabytes dentro de uma tag. Use para avatares, ícones e gráficos pequenos inline; use arquivos de verdade para tudo o mais.
JWTs e tokens de API
JSON Web Tokens são o principal consumidor de Base64 em APIs modernas, e eles usam o dialeto URL-safe sem padding da seção acima. Segundo o RFC 7519, um JWT compacto é três partes base64url separadas por pontos: header, payload, assinatura. O header e o payload são JSON puro; a assinatura é bytes crus. Construir um na mão é uma maneira agradável de ver cada peça em movimento:
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;
Dois pontos para notar. A assinatura é a codificação base64url de bytes HMAC crus, que é o motivo pelo qual o hash_hmac() é chamado com true para saída crua. E o header e o payload são legíveis por qualquer um, o que é por design: um JWT é um tíquete assinado, não um segredo. Para produção, você não assina nem verifica na mão. O pacote da comunidade é o firebase/php-jwt (v7, exige PHP 8.0 ou mais novo), instalado com o 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, assinatura
Nota de versão para a linha v7 da biblioteca: os algoritmos HMAC aplicam um comprimento mínimo de chave, então um segredo HS256 com menos de 32 bytes é rejeitado antes de qualquer codificação acontecer. Segredos longos já são a norma; isso só faz a biblioteca recusar tratar isso com calma.
A biblioteca cuida da conversão base64url, da assinatura e das verificações de expiração por você, e lança exceções tipadas em vez de retornar dados só meio confiáveis. Quando você escreve tokens com ela, nunca toca no base64_encode() diretamente, o que é exatamente como deveria ser.
HTTP: Basic Auth e o aperto de mão WebSocket
Dois trabalhos de construção de header em que o PHP faz o Base64 e o protocolo faz o resto.
HTTP Basic Auth (RFC 7617): o cliente envia Authorization: Basic mais o Base64 de username:password. Construí-lo é uma concatenação de string:
function basic_authorization_header(string $username, string $password): string
{
return 'Basic ' . base64_encode($username . ':' . $password);
}
$headers[] = 'Authorization: ' . basic_authorization_header('alice', 'secret123');
Diga em voz alta uma vez, porque o RFC te obriga: isso é codificação, não proteção. Qualquer um com uma captura de pacotes recupera as duas metades numa tecla, então a Basic auth só cabe em conexões HTTPS.
O aperto de mão WebSocket (RFC 6455): o servidor prova que ouviu o cliente ecoando uma chave transformada. Ele concatena o Sec-WebSocket-Key do cliente com um GUID mágico fixo, toma o SHA-1 do resultado e codifica o digest em base64. Isso é Base64 padrão, padding incluído, porque vive num header, não numa 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="
Esse exemplo é o do próprio RFC, o que o torna um autoteste útil: se a sua implementação produz os mesmos 28 caracteres, a camada WebSocket está falando certo.
E-mail: o caso de uso original
Tudo o mais neste artigo é descendente de um fato: o SMTP foi desenhado para carregar ASCII de 7 bits, e as pessoas queriam enviar binários. A resposta do padrão MIME, na seção 6.8 do RFC 2045, foi o Base64 como Content-Transfer-Encoding, com as duas regras da casa que você já conheceu: linhas de no máximo 76 caracteres e decodificadores que ignoram todo caractere fora do alfabeto. Então um anexo PDF viaja assim:
$pdf = file_get_contents('/var/www/uploads/report.pdf');
$attachment = chunk_split(base64_encode($pdf), 76, "\r\n");
// a biblioteca de e-mail agora coloca $attachment na parte MIME,
// com Content-Transfer-Encoding: base64
Os números práticos: a codificação em si custa 33 por cento, e o CRLF a cada 76 caracteres custa um pouco mais, então um anexo de 100 KB viaja como cerca de 137 KB de texto. Quando você escreve e-mail a partir do PHP, as bibliotecas (PHPMailer e seus parentes estáveis) fazem o embrulho por você, e você entrega a elas o binário cru. Se um dia você vir um muro de letras com 76 caracteres de largura num arquivo .eml cru, agora você sabe o algoritmo exato que o produziu.
Armadura PEM para chaves e certificados
Chaves e certificados precisam de mais do que um muro de letras; precisam de rótulos. A armadura PEM é uma linha BEGIN, um bloco Base64 quebrado a 64 caracteres e uma linha END, uma convenção herdada da Privacy-Enhanced Mail (RFC 1421) e mantida viva pelo OpenSSL. A extensão openssl do PHP produz e consome esse formato diretamente:
$res = openssl_pkey_new([
'private_key_bits' => 2048,
'private_key_type' => OPENSSL_KEYTYPE_RSA,
]);
openssl_pkey_export($res, $pem);
// o $pem já está armado: rótulo BEGIN, linhas de 64 chars, rótulo END
O caso interessante é quando a armadura tem que ser refeita na mão, por exemplo quando você recebe bytes DER crus de uma API e precisa de um arquivo PEM para uma ferramenta que só lê PEM. A convenção é 64 caracteres por linha, quebras LF e um rótulo que nomeia o conteúdo:
$rearmored = "-----BEGIN CERTIFICATE-----\n"
. chunk_split(base64_encode($der), 64, "\n")
. "-----END CERTIFICATE-----\n";
var_dump(openssl_x509_parse($rearmored) !== false); // bool(true): a armadura é válida
Errar o rótulo e o arquivo é lixo, não importa o quão perfeito seja o Base64. E errar o comprimento da linha e a maioria das ferramentas vai ler mesmo, porque decodificadores ignoram quebras, mas ferramentas de diff e humanos vão sofrer. Sessenta e quatro é o número.
Arquivos de configuração, variáveis de ambiente e bancos de dados
Base64 é um contêiner de texto, o que dele faz uma ferramenta de contrabando para valores que quebrariam o próprio contêiner. Um DSN de banco cheio de ponto-e-vírgulas e aspas, um JWT num arquivo .env, um blob binário numa coluna TEXT: todos eles viram uma string longa e segura.
O sabor de variável de ambiente é um ritual de duas etapas. Uma vez, na máquina que monta a configuração, você codifica:
echo 'DB_DSN_B64=' . base64_encode('pg:host=db;password=qu"ote') . PHP_EOL;
Depois, em cada inicialização da aplicação, você decodifica e valida na startup, para que uma configuração colada pela metade falhe alto em vez de falhar de forma obscura:
$dsn = base64_decode(getenv('DB_DSN_B64') ?: '', true);
if ($dsn === false) {
exit('DB_DSN_B64 is not valid Base64.');
}
Para bancos de dados, a mesma ideia armazena binário em colunas de texto. A conta de tamanho vale: o valor armazenado é cerca de 33 por cento maior que o blob, então um arquivo de 1 MB ocupa cerca de 1,33 MB na coluna, e você deveria escolher o tipo de coluna tendo isso em mente. E o mesmo aviso de todo lugar: isso é segurança de formato, não segredo. Qualquer um que possa ler a configuração ou consultar a coluna consegue reverter numa chamada. Se o valor for sensível, criptografe-o; o Base64 só o torna portátil.
Dados grandes e memória estável
Codificar é a direção que te cobra: a saída é um terço maior que a entrada, então um binário de 2 GB quer 2,66 GB de string codificada na memória. Num processo web de longa vida ou num host com memória limitada, esse é um motivo para streamar em vez de engolir tudo, e o PHP te dá dois caminhos.
O primeiro caminho é o stream filter convert.base64-encode, o gêmeo de streaming da função. Ele suporta parâmetros como um array associativo: line-length para a largura do embrulho e line-break-chars para o separador, o que reproduz o efeito do chunk_split() sem segurar a string inteira:
$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);
O segundo caminho é o truque clássico dos 57 bytes, e é uma pequena lenda do PHP. Uma linha MIME de 76 caracteres guarda exatamente 57 bytes de dados originais, então se você ler o arquivo de entrada em chunks de um múltiplo de 57 bytes, cada chunk codifica independentemente, sem restos para carregar entre chunks. Ler em chunks de 8151 bytes (57 vezes 143: 143 linhas completas de 76 caracteres de saída, perto do buffer tradicional de I/O de 8192 bytes do PHP) mantém a memória plana enquanto o arquivo sai streamando, perfeito em MIME:
$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);
Qual escolher? O filter quando você quer que o PHP cuide da canalização e não se importa com as fronteiras exatas dos chunks; o loop de 57 bytes quando quer saída MIME determinística, ganchos de progresso ou um teto duro no tamanho do buffer. Qualquer um dos dois, a pegada de memória fica num chunk, não num arquivo.
Armadilhas com sotaque PHP
As armadilhas que aparecem em codebases PHP de verdade, reunidas num lugar só:
- Codificação em dobro. O clássico: um valor que já é Base64 (de uma variável de env, de um banco de dados, de um script anterior) passa pelo
base64_encode()de novo porque ninguém conferiu. O resultado decodifica uma vez e devolve... mais Base64. O remédio é um cheque de ida e volta na fronteira, ou uma única função bem conhecida que detém toda codificação no codebase. - O
+em URLs. A saída padrão contém+e/. Numa query string form-encoded o+vira espaço antes do seu código vê-lo; num caminho, é um separador. Para qualquer coisa ligada a URL, emita base64url ou codifique o valor inteiro em percent comrawurlencode(). - O separador final. O
chunk_split()termina o resultado com o separador mesmo em múltiplos exatos do comprimento da linha. Uma linha vazia final geralmente é inofensiva (decodificadores ignoram), mas ela derruba contadores de linha ingênuos e ferramentas de diff.rtrim()o separador se o consumidor for chato. - Embrulho incompatível. Escrever com linhas MIME de 76 caracteres e ter um consumidor esperando linhas PEM de 64 caracteres (ou o inverso) não é um problema de decodificação, porque decodificadores ignoram quebras, mas é um problema de ferramentas de linha e de leitura humana. Escolha a convenção que o seu destino espera e cumpra.
- A quebra de linha final é dado. O
base64_encode()codifica cada byte, incluindo a quebra de linha no fim de um arquivo de texto. Quando dois sistemas produzem Base64 "diferentes" para o mesmo texto de cara, um\nfinal é o suspeito habitual. - Base64 não é criptografia. Codificar uma senha antes dela chegar ao banco não a protege; a formata. A coluna "criptografada" está a uma chamada de distância do texto plano para qualquer um com acesso de consulta. Cifre ou hasheie segredos de verdade; o Base64 é um traje de transporte.
- Memória um terço maior. Num build PHP de 32 bits ou num host com limites de memória apertados, codificar um binário grande pode falhar de cara. Stream, como mostrado acima, antes de ajustar o
memory_limit. - Nenhuma quebra de linha é adicionada, nunca. "MIME base64" na descrição da função não significa "saída embrulhada em MIME". Se a sua saída precisa de linhas de 76 caracteres, você as adiciona com
chunk_split()ou o filter.
Uma breve história de base64_encode
O lado da codificação da história do PHP é quase refrescante de chato, da melhor maneira. O base64_encode() chegou no PHP 4 como função de núcleo com um parâmetro e sem opções, e desde então não ganhou nem um. Modo strict nunca foi necessário (não há com o que ser strict quando você é quem produz os dados), opção de padding nunca foi adicionada, e o trabalho de embrulho foi delegado ao chunk_split() desde o primeiro dia, que é o motivo pelo qual as duas funções ainda moram juntas nas listas "See Also" do manual.
O manual carrega a cifra de 33 por cento desde que se tem memória: "dados codificados em Base64 ocupam cerca de 33% a mais de espaço que os dados originais". Essa frase está lá até hoje, e é o motivo pelo qual o número aparece neste artigo. O stream filter convert.base64-encode chegou depois, e teve seu próprio bug para crescer: em 2015, o PHP corrigiu um defeito (bug #68532) em que o filter, em modo de leitura em streams de memória, podia omitir o último caractere de padding, que é exatamente o tipo de corrupção silenciosa do qual o caminho baseado em função nunca padece. O PHP 8.0 adicionou os tipos nativos de parâmetro e retorno string, e é aí que o changelog termina. Uma função, um parâmetro, vinte anos, zero opções: um monumento a acertar a superfície da primeira vez.
Fatos PHP divertidos
Porque uma referência não está completa sem as bizarrices:
- A identidade vazia.
base64_encode('')é''. Sem padding, sem saída, sem surpresas: vazio entra, vazio sai. - Um endereço estranho. O manual do PHP arquiva
base64_encode()sob "URLs" no livro "Outras Extensões Básicas". Não há capítulo de "encoding"; "URLs" é onde você vai encontrá-la, junto comparse_url(). - O alfabeto nunca se moveu. Os mesmos 64 caracteres saem do codificador do PHP desde o PHP 4. Uma string Base64 produzida por um script PHP 4 numa máquina Windows em 2001 decodifica idêntica no PHP 8.4 em Linux hoje. Essa é interoperabilidade com um histórico de 25 anos.
- Um byte e três bytes têm o mesmo comprimento. Ambos produzem quatro caracteres; só o padding os distingue. É por isso que a tabela de tamanho deste artigo existe.
- Cinquenta e sete é um número mágico. Uma linha MIME de 76 caracteres guarda exatamente 57 bytes de dados originais, e é essa coincidência que torna possível o loop de chunks de streaming da seção de dados grandes sem carregar estado entre leituras.
- Ele tem um irmão de discado. O "See Also" do
base64_encode()lista atéconvert_uuencode(), o wrapper do PHP para o uuencode, o formato que codificava binários para e-mail antes do MIME padronizar o Base64. Ele mora no capítulo String Functions, mas é o registro fóssil do propósito original desta função. - Navegadores antigos eram chatos com o padding. Uma nota do php.net de 2004 reporta que o Internet Explorer recusava nomes de cookie contendo
=, que é o motivo pelo qual código veterano às vezes corta os pads finais do Base64 guardado em cookies. Configurações modernas não precisam desse truque, mas ele explica chamadas bizarras dertrim($x, '=')que você pode herdar.
O outro lado
Esse é o lado da codificação, e ele é o mais fácil dos dois: a função não pode falhar, a saída é determinística e o formato é um que você controla de ponta a ponta. A direção mais difícil é aquela em que você recebe o Base64 dos outros: as escolhas de padding deles, as quebras de linha deles, os dialetos URL-safe deles, os colados corrompidos deles. É aí que um decodificador precisa de modo strict, de um pipeline de validação e de uma saudável desconfiança de tudo. Base64 decoding em PHP, linkado a partir desta página, cobre o lado da decodificação na mesma profundidade.
Última atualização: 2026-09-08
Artigo relacionado: Decodificação Base64 em PHP: um guia completo