Загрузить файл
  1. base64hub
  2. Base64 в коде
  3. PHP
Код

Base64 в PHP

В PHP есть две функции, base64_encode и base64_decode, и потоковый фильтр для больших файлов. Все результаты ниже получены на PHP 8.4.

Закодировать и декодировать строку

Строки в PHP – это строки байтов, так что base64_encode кодирует те байты, которые в строке лежат. Если исходный файл сохранён в UTF-8, вы получите Base64 от UTF-8 без всяких преобразований:

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

Если текст пришёл из базы данных или формы в другой кодировке, сначала преобразуйте его через mb_convert_encoding($s, 'UTF-8', 'Windows-1252'). Иначе Base64 будет верным для этих байтов, но в других местах декодируется в кракозябры. В инструменте Base64 в текст можно применить к результату разные кодировки.

Строгий режим: base64_decode($s, true)

Без второго аргумента base64_decode выбрасывает все символы не из алфавита и декодирует остальное. С true функция вместо этого возвращает false:

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

Всегда проверяйте через ===: корректная пустая строка декодируется в "", а это ложное значение:

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

Строгий режим не так строг, как обещает название. Проверено на PHP 8.4:

ВходРезультат в строгом режиме
aGk (нет дополнения)"hi"
"aGk=\n" (перевод строки в конце)"hi"
aGl= (ненулевые биты дополнения)"hi"
aGk== (лишнее дополнение)false
a (невозможная длина)false
PDw_Pz8-Pg== (URL-safe символы)false

Если нужен только канонический Base64, удалите пробельные символы, декодируйте в строгом режиме и проверьте base64_encode($bytes) === $input. Валидатор Base64 выполняет те же проверки и указывает на плохой символ.

URL-safe Base64 с дополнением и без

Встроенной функции для Base64URL в PHP нет. Поменяйте два символа через strtr (для посимвольных замен он быстрее, чем str_replace):

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");     // <<???>>

Поскольку строгий режим терпит отсутствие дополнения, возвращать = перед декодированием не нужно. Подробнее о различиях в статье Base64 и Base64URL.

Закодировать и декодировать файл

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

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

Для data URI добавьте впереди MIME-тип: 'data:image/png;base64,' . base64_encode($png). Если включено расширение fileinfo, тип определит mime_content_type($path).

Большие файлы через потоковые фильтры

file_get_contents загружает в память и весь файл, и его копию в Base64, так что расход памяти получается около 2,3 размера файла. Потоковый фильтр convert.base64-encode кодирует данные прямо при копировании:

$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);

Декодирование устроено так же, а можно уложиться в одну строку через URL php://filter:

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

На тестовом файле в 300 КБ оба способа дали тот же результат, что и функции в памяти. Ручной цикл тоже подойдёт, если каждая часть кратна 3 байтам, например fread($in, 3 * 8192). Любой другой размер вставит = в середину вывода.

Перенос строк MIME

В теле письма строки должны быть не длиннее 76 символов. Ровно это делает chunk_split, по умолчанию с окончаниями строк \r\n:

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

В конце она тоже добавляет \r\n. Почтовые библиотеки делают это за вас; формат показывает кодировщик MIME Base64.

Частые подводные камни

  • Забытый строгий режим для недоверенных данных. Плохие данные декодируются в случайные байты и ломают что-то далеко от места, где возникла проблема.
  • URL-safe строка в режиме по умолчанию. base64_decode("PDw_Pz8-Pg==") возвращает string(6) "<<���", а не <<???>>. Сначала переведите её через strtr.
  • Передача null. В PHP 8.1+ вы получите Deprecated: base64_encode(): Passing null to parameter #1 ($string) of type string is deprecated. Передача массива бросает TypeError: base64_encode(): Argument #1 ($string) must be of type string, array given.
  • + превращается в пробел. Стандартный Base64 в строке запроса портится при URL-декодировании, потому что + там означает пробел. Используйте URL-safe вариант или rawurlencode.
  • Base64 как хранилище паролей. Base64 ничего не скрывает. Для паролей используйте password_hash, а подробнее читайте в статье кодирование и шифрование.

Версии

  • base64_encode, base64_decode и флаг $strict есть во всех поддерживаемых версиях PHP (строгий режим появился в 5.2).
  • С PHP 8.0 base64_decode объявлена как возвращающая string|false, а передача массива или объекта бросает TypeError вместо предупреждения.
  • Фильтр convert.base64-encode принимает параметры line-length и line-break-chars для вывода в стиле MIME, например stream_filter_append($out, 'convert.base64-encode', STREAM_FILTER_WRITE, ['line-length' => 76, 'line-break-chars' => "\r\n"]).

Вопросы и ответы

Что делает второй параметр base64_decode?

base64_decode($s, true) включает строгий режим: функция возвращает false, если во входной строке есть символы вне алфавита Base64. Без него такие символы молча отбрасываются.

Есть ли в PHP функция base64url_encode?

Нет. Для кодирования используйте rtrim(strtr(base64_encode($data), '+/', '-_'), '='), для декодирования – base64_decode(strtr($s, '-_', '+/'), true). Строгий режим допускает отсутствие дополнения.

Строгий режим base64_decode – это полноценная проверка?

Нет. Он всё равно пропускает пробелы, отсутствие дополнения и ненулевые биты дополнения вроде aGl=. Для точной проверки закодируйте результат заново и сравните с нормализованной входной строкой.

Почему base64_decode возвращает мусор, а не false?

Скорее всего, вы вызвали функцию без true. В режиме по умолчанию base64_decode('PDw_Pz8-Pg==') выбрасывает _ и - и возвращает шесть неправильных байтов. Сначала переведите URL-safe строку через strtr.

Читайте также