Enviar um arquivo
  1. base64hub
  2. Base64 no código
  3. PHP
Código

Base64 em PHP

O PHP tem duas funções, base64_encode e base64_decode, mais um filtro de stream para arquivos grandes. Todas as saídas abaixo vêm do PHP 8.4.

Codificar e decodificar uma string

As strings do PHP são strings de bytes, então o base64_encode codifica os bytes que a string tiver. Um arquivo-fonte salvo em UTF-8 dá Base64 de UTF-8 sem nenhuma etapa de conversão:

<?php
$enc = base64_encode("héllo wörld");
echo $enc;                  // aMOpbGxvIHfDtnJsZA==
echo base64_decode($enc);   // héllo wörld

Se o texto vem de um banco de dados ou de um formulário em outro charset, converta-o antes com mb_convert_encoding($s, 'UTF-8', 'Windows-1252'). Caso contrário, o Base64 fica correto para aqueles bytes, mas vira mojibake ao ser decodificado em outro lugar. A ferramenta Base64 para texto permite testar charsets diferentes no resultado.

Modo estrito: base64_decode($s, true)

Sem o segundo argumento, o base64_decode joga fora qualquer caractere que não esteja no alfabeto e decodifica o resto. Com true, ele retorna false:

var_dump(base64_decode("aGk=!!"));        // string(2) "hi"
var_dump(base64_decode("aGk=!!", true));  // bool(false)

Sempre compare com ===, porque uma entrada vazia válida decodifica para "", que é avaliado como falso:

$bytes = base64_decode($input, true);
if ($bytes === false) {
    throw new InvalidArgumentException('Invalid Base64');
}

O modo estrito é menos rígido do que o nome sugere. Testado no PHP 8.4:

EntradaResultado no modo estrito
aGk (sem preenchimento)"hi"
"aGk=\n" (quebra de linha no final)"hi"
aGl= (bits de preenchimento diferentes de zero)"hi"
aGk== (preenchimento demais)false
a (comprimento impossível)false
PDw_Pz8-Pg== (caracteres URL-safe)false

Se você só aceita Base64 canônico, remova os espaços em branco, decodifique no modo estrito e depois confira base64_encode($bytes) === $input. O validador de Base64 faz as mesmas verificações e aponta o caractere ruim.

Base64 URL-safe com e sem preenchimento

O PHP não tem função Base64URL embutida. Troque os dois caracteres com strtr, que é mais rápido que str_replace para mapeamentos de um caractere:

function b64url_encode(string $data, bool $pad = false): string {
    $s = strtr(base64_encode($data), '+/', '-_');
    return $pad ? $s : rtrim($s, '=');
}

function b64url_decode(string $s): string|false {
    return base64_decode(strtr($s, '-_', '+/'), true);
}

echo b64url_encode("<<???>>");        // PDw_Pz8-Pg
echo b64url_encode("<<???>>", true);  // PDw_Pz8-Pg==
echo b64url_decode("PDw_Pz8-Pg");     // <<???>>

Como o modo estrito tolera a falta de preenchimento, não é preciso recolocar os = antes de decodificar. Mais sobre as diferenças em Base64 vs Base64URL.

Codificar e decodificar um arquivo

echo base64_encode(file_get_contents("hello.txt"));   // aGVsbG8K

file_put_contents("copy.txt", base64_decode("aGVsbG8K", true));

Para um data URI, acrescente o tipo MIME na frente: 'data:image/png;base64,' . base64_encode($png). O mime_content_type($path) detecta o tipo se o fileinfo estiver habilitado.

Arquivos grandes com filtros de stream

O file_get_contents carrega o arquivo inteiro e a cópia em Base64, então o uso de memória fica em cerca de 2,3 vezes o tamanho do arquivo. O filtro de stream convert.base64-encode codifica enquanto copia:

$in  = fopen("big.bin", "rb");
$out = fopen("big.b64", "wb");
stream_filter_append($out, "convert.base64-encode");
stream_copy_to_stream($in, $out);
fclose($in);
fclose($out);

A decodificação funciona do mesmo jeito, ou numa linha só, por uma URL php://filter:

copy("php://filter/read=convert.base64-decode/resource=big.b64", "big.out");

Os dois geraram saída idêntica à das funções em memória num arquivo de teste de 300 KB. Um laço manual também funciona se cada bloco tiver um múltiplo de 3 bytes, por exemplo fread($in, 3 * 8192). Qualquer outro tamanho insere = no meio da saída.

Quebra de linha MIME

Corpos de e-mail precisam de linhas de no máximo 76 caracteres. O chunk_split faz exatamente isso, com finais de linha \r\n por padrão:

echo chunk_split(base64_encode(str_repeat("x", 60)));
// eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4
// eHh4

Ele também acrescenta um \r\n no final. As bibliotecas de e-mail fazem isso por você; o codificador Base64 MIME mostra o formato.

Armadilhas comuns

  • Esquecer o modo estrito com entrada não confiável. Dados ruins viram bytes aleatórios e falham num lugar bem longe da causa.
  • Entrada URL-safe no modo padrão. base64_decode("PDw_Pz8-Pg==") retorna string(6) "<<���", não <<???>>. Traduza com strtr antes.
  • Passar null. No PHP 8.1+ você recebe Deprecated: base64_encode(): Passing null to parameter #1 ($string) of type string is deprecated. Passar um array lança TypeError: base64_encode(): Argument #1 ($string) must be of type string, array given.
  • + virando espaço. Base64 padrão numa query string é estragado pela decodificação de URL, porque ali + significa espaço. Use a variante URL-safe ou rawurlencode.
  • Usar como armazenamento de senhas. Base64 não esconde nada. Use password_hash para senhas e veja codificação vs criptografia.

Notas de versão

  • base64_encode, base64_decode e a flag $strict existem em todas as versões suportadas do PHP (o modo estrito chegou no 5.2).
  • Desde o PHP 8.0, o base64_decode é declarado com retorno string|false, e passar um array ou objeto lança TypeError em vez de um aviso.
  • O filtro convert.base64-encode aceita as opções line-length e line-break-chars para saída MIME, por exemplo stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"]).

Perguntas frequentes

Para que serve o segundo parâmetro do base64_decode?

base64_decode($s, true) ativa o modo estrito: a função retorna false quando a entrada contém caracteres fora do alfabeto Base64. Sem ele, esses caracteres são descartados em silêncio.

O PHP tem uma função base64url_encode?

Não. Use rtrim(strtr(base64_encode($data), '+/', '-_'), '=') para codificar e base64_decode(strtr($s, '-_', '+/'), true) para decodificar. O modo estrito aceita a falta de preenchimento.

O modo estrito do base64_decode é um validador completo?

Não. Ele ainda aceita espaços em branco, preenchimento ausente e bits de preenchimento diferentes de zero, como em aGl=. Para uma validação exata, recodifique o resultado e compare com a entrada normalizada.

Por que o base64_decode retorna lixo em vez de false?

Provavelmente você o chamou sem o true. No modo padrão, base64_decode('PDw_Pz8-Pg==') descarta o _ e o - e retorna seis bytes errados. Traduza a entrada URL-safe com strtr antes.

Continue lendo