Codificação Base64 em Python: um guia completo
Aqui está o outro lado da moeda. Você tem dados na sua mão - um arquivo, um par de senha, um blob binário, um parágrafo de Unicode - e em algum ponto a jusante eles precisam viajar por um canal que aceita só letras. Esse é o trabalho inteiro do Base64: reescrever três bytes de dados como quatro caracteres de um alfabeto de 64 caracteres, completar o grupo final com = para que tudo saia em grupos de quatro, e entregar as letras. A página inicial deste site explica o formato por completo, alfabeto incluído, então vamos resumir isso a um fôlego e gastar o resto do tempo no que o Python realmente faz com ele.
A economia merece uma frase honesta antes de começarmos, porque o número aparece em toda conversa sobre isso: o preço dessa segurança de texto é o tamanho. O Base64 expande seus dados em mais ou menos um terço, quatro caracteres para cada três bytes de entrada, então um megabyte de binário vira um megabyte e um terço de letras. Para um token ou um valor de configuração isso é irrelevante; para um arquivo de vídeo é a razão pela qual você deveria pensar nas suas opções.
E a boa notícia: a resposta do Python para tudo isso é um import e uma função. O base64.b64encode está na biblioteca padrão há décadas, não precisa de instalação, e roda em velocidade de C por baixo. O resto deste guia é a cauda longa que faz a linha única ser útil no mundo real: a regra de só-bytes que impede metade de todos os bugs de TypeError, o alfabeto URL-safe, as ferramentas de quebra de linha do MIME, e os protocolos - JWTs, cabeçalhos HTTP, handshakes WebSocket, e-mail, PEM, data URLs - nos quais o Base64 está silenciosamente fazendo o seu trabalho.
Conheça o b64encode
O contrato cabe em quatro frases. Uma: a entrada é um objeto bytes-like - bytes, bytearray, memoryview - e uma string simples é recusada. Duas: a saída é um objeto bytes, nunca um str. Três: a saída sempre é com padding até um múltiplo de quatro caracteres, então até um único byte de entrada produz Zg==. Quatro: a saída é uma única linha, nunca quebrada, não importa o tamanho da entrada. Tudo o resto neste artigo é comentário sobre essas quatro frases:
import base64
encoded = base64.b64encode(b"foobar")
print(encoded)
# b'Zm9vYmFy'
print(len(encoded))
# 8
Para obter uma string de verdade, para uma URL ou um cabeçalho ou um campo JSON, decodifique o resultado como ASCII. O alfabeto garante que nada mais pode estar nele, o que torna essa etapa segura e barata:
import base64
text = base64.b64encode(b"foobar").decode("ascii")
print(text)
# Zm9vYmFy
Há um argumento a mais na assinatura, o altchars, e ele troca o + e o / do alfabeto padrão por um par diferente de caracteres. Esse é exatamente o botão por trás da variante URL-safe, então segure esse pensamento - você vai encontrá-lo daqui a algumas seções quando falarmos de tokens e strings de query.
A parede de tipos: str não é bytes
A primeira parede específica de Python neste artigo é o sistema de tipos, e vale a pena aprender a senti-la. O b64encode recusa strings com uma das mensagens de erro menos permissivas da linguagem:
import base64
try:
base64.b64encode("hello")
except TypeError as caught:
print(caught)
# a bytes-like object is required, not 'str'
A correção é o hábito único mais importante de todo este guia: transforme seu texto em bytes primeiro, e escolha a codificação de propósito em vez de torcer:
import base64
print(base64.b64encode("été".encode("utf-8")))
# b'w6l0w6k='
print(base64.b64encode("été".encode("utf-16")))
# b'//7pAHQA6QA='
Os mesmos caracteres, duas strings de bytes diferentes, duas saídas Base64 diferentes. A escolha da codificação é uma decisão, não um detalhe. UTF-8 é o padrão para qualquer coisa que vai cruzar um fio, um banco de dados, ou uma API. UTF-16 aparece quando você conversa com APIs do Windows, e ele traz uma byte-order mark na frente que você talvez não queira codificar, o que você pode descartar usando utf-16-le ou aparando com lstrip("\ufeff"). Latin-1 ainda se esconde em arquivos antigos europeus, onde um caractere é exatamente um byte e a questão inteira nem surge. O modelo mental para guardar: o codificador nunca olha para o seu texto; ele só enxerga bits. No momento em que os bytes cruzam a parede, a questão do charset está fechada - o que também é a razão pela qual o lado da decodificação tem que perguntar, depois, quem era dono daquele charset.
base64url: troque duas letras, jogue o padding fora
O alfabeto padrão esconde dois caracteres que URLs e sistemas de arquivos odeiam. O sinal + é silenciosamente lido como um espaço por qualquer decodificador de formulário, e o sinal / é um separador de caminho, então um payload do alfabeto padrão em uma string de query ou em um nome de arquivo é uma bomba-relógio. A seção 5 da RFC 4648 define a correção: uma variante onde + vira - e / vira _, onde o padding é descartado sempre que o comprimento dos dados é conhecido pelo contexto, e que a RFC insiste em chamar de base64url e não só de "base64". Você vai encontrá-lo em JSON Web Tokens, tokens OAuth, e parâmetros de cursor de API, ou seja, na maior parte da web moderna.
O Python embarca tanto uma função dedicada quanto o botão altchars da primeira seção, e eles produzem saída idêntica:
import base64
data = b"\xfb\xff\xfe"
print(base64.b64encode(data))
# b'+//+'
print(base64.urlsafe_b64encode(data))
# b'-__-'
print(base64.b64encode(data, altchars=b"-_"))
# b'-__-'
Em tokens e strings de query o padding geralmente vai junto, porque um = no final precisaria de percent-encoding e alguns middleboxes estragam ele de qualquer jeito:
import base64
padded = base64.urlsafe_b64encode(b"fooba")
print(padded)
# b'Zm9vYmE='
print(padded.rstrip(b"="))
# b'Zm9vYmE'
Remova, envie, e o receptor devolve os pads com o truque do módulo, "=" * (-len(s) % 4), que produz exatamente o número de pads que o comprimento exige. A regra geral: se os dados vão ficar em uma URL, um nome de arquivo, ou um JWT, use a variante urlsafe e jogue os pads fora; se vão ficar em um corpo de e-mail ou em um arquivo de texto, o alfabeto padrão com o seu padding é a norma.
Quando o seu leitor quer linhas: o MIME e a regra dos 76 caracteres
A única linha sem fim do b64encode é perfeita para campos JSON, cabeçalhos, e URLs, mas o e-mail tem opiniões. A RFC 2045, o padrão MIME, exige que a saída Base64 seja quebrada em linhas de no máximo 76 caracteres, e as ferramentas legadas do Python foram construídas para produzir exatamente isso. O encodebytes, adicionado no Python 3.1, faz a quebra de linha para um objeto bytes:
import base64
wrapped = base64.encodebytes(b"x" * 100)
for line in wrapped.splitlines():
print(len(line), line[:12])
# 76 eHh4eHh4eHh4
# 60 eHh4eHh4eHh4
A mecânica é um pouco simpática. O módulo codifica em blocos de 57 bytes, a constante MAXBINSIZE, porque 57 bytes viram exatamente 76 caracteres, e no CPython moderno cada linha quebrada termina com um simples line feed. A RFC 2045 pedia CRLF, mas a saída LF do Python é aceita por todo decodificador do ecossistema, incluindo o próprio do Python. A função legada de arquivo-para-arquivo encode faz a mesma quebra de linha direta de um file handle para outro, o que a torna uma ferramenta arrumada para arquivos grandes que você não quer guardar na memória duas vezes.
Qual ferramenta quando, em resumo: b64encode para qualquer coisa que vai para um campo JSON, uma URL, um cabeçalho, ou uma coluna de banco de dados; encodebytes para corpos de e-mail e blindagem no estilo PEM; o encode legado quando você está fazendo stream de um arquivo grande e quer a quebra de linha de graça. Escolher a errada é um bug clássico, porque um único salto de linha solto dentro de um campo JSON é suficiente para fazer um decodificador estrito do outro lado lançar uma exceção.
A família estendida
O módulo base64 é de verdade o módulo base-N, e ele carrega a família inteira da RFC 4648 mais um par de parentes de outros cantos do mundo da computação. A maioria deles é um drop-in de uma linha para o mesmo contrato de bytes-entrada-para-bytes-saída:
| Funções | Alfabeto | Quando Você O Encontra |
|---|---|---|
b16encode / b16decode |
0-9A-F |
"Base16" é só hexadecimal; o round trip mais rápido do módulo, ótimo para hashes e UUIDs |
b32encode / b32decode |
A-Z2-7 |
chaves de licença e códigos de ativação; sem 0, O, 1 ou I, então sobrevive a ser lido em voz alta |
b32hexencode / b32hexdecode |
0-9A-V |
Base32 com um alfabeto hex, adicionado no Python 3.10; mantém dados codificados ordenáveis em ordem lexicográfica |
a85encode / a85decode |
85 caracteres imprimíveis | ASCII85 do PostScript e do PDF, o descendente da utilidade btoa do Unix; no módulo desde o Python 3.4 |
b85encode / b85decode |
85 caracteres imprimíveis | o formato Base85 usado pelos diffs binários do git e do Mercurial; também desde o Python 3.4 |
z85encode / z85decode |
85 caracteres imprimíveis | Z85 do ZeroMQ, adicionado no Python 3.13; emoldura dados em grupos de quatro bytes |
Nenhum deles muda as regras que você já aprendeu: bytes entram, bytes saem, um alfabeto para escolher, e uma função de decodificação correspondente esperando do outro lado. Na prática você vai recorrer ao b16 sempre que um humano deveria conseguir ler o valor, ao b32 quando o valor for digitado ou dito à mão, e aos primos de 85 caracteres só quando uma especificação mandar. Para tudo o resto, o par Base64 do começo deste artigo é a ferramenta certa, e é sobre ele que todo o resto se constrói.
Imagens na página: Data URLs
O Base64 mais visível na web é o URI data:: mídia embutida diretamente em HTML ou CSS para que o navegador não dispare um segundo pedido. O formato é data:, o tipo de mídia, a palavra base64, uma vírgula, e os bytes codificados. Construir um a partir de um arquivo em disco é um bloco de três linhas:
import base64
with open("logo.png", "rb") as handle:
encoded = base64.b64encode(handle.read()).decode("ascii")
uri = "data:image/png;base64," + encoded
print(uri[:40])
# data:image/png;base64,iVBORw0KGgoAAAAN...
Duas cautelas, ambas baratas de observar. Primeiro, o navegador vai renderizar um data URI de bom grado, e vai guardar megabytes deles no documento de bom grado: para qualquer coisa além de alguns quilobytes, um pedido normal de imagem com um cabeçalho de cache adequado vence em toda métrica que importa. Segundo, o tipo de mídia depois do dois-pontos é uma promessa. Se os bytes são um JPEG, o URI diz image/jpeg, porque algumas ferramentas validam o par e alguns renderizadores simplesmente se recusam a adivinhar. A etapa .decode("ascii") também não é decoração; sem ela você está concatenando um objeto bytes a uma string e colecionando um TypeError, a parede de tipos fazendo o seu circuito.
Tokens que você pode entregar: JWTs
Um JSON Web Token é três pedaços base64url unidos por pontos: um cabeçalho, um payload, e uma assinatura. Se você está emitindo tokens de verdade, não monte os pedaços à mão. Instale o PyJWT (pip install pyjwt) e deixe ele construir as partes base64url, o padding, e a assinatura em uma única chamada:
import jwt
# Uma chave de menos de 32 bytes provoca a InsecureKeyLengthWarning do PyJWT, uma reclamação justa para uma chave de demonstração.
token = jwt.encode(
{"sub": "1234567890", "name": "John Doe"},
"super-secret-key",
algorithm="HS256"
)
print(token)
# eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIi...
print(type(token))
# <class 'str'>
Por baixo dos panos o PyJWT está fazendo exatamente o que este artigo descreve: serializar para JSON, passar pelo codificador urlsafe, e remover os pads, conforme a definição JWS na RFC 7515. Se algum dia você precisar montar um pedaço à mão, para um fixture de teste ou uma sessão de depuração, a receita é a mesma aritmética em qualquer lugar:
import base64
import json
payload = json.dumps({"sub": "1234567890"}).encode("ascii")
part = base64.urlsafe_b64encode(payload).rstrip(b"=")
print(part)
# eyJzdWIiOiAiMTIzNDU2Nzg5MCJ9
Uma nota sobre a direção da confiança: montar um token é a metade fácil. O receptor deve verificar a assinatura antes de confiar em uma única claim, e o PyJWT 2.x não vai decodificar um token sem uma lista explícita de algorithms, o que é um recurso, porque o erro de "qualquer algoritmo" é uma das linhas de código de autenticação mais caras já escritas.
HTTP: autenticação Basic e o handshake do WebSocket
Dois momentos do HTTP vivem ou morrem por causa do Base64. O primeiro é o esquema de autenticação mais antigo do protocolo: a autenticação Basic (RFC 7617), onde o cliente envia user:pass, codificado em base64, atrás da palavra Basic:
import base64
credentials = base64.b64encode(b"jane:pa:ss").decode("ascii")
header = "Basic " + credentials
print(header)
# Basic amFuZTpwYTpzcw==
Se o requests já está no seu stack, ele constrói esse cabeçalho para você com auth=("jane", "pa:ss"), o que vale a pena usar porque mantém o detalhe de codificação fora do seu código. E seja honesto sobre o que está acontecendo enquanto está nisso: a RFC 7617 é direta que o esquema "não é um método seguro de autenticação de usuário, nem protege de forma alguma a entidade, que é transmitida em texto puro". As credenciais são recuperáveis em uma linha de código por qualquer um que veja o tráfego, então isso é uma conveniência para conexões protegidas por TLS, não uma fronteira de segurança.
O segundo momento é o handshake do WebSocket (RFC 6455), onde o servidor prova que leu a chave aleatória do cliente respondendo com o Base64 de um hash SHA-1 da chave colada a um GUID mágico:
import base64
import hashlib
key = "dGhlIHNhbXBsZSBub25jZQ=="
magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
accept = base64.b64encode(
hashlib.sha1((key + magic).encode("ascii")).digest()
).decode("ascii")
print(accept)
# s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
A saída é o valor exato do exemplo resolvido na própria RFC, o que é uma forma agradável de checar uma implementação do zero. Em produção a biblioteca websockets faz essa etapa para você nas duas pontas; você só a faz à mão quando está escrevendo o minúsculo servidor de teste que prova o seu entendimento.
E-mail: o cliente original
O Base64 foi padronizado em 1993 para um trabalho, e esse trabalho era o e-mail: fazer o binário sobreviver ao mundo só-de-texto do SMTP, conforme o Content-Transfer-Encoding: base64 da RFC 2045. O pacote email do Python monta a mensagem, escolhe a codificação, e quebra o corpo no comprimento de linha padrão sem você escrever uma linha de Base64:
import email.mime.multipart
import email.mime.application
msg = email.mime.multipart.MIMEMultipart()
msg["Subject"] = "binary payload"
part = email.mime.application.MIMEApplication(
b"\x00\x01\x02", _subtype="octet-stream"
)
msg.attach(part)
text = msg.as_string()
print(text)
# ...
# Content-Transfer-Encoding: base64
#
# AAEC
# --...
A parte MIMEApplication é a interessante: ela quebra os bytes nas linhas corretas de 76 caracteres e carimba o cabeçalho de transfer-encoding, que é exatamente o comportamento encodebytes de antes aplicado pelo framework. Se você está montando um trecho solto em vez de uma mensagem completa, o email.encoders.encode_base64(obj) faz uma única codificação com quebra de linha direta em um objeto de mensagem. E quando a mensagem chega do outro lado, o lado da decodificação da história, do get_payload(decode=True) à decodificação de cabeçalhos, é coberto no artigo relacionado de decodificação.
Blindagem PEM para chaves e certificados
Arquivos PEM - os certificados, chaves privadas, e CRLs que começam com -----BEGIN ...----- - são nada além de blindagem em torno do Base64: uma linha de rótulo, Base64 quebrado, um rótulo de fechamento. A blindagem é fácil de ver através, porque o corpo é só a saída com quebra de linha que você já conheceu:
import base64
der = b"\x30\x03\x02\x01\x05" # um blob DER minúsculo para ilustração
body = base64.encodebytes(der).decode("ascii")
armor = ("-----BEGIN CERTIFICATE-----\n"
+ body
+ "-----END CERTIFICATE-----")
print(armor)
# -----BEGIN CERTIFICATE-----
# MAMCAQU=
# -----END CERTIFICATE-----
Remova as duas linhas de rótulo, junte o resto, e o b64decode devolve os bytes DER para você. Em produção você quase nunca faz isso à mão: o pacote cryptography (pip install cryptography) gera a blindagem com public_bytes e faz o parse dela com load_pem_x509_certificate e companhia, cuidando da etapa de Base64 por baixo dos panos. O caminho manual se paga no momento específico em que os bytes DER crus já estão nas suas mãos - uma coluna de banco de dados, um arquivo de configuração, um buffer de um protocolo - e a especificação na sua frente diz "PEM, por favor".
Enviando arquivos: uploads, downloads e o hábito do .b64
O caso de uso mais antigo da internet é um arquivo binário que precisa cruzar um canal que só carrega texto: um FTP que estraga as quebras de linha, um formulário que recusa uploads, uma janela de chat que come binário. A receita é ler, codificar, enviar, e deixar a ponta distante decodificar, e a metade interessante é o par de etapas do meio:
import base64
with open("photo.png", "rb") as src:
data = src.read()
wrapped = base64.encodebytes(data)
with open("photo.b64", "wb") as dst:
dst.write(wrapped)
print(len(wrapped), "bytes on disk for", len(data), "in the photo")
# mais ou menos um terço maior que o original
Duas notas. A extensão .b64 é uma convenção da comunidade, não um padrão, então o lado receptor também precisa conhecer a convenção - que é por que as APIs JSON geralmente envolvem o payload em um campo nomeado como "image_base64" e dizem isso na documentação delas. E as linhas quebradas de 76 caracteres do encodebytes são o formato de escolha para o arquivo em disco, porque copiar-e-colar limpas em toda ferramenta de texto que humanos possuem, de clientes de e-mail a leitores de PDF. A direção reversa, ler um arquivo desses de volta em bytes, é uma única chamada no artigo relacionado de decodificação; aqui você é só o remetente, e o trabalho do remetente é ser consistente.
A questão do armazenamento: arquivos de configuração, variáveis de ambiente e bancos
Desenvolvedores adoram colocar Base64 em lugares que só aceitam texto: um arquivo .env, uma configuração .ini, uma coluna TEXT. A etapa de codificação é trivial, e a forma mais comum é JSON-em-Base64:
import base64
import json
config = {"api_user": "svc-bot", "api_pass": "hunter2-not-really"}
packed = base64.b64encode(
json.dumps(config).encode("utf-8")
).decode("ascii")
print(packed)
# eyJhcGlfdXNlciI6ICJzdmMtYm90IiwgImFwaV9wYXNzIjogImh1bnRlcjItbm90LXJlYWxseSJ9
E então o aviso, porque é aqui que mora o mal-entendido mais caro de todo este artigo. O Base64 não é ofuscação que se sustenta, e não é criptografia. A seção 12 da RFC 4648 coloca o assunto tão claramente quanto uma RFC consegue: a codificação base "oculta visualmente informação que de outro modo seria facilmente reconhecida, como senhas, mas não fornece qualquer confidencialidade computacional". Um arquivo .env com segredos em Base64 te protege da pessoa que dá uma olhada nele, não da pessoa que o lê, e um comando de base64 -d depois o "segredo" está parado no terminal dela em texto puro. Se os dados forem de verdade sensíveis, criptografe primeiro - o pacote cryptography embarca o Fernet exatamente para isso - e só então codifique o texto cifrado em Base64 se o seu armazenamento exigir texto.
Um milhão de bytes depois: big data e chunking
O b64encode é uma função de velocidade de C - em um laptop típico ele processa um megabyte em mais ou menos um milissegundo - mas não é uma função de streaming. Não existe um par update-e-finish em lugar nenhum da biblioteca padrão, então codificar dados maiores do que você quer guardar na memória significa fazer a aritmética de fronteira você mesmo. Três bytes de entrada fazem quatro caracteres de saída, então qualquer fronteira de chunk tem que cair em uma costura de três bytes:
import base64
def encode_chunks(chunks):
out = []
leftover = b""
for chunk in chunks:
buffer = leftover + chunk
whole = len(buffer) // 3 * 3
if whole:
out.append(base64.b64encode(buffer[:whole]))
leftover = buffer[whole:]
if leftover:
out.append(base64.b64encode(leftover))
return b"".join(out)
with open("video.mp4", "rb") as handle:
encoded = encode_chunks(iter(lambda: handle.read(65536), b""))
A saída é idêntica byte por byte à de codificar o arquivo inteiro em uma única chamada, porque a costura de três bytes é o único lugar onde a agrupação pode quebrar. O padding aparece exatamente uma vez, no chunk final, que é o que um decodificador estrito do outro lado vai esperar. A linha iter(lambda: handle.read(65536), b"") é o idiom padrão para ler um arquivo em pedaços de tamanho fixo, e a variável leftover é o algoritmo inteiro. O lado da decodificação mantém uma costura de quatro caracteres em vez de uma de três bytes, então os dois artigos dividem a aritmética entre eles em vez de repeti-la.
Onde os codificadores erram
O lado da codificação tem menos armadilhas que o lado da decodificação, porque tem menos coisas para dar errado quando você é quem produz as letras. Ainda assim, estas aparecem toda semana, e cada uma delas tem uma correção de dois minutos se você a reconhecer cedo:
- Alimentar o codificador com uma string. O
TypeErrorda seção da parede de tipos. Corrija na fonte com.encode("utf-8"), e pense em qual charset você realmente quer dizer antes de digitá-lo. - Esquecer que a saída é bytes.
b64encoderetorna bytes; ostré o que vai para uma URL ou um campo JSON, então a etapa.decode("ascii")faz parte da receita, não é um pensamento posterior. - Montar à mão a troca URL-safe.
str.replace("+", "-").replace("/", "_")funciona, mas são duas letras de dívida de manutenção ondeurlsafe_b64encodeé uma chamada. Pior, uma troca pela metade, com os+corrigidos e as barras esquecidas, produz um alfabeto que não corresponde a nenhuma especificação. - Deixar os pads em uma URL. Um
=no final dentro de uma string de query sofre percent-encoding por uma ferramenta e é removido por outra, e a matemática de padding do receptor quebra da forma mais confusa. Remova-os; o comprimento diz ao decodificador tudo o que ele precisa. - Quebrando linhas onde não é bem-vindo. As quebras de linha do
encodebytessão corretas para e-mail e PEM, e veneno para um campo JSON ou uma URL. Uma única quebra de linha solta é suficiente para fazer um decodificador estrito do outro lado lançar uma exceção sobre os seus dados, não sobre o seu formato. - Codificando duas vezes. Os dados já eram Base64 a montante - um campo que chega pré-codificado de outra API, um arquivo que levou o tratamento
.b64duas vezes - e a segunda passada produz uma string que decodifica de volta para a primeira codificação. Dê o round-trip uma vez, confira os bytes mágicos, e pare. - Confiança no Base64 com segredos. O aviso da seção de armazenamento, repetido porque custa dinheiro de verdade: se o modelo de ameaça inclui qualquer pessoa lendo o arquivo, você precisa de um cifrador, não de um alfabeto.
Um changelog que dá para ler
A idade do módulo aparece em melhorias quietas e datadas em vez de revoluções. A versão curta, na ordem em que as peças chegaram, do ponto de vista do codificador:
| Versão | O Que Aconteceu |
|---|---|
| Python 2.4 (2004) | o suporte completo à RFC 3548 do Barry Warsaw embarca: as famílias b16, b32 e b64, mais as variantes standard_* e urlsafe_* usadas hoje |
| Python 3.1 (2009) | encodebytes chega e encodestring é descontinuado, uma renomeação que tutoriais antigos ainda tropeçam |
| Python 3.4 (2014) | todo codificador aceita qualquer objeto bytes-like, e a85encode e b85encode entram no módulo |
| Python 3.6 (2016) | binascii.b2a_base64 aprende um switch newline, que é o que deixa o b64encode ser uma única linha sem fim |
| Python 3.9 (2020) | os nomes legados encodestring e decodestring são finalmente removidos |
| Python 3.10 (2021) | b32hexencode e b32hexdecode, os primos de alfabeto hex ordenável |
| Python 3.13 (2024) | z85encode e z85decode, o alfabeto do ZeroMQ, entram na família |
| Python 3.14 (2025) | imports mais rápidos pela biblioteca padrão toda, base64 incluído, e um b16decode até seis vezes mais rápido, já que a validação dele agora roda em bytes.translate em vez de uma expressão regular |
O fio condutor, se você quiser um: o módulo foi reescrito em 1995 para delegar o trabalho dele ao módulo binascii de nível C, e essa delegação ainda é verdadeira hoje. A primeira mudança da era dos bytes, um commit de 2007 durante o desenvolvimento do Python 3 que fez tudo usar bytes em qualquer lugar, é de onde veio a parede de tipos deste artigo, e é a razão pela qual um codificador moderno recebe bytes e devolve bytes, com tudo o resto sendo um wrapper em torno desse contrato único.
Coisas que o módulo não te conta
O trabalho sério está feito, então aqui estão as pequenas delícias no lado da codificação do livro-razão:
- O exemplo da própria documentação rodou a mesma demonstração por mais de uma década: entra
b'data to be encoded', saib'ZGF0YSB0byBiZSBlbmNvZGVk'. Você já encontrou esse par antes, saiba ou não. - A função C por baixo do
b64encodeadiciona o seu salto de linha final com um comentário que diz "Acrescente um salto de linha de cortesia". Uma cultura inteira, em uma linha de código-fonte. - A palavra
passwordcodifica paracGFzc3dvcmQ=, que é por que o Base64 em um arquivo de log parece um segredo para um scanner e está a um comando de virar um para um leitor. - O
b64encodenunca quebra linhas. Nunca. Um gigabyte de entrada produz uma única linha de 1.3 gigabyte, e a função nem pisca. Se você queria linhas, você tinha que pedir oencodebytes. - A docstring do módulo ainda cita a RFC 3548, a edição de 2003 da especificação. A RFC 4648 assumiu em 2006; a docstring simplesmente nunca percebeu.
- O Python 2 não tinha parede de tipo alguma: o
b64encodeaceitava umastrde bom grado e devolvia uma. A grande reescrita de bytes de 2007 encerrou isso, e é para os tutoriais antigos do Python 2 que a maioria das threads de "por que a minha codificação trava" ainda aponta. - O
z85encode, o membro mais novo da família (Python 3.13), é o mais exigente: o ZeroMQ emoldura dados em grupos de quatro bytes, então a especificação exige que a saída codificada seja um múltiplo de cinco caracteres - e a documentação deixa o padding por sua conta: a entrada tem que chegar um múltiplo de 4 bytes (o codificador não vai fazer o padding para você; uma entrada de 3 bytes produz um frame de 4 caracteres que nenhum par ZeroMQ vai aceitar).
Então a filosofia do codificador em três regras. Decida os bytes primeiro e a codificação em segundo, porque a parede de tipos é onde a maioria dos bugs de Base64 do Python nasce. Escolha o alfabeto para o canal, não para os dados: padrão com pads para e-mail e arquivos, base64url sem pads para URLs e tokens, e nunca improvise uma terceira variante no teclado. E mantenha a saída na forma que o seu leitor espera, uma linha para JSON e cabeçalhos, linhas de 76 caracteres para MIME e PEM, porque o decodificador do outro lado vai te cobrar isso.
Quando essas letras chegam do outro lado, a diversão de verdade começa: pads ausentes, descarte silencioso, payloads que não são exatamente Base64, e um decodificador com dois humores para navegar. Tudo isso é coberto em detalhe no artigo relacionado de decodificação Base64 no final desta página, e os dois guias leem bem como um par. Boa codificação.
Última atualização: 2026-09-08
Artigo relacionado: Decodificação Base64 em Python: um guia completo