Закодировать и декодировать строку
Функции Base64 в Python работают с байтами, поэтому текст сначала нужно перевести в UTF-8, а в конце декодировать обратно. Пропустить один из этих шагов – самая частая ошибка.
import base64
s = "héllo wörld"
encoded = base64.b64encode(s.encode("utf-8")).decode("ascii")
print(encoded) # aMOpbGxvIHfDtnJsZA==
decoded = base64.b64decode(encoded).decode("utf-8")
print(decoded) # héllo wörld
b64decode принимает и str, и bytes, а b64encode – только байтоподобные объекты. Если передать строку, получите TypeError: a bytes-like object is required, not 'str'.
Чтобы проверить результат вручную, вставьте его в декодер Base64: он работает локально в браузере.
URL-safe Base64 с дополнением и без
URL-safe алфавит заменяет + на -, а / на _ (см. Base64 и Base64URL). Python сохраняет дополнение =; если целевой формат (JWT, многие API) обходится без него, отрежьте его сами.
data = b"<<???>>"
print(base64.b64encode(data)) # b'PDw/Pz8+Pg=='
print(base64.urlsafe_b64encode(data)) # b'PDw_Pz8-Pg=='
print(base64.urlsafe_b64encode(data).rstrip(b"=")) # b'PDw_Pz8-Pg'
Чтобы декодировать строку без дополнения, его нужно вернуть, потому что Python требует длину, кратную 4:
def b64url_decode(s: str) -> bytes:
return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
print(b64url_decode("PDw_Pz8-Pg")) # b'<<???>>'
b64encode и b64decode принимают также аргумент altchars – двухбайтовую строку, которая заменяет +/. altchars=b"-_" равносилен URL-safe функциям.
print(base64.b64decode("PDw_Pz8-Pg==", altchars="-_")) # b'<<???>>'
Закодировать и декодировать файл
Если файл помещается в память, прочитайте байты и закодируйте их одним вызовом:
from pathlib import Path
b64 = base64.b64encode(Path("hello.txt").read_bytes()).decode("ascii")
print(b64) # aGVsbG8K
Path("hello-copy.txt").write_bytes(base64.b64decode(b64))
Если нужно разово что-то сконвертировать без всякого кода, то же самое в браузере делает инструмент файл в Base64.
Большие файлы и потоки
Base64 превращает каждые 3 входных байта в 4 символа, так что поток можно кодировать по частям, если каждая часть, кроме последней, кратна 3 байтам. Тогда дополнение не появится в середине, а склеенный вывод совпадёт с кодированием за один вызов.
CHUNK = 3 * 1024 * 256 # 768 KiB, a multiple of 3
with open("big.bin", "rb") as src, open("big.b64", "wb") as dst:
while chunk := src.read(CHUNK):
dst.write(base64.b64encode(chunk))
Мы сверили это с base64 -w 0 на случайном файле в 100 КБ: совпадение байт в байт. Декодирование по частям работает так же с размером частей, кратным 4 символам, если во входных данных нет переносов строк.
Перенос строк MIME
Почта и некоторые старые системы требуют строк не длиннее 76 символов (RFC 2045). Так делает encodebytes, а base64.encode / base64.decode делают то же между файловыми объектами:
print(base64.encodebytes(b"x" * 60))
# b'eHh4eHh4...eHh4\neHh4\n' (76 chars, newline, rest, newline)
with open("big.bin", "rb") as src, open("big.mime", "wb") as dst:
base64.encode(src, dst)
with open("big.mime", "rb") as src, open("big.out", "wb") as dst:
base64.decode(src, dst)
Обратите внимание на перевод строки в конце: encodebytes всегда его добавляет. В браузере то же делает кодировщик MIME Base64.
Проверка входных данных
По умолчанию b64decode снисходителен: перед декодированием он выбрасывает все символы вне алфавита. Это скрывает ошибки. Передайте validate=True, чтобы сделать его строгим.
import binascii
print(base64.b64decode("aGk=!!")) # b'hi' (junk ignored)
try:
base64.b64decode("aGk=!!", validate=True)
except binascii.Error as e:
print(e) # Only base64 data is allowed
С validate=True переводы строк тоже считаются недопустимыми, так что сначала удалите переносы MIME. Все эти ошибки имеют тип binascii.Error (подкласс ValueError):
| Вход | Сообщение |
|---|---|
aGk | Incorrect padding |
a | Invalid base64-encoded string: number of data characters (1) cannot be 1 more than a multiple of 4 |
aGk== с validate=True | Excess padding not allowed |
aGk=!! с validate=True | Only base64 data is allowed |
Отчего возникает каждая из них, объясняет статья об ошибках Base64, а где ломается строка, покажет валидатор Base64.
Частые подводные камни
URL-safe строка в b64decode. Без validate=True символы - и _ молча выбрасываются, и вместо ошибки вы получаете мусор:
print(base64.b64decode("PDw_Pz8-Pg==")) # b'<<\x0f\xcf\xc3\xe0'
print(base64.urlsafe_b64decode("PDw_Pz8-Pg==")) # b'<<???>>'
urlsafe_b64decode принимает и стандартный Base64, так что, если вы не уверены, какой алфавит вам пришёл, безопаснее по умолчанию брать его.
Декодирование двоичных данных как текста. Если внутри картинка или ZIP, .decode("utf-8") падает с UnicodeDecodeError: 'utf-8' codec can't decode byte 0xff in position 0: invalid start byte. Оставьте данные байтами и запишите их в файл.
b'...' в выводе. Если напечатать результат b64encode, вы увидите литерал байтов. Если этот b' попал в JSON или заголовок, вы забыли .decode("ascii").
Base64 как защита. Это кодировка, обратить её может кто угодно. См. кодирование и шифрование.
Версии
base64.b64encode,b64decode,urlsafe_*иaltcharsесть во всех версиях Python 3.b64decodeпринимает ASCII-строкиstr(а не только байты) начиная с Python 3.3.encodestringиdecodestringудалены в 3.9; используйтеencodebytesиdecodebytes.binascii.b2a_base64(data, newline=False)(3.6+) – низкоуровневая функция, на которой построенb64encode; дляb"hi"она возвращаетb'aGk='.- В том же модуле есть
b32encode,b16encode,b85encodeиa85encodeдля других алфавитов.
Вопросы и ответы
Почему b64encode возвращает b'...', а не строку?
Функции Base64 в Python принимают байты и возвращают байты. Вызовите .decode("ascii") у результата, чтобы получить str, а у текста перед кодированием – .encode("utf-8").
Как в Python декодировать Base64 без дополнения?
Перед декодированием верните недостающие =: s + "=" * (-len(s) % 4). Строку без дополнения Python отвергает с ошибкой binascii.Error: Incorrect padding.
Чем b64decode отличается от urlsafe_b64decode?
urlsafe_b64decode перед декодированием заменяет - и _ на + и /. Обычный b64decode молча выбрасывает - и _ как мусор, если не передать validate=True, и вместо ошибки вы получаете неправильные байты.
Есть ли ещё base64.encodestring?
Нет. encodestring и decodestring удалены в Python 3.9. Для вывода в стиле MIME с переносами строк используйте encodebytes и decodebytes.