Codificação Base64 em C++ (Cpp): um guia completo
O problema inverso é o de maior manchete: você tem bytes - um certificado, uma imagem, um blob aleatório, uma assinatura - e precisa que eles viajem por algo que só fala texto: um campo JSON, um header de email, uma URL, uma variável de ambiente. A página inicial deste site passeia pelo formato em profundidade, então esta é só a versão curta: três bytes viram quatro caracteres do alfabeto, uma cauda curta ganha uma ou duas marcas =, e a forma codificada sai cerca de 33 por cento maior que o original. Codificar é a direção que cresce, então todo buffer deste artigo é dimensionado para isso, e a aritmética é uma linha - 4 * ((n + 2) / 3) - que não muda não importa qual codificador você escolha.
Como no lado da decodificação, o próprio C++ não codifica um único byte por você. A biblioteca padrão teve trinta anos para desenvolver uma função base64 e gastou todos eles com outras coisas, então todo programa em C++ traz o próprio codificador de um banco de quatro personalidades muito diferentes, mais a opção de escrever cerca de quarenta linhas suas. Um é um cavalo de batalha que carrega TLS desde os anos 1990 e que adiciona padding, termina com NUL e quebra linhas sem pedir permissão. Um é um codec header-only rápido escondido num namespace que os autores rotularam como "detail". Um é um iterador de 2002 que aparentemente nunca encontrou um caractere de padding. Um é uma função que o sistema operacional entrega há décadas e que anexa um CRLF no final do seu token. E a quinta opção é a sua. Uma vez que você sabe o que cada um adiciona, recusa ou anexa em silêncio, codificar deixa de ser fonte de bugs de off-by-one. Vamos ao empacotamento.
O padrão nunca lançou um empacotador
Cada padrão desde o C++98 - e foram oito, até o C++26 - olhou para o alfabeto de 64 caracteres e seguiu em frente. Não existe <base64>, não existe std::base64, nada em <string> ou <vector> que empacote os seus bytes. O trabalho técnico do C++26 terminou e foi aprovado por voto (114-12-3) na reunião ISO C++ de março de 2026 em Croydon, Reino Unido, e ele de fato adiciona um header <text_encoding> para trabalho de codec de texto; as próximas reuniões do comitê, em junho de 2026 (Brno) e novembro de 2026 (Búzios, Brasil), abrem o working draft do C++29 em vez de revisar o C++26. Base64 não está no padrão, e é difícil culpar o comitê: codificação de texto é sobre conjuntos de caracteres, e base64 é sobre bytes, então o novo header nunca seria o lugar certo. Na prática, o ecossistema fez o trabalho. As rotinas base64 EVP do OpenSSL estão em toda release do OpenSSL, as bibliotecas do Boost carregam dois codificadores independentes, o Windows entrega uma função do CryptoAPI com uma tabela de flags para o serviço, e um snippet de quarenta linhas é copiado e colado pela linguagem desde 2008. Se o seu projeto é baseado em CMake, o setup inteiro de dependências cabe em três linhas:
find_package(OpenSSL REQUIRED)
find_package(Boost REQUIRED)
target_link_libraries(my_app PRIVATE OpenSSL::Crypto)
A release do Boost para notar é a 1.92.0, de agosto de 2026, de um projeto fundado em 1998 que entrega bibliotecas desde a sua primeira release em 1999. Os dois codificadores do Boost abaixo são header-only - não há nada para linkar - enquanto o OpenSSL quer -lcrypto, que a maioria dos programas C++ que tocam TLS já tem no binário.
Primeiro, a matemática: dimensionando todo buffer deste artigo
Base64 agrupa bytes de três em três, então o tamanho da saída tem uma forma que nunca surpreende quando você conhece: para cada 3 bytes de entrada, 4 caracteres de saída, e uma cauda curta é completada com padding até o grupo cheio. A contagem exata para n bytes de entrada é:
4 * ((n + 2) / 3)
O +2 é o truque do teto: a divisão inteira arredonda para baixo, então somar 2 antes faz ela arredondar para cima ao próximo múltiplo de três. A partir daí, cada tamanho de buffer neste artigo é uma substituição. A função one-shot do OpenSSL quer um buffer que caiba nos dados codificados mais o NUL que ela anexa no final - a man page ilustra o contrato com 16 bytes de entrada virando 24 bytes codificados mais 1 NUL, 25 bytes no total, e a função retorna o tamanho sem o NUL. O caminho streaming dela processa a entrada em blocos de 48 bytes, e a man page dimensiona a saída em 65 bytes por bloco (64 caracteres mais a quebra de linha que cada bloco sempre produz) mais um byte para o NUL. O header do Boost.Beast te entrega a fórmula exata como uma função constexpr. E o seu próprio código reserva (n + 2) / 3 * 4 e chama o dia. Aqui estão os números que você vai encontrar de verdade:
| Entrada | Saída (com padding) | O que reparar |
|---|---|---|
| 1 byte | 4 caracteres | A menor forma com padding: QQ== |
| 2 bytes | 4 caracteres | Três caracteres de dados e um pad |
| 3 bytes | 4 caracteres | Um grupo cheio, sem padding nenhum |
| 48 bytes | 64 caracteres | Exatamente um bloco streaming do OpenSSL |
| 500 bytes | 668 caracteres | Quebrado em 64, são 11 linhas, 679 caracteres com as quebras |
| 1 GB | cerca de 1.33 GB | Reserve a coluna, o arquivo e a rede para o imposto |
Se o lado receptor é uma coluna de tamanho fixo, um buffer ou uma linha num arquivo de texto, esta fórmula é o documento de design inteiro. A única direção em que ela pode morder é a outra: o lado da decodificação precisa de 3n/4 menos os pads, e um buffer de decodificação dimensionado com a fórmula de codificação é uma superalocação clássica que cresce até virar um ticket de bug de memória. Dimensionar a direção que encolhe é problema do guia irmão; aqui você só cresce.
Aqui está o panorama, porque as diferenças estão todas nos extras - o padding, as quebras de linha, os NULs - e não no empacotamento central, que cada linha implementa de forma idêntica:
| Codificador | De onde ele vem | Padding | Bytes extras para reservar | Especificidade para lembrar |
|---|---|---|---|---|
EVP_EncodeBlock |
<openssl/evp.h>, link -lcrypto |
Sempre | 1 (um NUL no buffer) | O exemplo de 16 bytes da man page é o contrato |
EVP_EncodeUpdate + Final |
idem | Sempre | 65 por bloco de 48 bytes | Quebra duro em 64 caracteres, todo bloco termina com quebra de linha |
Boost.Beast encode |
boost/beast/core/detail/base64.hpp, header-only |
Sempre | 0 | Mora num namespace chamado detail |
| Iteradores do Boost.Serialization | boost/archive/iterators/base64_from_binary.hpp, header-only |
Nunca | 0 - você adiciona os 1 ou 2 pads você mesmo | Codificador mais antigo da caixa de ferramentas, 2002 |
CryptBinaryToStringA |
wincrypt.h, crypt32.lib |
Sempre | 2 (um CRLF) a menos que NOCRLF |
Tem uma flag URL-safe que o resto da caixa de ferramentas não tem |
| Suas próprias quarenta linhas | Em lugar nenhum: é sua | Sua escolha | Sua escolha | Você é dono de todo caso de borda para sempre |
O algoritmo central é idêntico em cada linha - essa é a parte reconfortante de um formato de 1987. O que difere é o que cada implementação adiciona em volta do payload, e quase toda armadilha deste artigo é uma dessas adições encontrando um consumidor que não esperava.
OpenSSL: o codificador que a sua pilha TLS já linka
Se o seu programa já linka o OpenSSL para TLS, não precisa adicionar nada. A função one-shot é uma única chamada:
int EVP_EncodeBlock(unsigned char *t, const unsigned char *f, int n);
Entregue a ele os bytes de origem e o tamanho, e ele escreve a codificação com padding, em uma linha. O contrato vale a pena memorizar, porque a man page o declara com um exemplo: para cada 3 bytes de entrada, 4 bytes de saída; uma cauda que não é divisível por 3 ganha padding para a saída ser sempre divisível por 4; e um caractere terminador NUL é adicionado por cima. O exemplo documentado é 16 bytes de entrada, 24 bytes codificados mais 1 NUL, 25 bytes no total no buffer, com a função retornando 24 - o tamanho sem o NUL. Dimensione o buffer de acordo e o wrapper é de algumas linhas:
#include <cstddef>
#include <cstdio>
#include <string>
#include <openssl/evp.h>
std::string openssl_encode(const std::string &in) {
std::string out;
out.resize(4 * ((in.size() + 2) / 3) + 1);
int n = EVP_EncodeBlock(reinterpret_cast<unsigned char *>(out.data()),
reinterpret_cast<const unsigned char *>(in.data()),
static_cast<int>(in.size()));
if (n < 0) return {};
out.resize(static_cast<size_t>(n));
return out;
}
int main() {
std::printf("%s\n", openssl_encode("Mane").c_str());
std::printf("%s\n", openssl_encode("M").c_str());
std::printf("%s\n", openssl_encode("").c_str());
}
Repare no que o std::string está fazendo que o C te obrigaria: ele cresce até exatamente o tamanho retornado, então o NUL que o OpenSSL anexou fica simplesmente além do tamanho acompanhado e nunca vira parte do payload. Codifique "Mane" e ganha TWFuZQ==, a cauda clássica de quatro caracteres com o seu único pad; codifique um byte e ganha um par de dois caracteres de dados vestindo um disfarce de dois pads; não codifique nada e ganha a string vazia, o único caso em que um codificador base64 se comporta exatamente como a função identidade. A única linha de lógica de verdade da função inteira é o resize: ela transforma "bytes escritos mais um NUL" em "exatamente o payload".
Para dados que chegam em pedaços - um arquivo, um socket, um stream que você não quer bufferizar - o OpenSSL tem um context que você alimenta e finaliza, e a aritmética de blocos da man page é incomummente explícita. Somente blocos cheios de 48 bytes são processados na hora; qualquer resto fica guardado dentro do context e é liberado por uma chamada seguinte ou pela final. Cada bloco processado escreve 64 caracteres mais uma quebra de linha - 65 bytes - e a chamada final trata o bloco parcial, que é por isso que o seu teto documentado é 65 bytes mais o NUL. A consequência para saber antes de chamar: esta API quebra em 64 caracteres. Não é configurável. É isso que o codificador streaming é.
#include <algorithm>
#include <cstdio>
#include <string>
#include <vector>
#include <openssl/evp.h>
std::string openssl_encode_wrapped(const std::string &in) {
EVP_ENCODE_CTX *ctx = EVP_ENCODE_CTX_new();
EVP_EncodeInit(ctx);
std::string out;
out.reserve(4 * ((in.size() + 2) / 3) + in.size() / 48 + 2);
std::vector<unsigned char> buf(128);
int outl = 0;
for (size_t pos = 0; pos < in.size();) {
size_t take = std::min<size_t>(48, in.size() - pos);
EVP_EncodeUpdate(ctx, buf.data(), &outl,
reinterpret_cast<const unsigned char *>(in.data()) + pos,
static_cast<int>(take));
out.append(reinterpret_cast<const char *>(buf.data()), outl);
pos += take;
}
EVP_EncodeFinal(ctx, buf.data(), &outl);
out.append(reinterpret_cast<const char *>(buf.data()), outl);
EVP_ENCODE_CTX_free(ctx);
return out;
}
int main() {
std::string s = openssl_encode_wrapped(std::string(500, 'A'));
std::printf("500 bytes -> %zu chars\n", s.size());
int lines = 0;
size_t longest = 0, run = 0;
for (char c : s) {
if (c == '\n') { lines++; run = 0; }
else run++;
longest = std::max(longest, run);
}
std::printf("lines=%d longest=%zu lastchar=%c\n", lines, longest, s.back());
}
Alimente com 500 bytes da letra A e a conta fecha exatamente como a man page prometeu: 668 caracteres codificados, e como a saída é cortada em linhas de 64 caracteres, você ganha 11 linhas, 679 caracteres no total, e o último caractere de todos é uma quebra de linha. É essa quebra de linha final que quebra os consumidores: cole o resultado numa string JSON e você tem um caractere de controle onde deveria ter uma aspas; use como segmento de token e você inventou um novo segmento. A regra prática: a API de bloco para payloads de uma linha (tokens, headers, valores de config), a API streaming quando o consumidor quer saída quebrada em formato MIME, e na dúvida, remova a quebra de linha final com um loop while (out.back() == '\n') antes que o payload cruze uma fronteira que não espera.
Boost.Beast: um empacotador rápido num namespace detail::
A biblioteca HTTP do Boost entrega um codec base64 no endereço improvável boost/beast/core/detail/base64.hpp. O namespace detail:: é o jeito do Boost de dizer "isso é assunto interno nosso", e os mantenedores se recusaram a promover o codec a uma API pública. Todo mundo usa assim mesmo: ele é pequeno, é rápido, é header-only (defina BOOST_BEAST_HEADER_ONLY antes do include e não há nada para linkar), e é o mesmo codec que o próprio handshake WebSocket do Boost.Beast usa para o cálculo do Sec-WebSocket-Accept, o que significa que ele está mastigando tráfego de verdade há anos.
No lado da codificação a API é quase insultantemente calma. Um helper constexpr te dá o tamanho exato da saída - 4 * ((n + 2) / 3), a mesma fórmula da seção de matemática, agora com um compilador para conferir - e a função encode escreve o resultado com padding no seu buffer e te diz quantos caracteres usou. Não há canal de erro, porque codificar não pode falhar: qualquer byte é entrada válida, e o tamanho da saída é uma função pura do tamanho da entrada. O wrapper:
#define BOOST_BEAST_HEADER_ONLY
#include <boost/beast/core/detail/base64.hpp>
#include <cstddef>
#include <cstdio>
#include <string>
namespace b64 = boost::beast::detail::base64;
std::string beast_encode(const std::string &in) {
std::string out(b64::encoded_size(in.size()), '\0');
std::size_t n = b64::encode(out.data(), in.data(), in.size());
out.resize(n);
return out;
}
int main() {
std::printf("%s\n", beast_encode("Mane").c_str());
std::printf("%s\n", beast_encode("M").c_str());
}
Codifique "Mane" e ganha TWFuZQ==; codifique o único byte M e ganha TQ== - os mesmos bytes que o wrapper do OpenSSL produziu, sem NUL para se preocupar e sem linhas para remover. Duas coisas para arquivar. Primeira, a procedência: a fonte tem copyright 2016-2019 de Vinnie Falco, com um rodapé atribuindo partes a um snippet de Rene Nyffenegger de 2004-2008 - a mesma canção folclórica que começou a história do base64 em C++, agora entregue dentro do Boost, no seu binário, fazendo handshakes WebSocket para a web inteira. Segunda, a prática: como o codec adiciona padding e nunca quebra linhas, ele é a ferramenta certa para qualquer coisa que precise ser uma linha - tokens, headers, payloads de API - e a fórmula encoded_size te dá um buffer exatamente certo, nunca uma aproximação.
Boost.Serialization: o iterador que esqueceu que padding existe
O base64 mais antigo do ecossistema C++ não é uma função, mas um conjunto de adaptadores de iteradores composáveis, escritos por Robert Ramey em 2002 para a biblioteca de serialização do Boost. A direção de codificação é uma cadeia de dois adaptadores: um transformador de largura que reagrupa os seus bytes crus de oito para seis, e um iterador que transforma cada valor reagrupado em um caractere do alfabeto:
#include <boost/archive/iterators/base64_from_binary.hpp>
#include <boost/archive/iterators/transform_width.hpp>
#include <cstddef>
#include <cstdio>
#include <string>
namespace it = boost::archive::iterators;
std::string boost_iter_encode(const std::string &in) {
using enc =
it::base64_from_binary<it::transform_width<const char *, 6, 8>>;
std::string out(enc(in.data()), enc(in.data() + in.size()));
switch (in.size() % 3) {
case 1: out += "=="; break;
case 2: out += '='; break;
default: break;
}
return out;
}
int main() {
std::printf("%s\n", boost_iter_encode("Mane").c_str());
std::printf("%s\n", boost_iter_encode("M").c_str());
}
O iterador faz o empacotamento central e nada mais - sem padding, sem NUL, sem quebras de linha e sem canal de erro, porque o empacotamento central não pode falhar. Codifique "Mane" e o iterador te entrega seis caracteres, TWFuZQ, com a cara impassível: a codificação de verdade de quatro bytes são oito caracteres, e nunca ocorreu a um iterador de 2002 se importar. É por isso que a instrução switch é carregante, não decorativa: um byte a menos de um grupo ganha dois pads, dois bytes a menos ganha um. A mesma cadeia menos o switch é o que você ganha se esquecer esse passo, e o resultado é uma string que decodifica bem num decodificador tolerante, falha num estrito, e transforma a mensagem de erro do seu consumidor de API em mistério. (O lado da decodificação desta mesma família de iteradores é o que joga uma exceção num único espaço solto - mais no guia irmão.)
Quarenta linhas, zero dependências
Base64 é pequeno o suficiente para um codificador correto ser uma coisa respeitável de se possuir, e no C++ o retorno é melhor do que em qualquer outra linguagem: std::string torna o gerenciamento de buffer agradável, a fórmula te dá o tamanho exato de antemão, e um codificador feito à mão é o que não tem opiniões de jeito nenhum - sem NUL, sem quebras de linha, sem hábitos de plataforma - que é exatamente o que você quer sob um arquivo de config ou uma fronteira de API. Esta versão empacota em grupos de 3 bytes contra uma tabela de 64 caracteres:
#include <cstddef>
#include <cstdio>
#include <string>
std::string base64_encode(const std::string &in) {
static const char *table =
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string out;
out.reserve((in.size() + 2) / 3 * 4);
const unsigned char *p =
reinterpret_cast<const unsigned char *>(in.data());
size_t n = in.size();
for (size_t i = 0; i < n; i += 3) {
unsigned v = p[i] << 16;
if (i + 1 < n) v |= p[i + 1] << 8;
if (i + 2 < n) v |= p[i + 2];
out.push_back(table[(v >> 18) & 63]);
out.push_back(table[(v >> 12) & 63]);
out.push_back(i + 1 < n ? table[(v >> 6) & 63] : '=');
out.push_back(i + 2 < n ? table[v & 63] : '=');
}
return out;
}
int main() {
std::printf("%s\n", base64_encode("Mane").c_str());
std::printf("%s\n", base64_encode("M").c_str());
std::printf("%s\n", base64_encode("M\312\277").c_str());
}
Passe pelas partes. A linha do reserve é a seção de matemática: (n + 2) / 3 * 4 caracteres, exatamente, então não há realocação no meio do loop. O reinterpret_cast para const unsigned char * não é cerimônia - em plataformas onde char é assinado, um byte acima de 127 seria um número negativo, e no momento em que ele tocase um índice de tabela você teria comportamento indefinido vestindo jaleco de laboratório. Cada iteração puxa até três bytes para um valor de 24 bits, empurra as quatro fatias de 6 bits para a tabela, e na cauda emite = no lugar do byte que não estava lá - os guards i + 1 < n e i + 2 < n são a lógica de padding inteira. Dê a ele "Mane" e ganha TWFuZQ==. Dê a ele um único M e ganha TQ==. Dê a ele bytes acima de 127 - o par 0xCA 0xBF na terceira linha do exemplo - e a saída continua ASCII puro (Tcq/), porque um byte acima de 127 é só um byte, e a tabela não se importa com o que ele significa. Quarenta linhas, sem dependências, e cada caso de borda é uma linha que você escreveu, que é o ponto inteiro.
Windows CryptoAPI: o empacotador embutido no sistema operacional
No Windows há um codificador base64 no próprio sistema operacional, mais antigo que a maioria dos frameworks deste artigo: CryptBinaryToStringA do wincrypt.h, no crypt32.lib, parte do CryptoAPI que vem com o Windows há décadas. Ele converte um array de bytes em uma string formatada, e a sua tabela de flags se lê como um menu da história inteira do formato:
| Flag | Valor | O que você ganha |
|---|---|---|
CRYPT_STRING_BASE64HEADER |
0x0 |
Base64 embrulhado nas linhas de header BEGIN/END de certificado |
CRYPT_STRING_BASE64 |
0x1 |
Base64 puro, sem headers |
CRYPT_STRING_BASE64URI |
0xD |
O alfabeto URL-safe: + vira -, / vira _, conforme a seção 5 do RFC 4648 |
CRYPT_STRING_NOCRLF |
0x40000000 |
Sem quebra de linha anexada no final |
CRYPT_STRING_NOCR |
0x80000000 |
Um LF puro em vez do CRLF padrão |
A primeira coisa a saber é o padrão: a menos que você passe CRYPT_STRING_NOCRLF, a função anexa um par de carriage-return/line-feed ao final da sua string - o comportamento documentado é que todo formato não binário ganha uma sequência de quebra de linha - então um token base64 que precisa caber numa linha quer BASE64 | NOCRLF, e essa combinação é a chamada idiomática. A segunda coisa é a convenção de chamada, que é o clássico processo de duas etapas do Windows: chame com um buffer NULL para perguntar quanto espaço é preciso (a resposta inclui o NUL terminador), aloque, chame de novo, e leia de volta o tamanho sem o NUL:
#include <windows.h>
#include <wincrypt.h>
#include <cstddef>
#include <string>
std::string win32_encode(const std::string &in,
DWORD flags = CRYPT_STRING_BASE64) {
DWORD need = 0;
if (!CryptBinaryToStringA(reinterpret_cast<const BYTE *>(in.data()),
static_cast<DWORD>(in.size()),
flags | CRYPT_STRING_NOCRLF,
nullptr, &need))
return {};
std::string out(need, '\0');
DWORD got = 0;
if (!CryptBinaryToStringA(reinterpret_cast<const BYTE *>(in.data()),
static_cast<DWORD>(in.size()),
flags | CRYPT_STRING_NOCRLF,
out.data(), &got))
return {};
out.resize(got);
return out;
}
Duas notas mais. A flag URI é o único base64url nativo deste artigo inteiro - no Windows você pode codificar o alfabeto dos tokens diretamente, e a abordagem de transcode da seção abaixo é estritamente para as outras plataformas. E a entrada CRYPT_STRING_BASE64HEADER, com o seu valor 0, é também a flag que você ganha se passar zero, então uma chamada que "queria dizer" nenhuma flag embrulha, em silêncio, o payload nas linhas de header de certificado - o hábito de enquadramento da era PEM, útil para gerar arquivos .pem e uma surpresa para tudo o resto. Link contra crypt32.lib e a função é sua pelo resto da vida do programa.
Base64url: o alfabeto dos tokens e das URLs
O alfabeto padrão tem dois caracteres que não sobrevivem a uma URL: + significa espaço numa query string, e / significa diretório num path. A seção 5 do RFC 4648 resolve isso com duas trocas de caracteres - + vira - e / vira _ - e é direto sobre o resultado: essa codificação "não deve ser considerada a mesma que a codificação base64". É o alfabeto dos JWTs, dos code challenges do OAuth PKCE, dos identificadores de vídeo do YouTube e da maioria dos tokens de API, e ele dispensa o padding = com frequência também, porque num token o tamanho é conhecido implicitamente e os pads seriam apenas percent-escapes à espera de acontecer.
Entre os codificadores deste artigo, só a flag do Windows emite o alfabeto nativamente - o OpenSSL não tem modo URL-safe, e nenhum dos sabores do Boost tem - então na maioria das plataformas a receita é: codificar no padrão, trocar os dois caracteres, dispensar os pads. São uma dúzia de linhas:
#include <cstddef>
#include <cstdio>
#include <string>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
std::string base64url_encode(const std::string &in, bool pad = false) {
std::string out = base64_encode(in);
for (char &c : out) {
if (c == '+') c = '-';
else if (c == '/') c = '_';
}
if (!pad)
while (!out.empty() && out.back() == '=')
out.pop_back();
return out;
}
int main() {
std::printf("%s\n", base64url_encode("M\312\277").c_str());
std::printf("%s\n", base64url_encode("M").c_str());
std::printf("%s\n", base64url_encode("M", true).c_str());
}
A primeira linha de saída é Tcq_, onde o alfabeto padrão teria escrito /; as duas linhas seguintes mostram o interruptor de padding em ação - TQ sem padding por padrão, TQ== quando o consumidor quer de volta. Esse argumento pad é o que vale pensar, porque os consumidores não concordam: segmentos de JWT não querem pads, challenges PKCE não querem pads, mas um valor base64url que acaba num campo onde o decodificador é estrito sobre tamanho pode querer os pads de volta, e o interruptor é um bool, não uma reescrita. E o modo de falha para lembrar na direção oposta: um - dentro de um payload do alfabeto padrão é simplesmente inválido, então os dois alfabetos não são intercambiáveis no nível do byte - um token codificado com o alfabeto errado não decodifica, ele falha, que é a falha que você quer numa fronteira de segurança.
Quebra de linha: 64, 76, ou nunca
Base64 quebrado tem três comprimentos de linha na natureza, cada um com uma história. O codificador streaming do OpenSSL é duro em 64 caracteres - o hábito PEM, onde o padrão de 1987 do Privacy-Enhanced Mail quebrava em 64. O MIME, quando padronizou a codificação para email em 1993, mudou para 76 caracteres, e esse número é o padrão do comando base64 do coreutils (a flag -w dele define a largura, e -w 0 desliga a quebra por completo) e da maioria das ferramentas do ecossistema. O RFC 4648 em si não pega lado: ele cita 76 como o limite do MIME e diz às implementações para não quebrar de jeito nenhum, a menos que a especificação referenciada ordene. Qual você emite depende de quem consome, e o consumidor - não o formato - é a restrição de design.
Quebrar é um passo de pós-processamento na string codificada, nunca um passo de entrada: os grupos de 4 caracteres são a unidade de significado, então cortar a string em qualquer múltiplo da largura é um corte seguro - cada fronteira de linha cai entre grupos. A versão em C++ é um loop:
#include <cstddef>
#include <cstdio>
#include <string>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
std::string wrap_lines(std::string s, size_t width = 76) {
std::string out;
for (size_t i = 0; i < s.size(); i += width)
out += s.substr(i, width) + "\r\n";
return out;
}
int main() {
std::string mime = wrap_lines(base64_encode(std::string(200, 'x')));
int lines = 0;
for (char c : mime)
if (c == '\n') lines++;
std::printf("mime: %d lines, %zu chars\n", lines, mime.size());
}
A conta: 200 bytes codificam para 268 caracteres, e quebrado em 76 com terminadores CRLF são 4 linhas - três linhas cheias e uma cauda de 40 caracteres - 276 caracteres no fio. A escolha de CRLF no snippet é a escolha do email; para tudo o resto, LF é o padrão moderno, e a única regra inegociável é consistência - um decodificador que espera CRLF vai ler um LF solto como caractere de dados se estiver sendo estrito. (A regra do MIME é que os decodificadores devem ignorar quebras de linha, que é por isso que o email nunca sofreu com a diferença.) O terceiro hábito para conhecer: o comando openssl base64 - que é o programa enc de sobretudo, conferindo o próprio nome em argv[0] - quebra em 64 sem -A e emite uma linha com -A, e é a única ferramenta da linha de comando cujo comportamento você confere a cada execução em vez de confiar na memória.
Binário em JSON e config
Uma string JSON tem uma pequena lista de caracteres que ela não pode conter crus: a aspas, o backslash e os caracteres de controle abaixo de 0x20. Um certificado, uma chave aleatória, uma assinatura - todos eles estão cheios de bytes que virariam uma cascata de escapes se tentassem viajar dentro de um campo de string crua, e os caracteres de controle fariam alguns parsers engasgarem na hora. Base64 é a correção, e é a resposta padrão que todo formato de config que precisa carregar binário te dá: o valor é armazenado como uma única linha de caracteres puros do alfabeto, e as regras de aspas da biblioteca JSON não têm mais nada a fazer.
O padrão em C++ é a implementação inteira: leia os bytes (em modo binário, obviamente), codifique, armazene a string. O consumidor decodifica do outro lado. A única armadilha específica de JSON é a string quebrada: um certificado quebrado em 76 caracteres colado direto num arquivo JSON é uma string cheia de caracteres de controle literais, que é ou um erro de parse ou uma corrupção em silêncio, dependendo do humor do parser. Se o valor precisa ser quebrado para olhos humanos, ele precisa ser escapado ou precisa ser uma linha - e para config máquina a máquina, uma linha é a resposta. A outra armadilha é o valor sem rótulo: uma coluna de config que diz base64 numa man page de 2014 geralmente é alfabeto padrão com padding, mas tokens da era da API são URL-safe sem padding, e o teste dos quatro caracteres do guia de decodificação - ele contém + ou /, - ou _, um = no final? - é o diagnóstico inteiro.
Data URIs: arquivos que colam em páginas
Uma data URI é uma URL cujo payload está ali mesmo no endereço: data:, um tipo de mídia opcional, um marcador ;base64 opcional, uma vírgula e os dados em si - o esquema inteiro do RFC 2397. Navegadores usam para embutir imagens, fontes e scripts pequenos diretamente em HTML e CSS sem request extra, e se uma página continua funcionando com a rede desligada, uma data URI é uma suspeita forte. No lado do C++, o trabalho de codificação é montar a string, que é concatenação de string com uma constante:
#include <cstdio>
#include <string>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
std::string make_data_uri(const std::string &mime_type,
const std::string &binary) {
return "data:" + mime_type + ";base64," + base64_encode(binary);
}
int main() {
std::printf("%s\n", make_data_uri("text/plain", "hi").c_str());
}
As armadilhas estão todas nos detalhes. O marcador ;base64 tem exatamente sete caracteres, que é o tamanho que bugs de off-by-one caçam: um parser que confere seis é um parser que aceita data:text/plain;base4,... e decodifica lixo com a cara impassível. E o payload de uma data URI base64 é uma linha - quebras de linha não fazem parte da gramática da URI, então se o seu codificador quebrou a imagem em 76 (e codificadores em formato MIME quebram, por padrão), a URI está quebrada antes de chegar ao navegador. A regra para este consumidor: codifique, não quebre, e mantenha o tipo de mídia preciso - um image/png errado num JPEG é o tipo de mentira que só aparece como um thumbnail quebrado às 2 da manhã.
Tokens: JWTs, PKCE e chaves de API
O base64 com maiores stakes na internet está num token. Um JSON Web Token são três segmentos base64url colados com pontos: um JSON de header, um JSON de claims e uma assinatura calculada sobre a string header.claims. O C++ não tem um tipo JWT embutido, mas construir um é o codificador base64url de cima mais uma chamada HMAC, porque o token inteiro é base64url até o momento em que não é - até virar uma assinatura:
#include <cstddef>
#include <cstdio>
#include <string>
#include <openssl/evp.h>
#include <openssl/hmac.h>
/* base64_encode e base64url_encode das seções anteriores */
std::string jwt_hmac256(const std::string &signing_input,
const std::string &secret) {
unsigned char digest[EVP_MAX_MD_SIZE];
unsigned int len = 0;
HMAC(EVP_sha256(), secret.data(), static_cast<int>(secret.size()),
reinterpret_cast<const unsigned char *>(signing_input.data()),
signing_input.size(), digest, &len);
return std::string(reinterpret_cast<const char *>(digest), len);
}
int main() {
const std::string header_json = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
const std::string claims_json =
"{\"sub\":\"1234567890\",\"name\":\"John Doe\",\"iat\":1516239022}";
std::string head = base64url_encode(header_json);
std::string claims = base64url_encode(claims_json);
std::string signing_input = head + "." + claims;
std::string sig = base64url_encode(jwt_hmac256(signing_input, "secret"));
std::printf("token: %s\n", (signing_input + "." + sig).c_str());
}
Rode o exemplo e o token que sai é um token HS256 de manual: o header decodifica para {"alg":"HS256","typ":"JWT"}, os claims para um subject, um name e um timestamp de emissão, e a assinatura é o base64url de um HMAC-SHA256 sobre os dois segmentos codificados. Três detalhes carregam o design inteiro. A entrada de assinatura são os segmentos codificados, não o JSON cru - assine o JSON e você assinou os bytes errados. Os segmentos são base64url sem padding - os pads ficariam no meio de uma URL, e o ponto inteiro do alfabeto era manter o token uma string limpa. E HS256 significa um segredo compartilhado, que é um algoritmo de servidor para servidor: um segredo que mora no código de um client não é segredo, e o token que ele assina não é credencial. (O fluxo PKCE do OAuth usa o mesmo alfabeto a uma distância: um verifier aleatório, hasheado com SHA-256, base64url sem pads num code challenge - o codificador da seção base64url é a implementação inteira do lado do client.)
HTTP Basic Auth
O base64 mais antigo do HTTP é o header de credenciais: Authorization: Basic seguido do base64 de user:password, um esquema tão antigo que antecede o JSON. Construí-lo é uma concatenação:
#include <cstdio>
#include <string>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
std::string basic_auth_header(const std::string &user,
const std::string &pass) {
return "Basic " + base64_encode(user + ":" + pass);
}
int main() {
std::printf("%s\n", basic_auth_header("user", "password").c_str());
}
A saída é a string que você provavelmente já viu numa request capturada: Basic dXNlcjpwYXNzd29yZA==. Duas notas de C++. A concatenação user + ":" + pass é onde uma password contendo dois-pontos confundiria um parser ingênuo do outro lado - a regra de parse é "divida no primeiro dois-pontos", que é por isso que o lado que constrói é livre para colocar qualquer coisa em qualquer campo. E se as credenciais não forem ASCII, a leitura segura do esquema é tratar o user-id e a password como UTF-8 antes do base64, o que no C++ significa que o seu std::string já está fazendo o trabalho - desde que você o tenha preenchido com bytes UTF-8 e não com o que a locale decidiu. A nota de segurança pertence a toda menção deste esquema: Basic auth é ofuscação, não proteção. O header viaja em claro para qualquer um que consiga ler a rede, então só é aceitável atrás de TLS, e mesmo assim é a escolha para chamadas máquina a máquina, não para pessoas. (O codec do Boost.Beast - o do namespace detail:: - faz este mesmo trabalho de header dentro da implementação WebSocket do Boost, codificando em base64 o digest SHA-1 que vira a chave Sec-WebSocket-Accept, que é a prova discreta de que o padrão está fazendo isso desde 2017.)
Email: regras de sete bits, resposta Base64
Email é onde o base64 aprendeu os seus hábitos, e os hábitos ainda são carregantes. O SMTP, na sua forma original, foi construído para carregar ASCII de sete bits, então qualquer coisa binária tinha que ser reescrita como texto imprimível antes de poder viajar. O Privacy-Enhanced Mail fez isso em 1987 com linhas de 64 caracteres e uma verificação de integridade de mensagem RSA-MD2/MD5 colada no final, e o MIME, quando padronizou a codificação para email em 1993, relaxou o limite para 76 caracteres e adicionou a regra de que um decodificador conformante deve simplesmente ignorar as quebras de linha. Um anexo de email ainda é base64 hoje, quebrado em 76, e a aritmética exata dá 4/3 vezes 78/76 - cerca de 137 por cento do tamanho original, mais cerca de 814 bytes de headers.
O lado do C++ é o codificador mais a função de quebra de cima - codifique para uma linha, quebre em 76 com CRLF, pronto. Os dois detalhes específicos de email: a última linha pode ou não carregar uma quebra de linha final (os decodificadores são obrigados a ignorá-la, então as duas formas são legais e as duas são comuns), e o valor quebrado não é um valor JSON, uma variável de ambiente ou um token - é um blob que pertence a um corpo MIME, e movê-lo para qualquer outro lugar é onde a quebra deixa de ser um hábito e começa a ser um bug. A direção inversa - um anexo chegando quebrado em 76 - é território do guia irmão, onde os quatro decodificadores C++ discordam sobre quebras de linha de quatro jeitos diferentes.
Arquivos, streams e o teto de dois gigabytes
Codificar um arquivo é o espelho do trabalho com arquivos do guia de decodificação: abrir em modo binário (no Windows, uma leitura em modo texto traduziria pares CRLF em quebras de linha únicas e mudaria seus dados antes que o codificador os visse), ler os bytes, codificar, escrever em binário. A versão para arquivo pequeno é um trabalho de uma função:
#include <cstdio>
#include <fstream>
#include <iterator>
#include <string>
#include <vector>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
std::string encode_file(const std::string &path) {
std::ifstream in(path, std::ios::binary);
if (!in) return {};
std::vector<unsigned char> bytes{std::istreambuf_iterator<char>(in),
std::istreambuf_iterator<char>()};
return base64_encode(
std::string(reinterpret_cast<const char *>(bytes.data()), bytes.size()));
}
int main() {
std::string b64 = encode_file("/etc/hostname");
std::printf("file -> %zu chars\n", b64.size());
}
O teto é o fato específico de C++ do título da seção: todo parâmetro de tamanho na API EVP é um int. Uma única chamada EVP_EncodeBlock portanto codifica no máximo cerca de 2 GB de entrada, e o buffer de saída para essa chamada - 1.33 vezes maior - não cabe num int de jeito nenhum. Abaixo do teto, a API de bloco serve para arquivos que cabem na memória. Acima dele, ou para um arquivo que você não quer na memória, você faz chunks - e a regra de chunking é a única restrição específica de base64 no loop: os chunks têm que ser múltiplos de 3 bytes, porque o agrupamento é de três em três e uma fronteira de chunk no meio de um grupo muda a saída. 3072 - três chunks de 1024 bytes - é um tamanho de chunk confortável, e o loop vira:
#include <algorithm>
#include <cstddef>
#include <string>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
std::string encode_streamed(const std::string &data) {
std::string out;
for (size_t pos = 0; pos < data.size();) {
size_t take = std::min<size_t>(3072, data.size() - pos);
out += base64_encode(data.substr(pos, take));
pos += take;
}
return out;
}
Cada chunk codifica independentemente e a concatenação é idêntica ao resultado one-shot - que é a propriedade que torna o chunking seguro de algum jeito, e ela sai direto do agrupamento de 3 bytes. (O context streaming do OpenSSL da seção do codificador faz o mesmo trabalho enquanto adiciona quebra de linha em 64 caracteres de graça, que é a ferramenta certa quando o consumidor quer formato MIME.) E o lado da saída tem o mesmo orçamento que o lado da entrada: um arquivo de 10 GB vira uma string de 13.3 GB, então o buffer - ou o arquivo que você está escrevendo - é dimensionado com a fórmula da seção de matemática, e o teto int diz que o caminho em chunks não é uma conveniência acima de 2 GB - é o único caminho.
Variáveis de ambiente e a linha de comando
Variáveis de ambiente têm o mesmo problema que strings JSON e uma resposta pior: elas não carregam bytes NUL de jeito nenhum, e caracteres de controle também não são amigos delas. O truque padrão é codificar o payload em base64 para que ele sobreviva à shell, e no C++ a direção de codificação é um one-liner:
#include <cstdio>
#include <cstdlib>
#include <string>
/* base64_encode da seção "Quarenta linhas, zero dependências" */
int main() {
setenv("MY_PAYLOAD", base64_encode("hello, env").c_str(), 1);
std::printf("env: %s\n", getenv("MY_PAYLOAD"));
}
O valor que chega no ambiente é aGVsbG8sIGVudg==: alfabeto puro, seguro para a shell, seguro para um arquivo .env, seguro para um dashboard de CI, e decodificável em qualquer máquina que tenha um decodificador base64. A linha de comando em si tem a mesma história de duas ferramentas do lado da decodificação, com as flags de codificação: base64 do coreutils (ou a reimplementação uutils que distribuições mais novas trazem; confira com base64 --version) quebra em 76 por padrão, e -w 0 te dá uma linha; openssl base64 - o programa enc conferindo o próprio nome em argv[0] e trocando para o modo base64 - quebra em 64 e aceita -A para uma linha só:
# uma linha, para tokens e config
base64 -w 0 < payload.bin > payload.b64
openssl base64 -A < payload.bin > payload.b64
# quebrado, para email e arquivos de texto
base64 < payload.bin > payload-76.b64
openssl base64 < payload.bin > payload-64.b64
Nenhum fala base64url nativamente, então um token que você cunha numa shell ganha o tratamento de transcode antes de ir para uma URL. E a linha de comando é onde o hábito de falha silenciosa do lado da codificação é mais perigoso: um codificador que quebrou quando o seu consumidor esperava uma linha não vai dar erro, vai simplesmente produzir uma string com quebras de linha dentro - que é exatamente a falha que você agora está caçando em produção. Para qualquer coisa que importe, codifique no seu programa, onde o buffer é dimensionado pela fórmula e a forma da linha é uma variável que você controla.
Armadilhas: a edição C++
- O NUL que você não pediu.
EVP_EncodeBlockanexa um terminador NUL depois do payload. O exemplo da man page: 16 bytes de entrada, 24 codificados mais o NUL, 25 no buffer, 24 retornados. Reserve o byte extra e resize para o valor retornado, ou o seu token termina com um byte zero. - O 64 duro. A API streaming do OpenSSL quebra em 64 caracteres, todo bloco termina com quebra de linha, e não há flag para mudar isso. Saída de codificador quebrada num consumidor de uma linha é um bug de caractere de controle.
- O bloco de 48 bytes.
EVP_EncodeUpdatesó emite saída para blocos de entrada cheios de 48 bytes; o resto fica no context até oEVP_EncodeFinal. Reserve 65 bytes de saída por bloco mais o NUL, e não leia*outlcomo "bytes do meu payload" - é o que essa chamada escreveu, que para uma primeira chamada pequena é zero. - Os pads faltantes do iterador. A cadeia do Boost.Serialization nunca emite
=. Um iterador de 2002 codificando "Mane" te dá seis caracteres. Anexe os pads você mesmo, ou o seu consumidor estrito vai recusar a string. - O CRLF que você não pediu.
CryptBinaryToStringAanexa um par CR/LF a menos que você passeCRYPT_STRING_NOCRLF. Um token base64 construído com as flags padrão é dois caracteres mais longo do que deveria, e o penúltimo caractere é um carriage return. - A chamada NULL conta o NUL. A sonda de tamanho do Windows retorna o comprimento necessário incluindo o null terminador; a chamada de verdade devolve o comprimento sem ele. Misturar os dois é o off-by-one clássico, e ele escreve um byte além do buffer ou perde o último caractere.
- Quebre depois, não durante. Quebra de linha é um passo de pós-processamento na string codificada. Corte em múltiplos da largura - sempre seguro, porque cada grupo de 4 caracteres é autocontido - e nunca quebre os bytes crus, que é um lugar onde quebras de linha não pertencem.
- Chunks de três em três. Se você codifica um payload grande em pedaços, as fronteiras dos pedaços têm que cair em grupos de 3 bytes, ou o agrupamento - e a saída - muda. 3072 é um chunk simpático; 3071 é um bug.
- int, não size_t. Todo parâmetro de tamanho do EVP é um
int. O teto de uma chamada só é cerca de 2 GB de entrada, e a saída para essa entrada não cabe numintde jeito nenhum. Acima do teto, o caminho em chunks ou streaming não é uma preferência. - char assinado. Se você empacota de um
char *sem o cast unsigned, um byte acima de 127 é um número negativo em plataformas ondecharé assinado, e indexar uma tabela com ele é comportamento indefinido.const unsigned char *não é cerimônia. - A string JSON quebrada. Um valor quebrado em 76 caracteres colado num arquivo JSON é uma string de caracteres de controle literais. Ou é uma linha, ou está escapado, ou não está no JSON.
- Pads são um contrato. Alguns consumidores querem padding (MIME, a maioria dos decodificadores), outros não (JWT, PKCE, tokens em URLs), e alguns estritos rejeitam pads faltantes ou não canônicos sem cerimônia. O pad não é decoração; é parte do acordo do formato.
- Os dois alfabetos. Um
-ou_dentro de um payload do alfabeto padrão é inválido, e um+ou/dentro de um URL-safe é inválido. Os alfabetos não são intercambiáveis no nível do byte - codifique com o certo para o destino, e transcode com intenção. - std::string e strlen.
std::stringcarrega bytes zero com prazer, mas no momento em que você entrega uma string C para uma API legada,strlenpara no primeiro NUL. Passe ponteiro e tamanho, nunca um ponteiro nu. - O orçamento. A saída é 4/3 da entrada: se a entrada é 1.5 GB, a saída é 2 GB - que também é o teto
int. Dimensione o buffer receptor, a coluna e a rede com a fórmula, não com um chute.
Como o C++ teve o seu Base64
A história do formato é mais antiga que a era moderna da linguagem, e a história do C++ é a história da linguagem repetidamente não entregando. O primeiro uso padronizado da codificação hoje chamada de base64 do MIME foi o protocolo Privacy-Enhanced Mail, proposto em 1987 com linhas de 64 caracteres e uma verificação de integridade de mensagem RSA-MD2/MD5 colada no final; o nome "base64" em si só chegou em 1993, quando as especificações do MIME o batizaram. O C++ chegou como C++98 em 1998 - cinco anos depois do MIME - e o primeiro código base64 ao qual os desenvolvedores da linguagem recorreram foi o par em C de 2004-2008 de Rene Nyffenegger, que uma pergunta no Stack Overflow de 4 de dezembro de 2008 espalhou pela web. A melhor parte dessa história: uma resposta linkou a própria página de Nyffenegger e trouxe a implementação junto, header de licença e tudo, e uma resposta mais votada fez benchmark da solução dele contra o resto do campo. A canção folclórica tem um header de licença - o compositor em si nunca apareceu nos comentários.
Depois o ecossistema fez o que ecossistema faz. Em 2002, o Boost.Serialization de Robert Ramey trouxe os adaptadores de iteradores - o base64 mais antigo da caixa de ferramentas do C++, estrito na direção de decodificação e famosamente sem padding na direção de codificação, um ano antes do RFC 3548 codificar as regras do alfabeto que ele já aplicava. Em 2017, o Boost 1.66 trouxe o Beast, e com ele o codec header-only que ainda vem até hoje com o crédito de Nyffenegger no rodapé. O EVP_EncodeBlock do OpenSSL e cia. estão em toda release do OpenSSL, então o cavalo de batalha está na caixa de ferramentas desde que a linguagem está discutindo se ele deveria estar no padrão. No Windows, a história é simplesmente que o sistema operacional o entregou: uma função, uma tabela de flags, nenhum padrão envolvido de jeito nenhum. Enquanto isso, o padrão em si foi C++11, C++14, C++17, C++20, C++23 (publicado em 2024) e agora o C++26, e cada um deles olhou para o alfabeto de 64 caracteres e seguiu em frente. O conteúdo técnico do C++26 terminou e foi aprovado por voto (114-12-3) na reunião ISO C++ de março de 2026 em Croydon, Reino Unido, e ele de fato adiciona um novo header <text_encoding> para trabalho de codec de texto; as reuniões seguintes do comitê, em junho de 2026 (Brno) e novembro de 2026 (Búzios, Brasil), abrem o working draft do C++29 em vez de revisar o C++26. Base64 não está no padrão. Oito padrões, três décadas, um header para codificação de texto - e o comitê agora teve todas as desculpas possíveis para adicionar base64 e recusou todas. A história prática do base64 em C++ é, e continua sendo, a história das suas bibliotecas: um par EVP, dois sabores do Boost, uma flag do Windows e um snippet de quarenta linhas que é seu.
Curiosidades que valem a pena saber
- O bloco de 48 bytes do codificador streaming do OpenSSL é um número que não aparece em nenhum RFC. São 16 grupos base64, escolhidos para que a linha de saída tenha exatamente 64 caracteres - o hábito PEM - e é um dos últimos lugares onde 1987 ainda está fazendo trabalho de sustentação em 2026.
- O
encoded_sizedo Boost.Beast é a seção de matemática como uma funçãoconstexpr:4 * ((n + 2) / 3), avaliada em tempo de compilação quando você dá a ela uma constante. A biblioteca padrão nunca teve esse one-liner; o Boost o entregou num namespacedetail::, no lugar. - O base64 com padding menor possível são quatro caracteres,
QQ==: um byte vestindo um disfarce de dois caracteres. O menor sem padding são dois caracteres,QQ. A contagem de pads é também uma mensagem: dois pads significa que o último grupo tinha um byte, um pad significa que tinha dois, e sem pads significa que tinha três - o receptor consegue recuperar o tamanho da entrada da cauda sozinha. - A matemática do overhead do MIME é exata: 4/3 vezes 78/76, que é por isso que um anexo de email chega com cerca de 137 por cento do tamanho original, mais cerca de 814 bytes de headers. Cada codificador deste artigo paga o mesmo imposto; a largura da quebra só muda como ele é cobrado.
- Numa libstdc++ ou MSVC típica,
std::stringcarrega payloads pequenos num buffer da pilha através da small-string optimization em vez de alocar. Uma entrada de 9 bytes codifica para 12 caracteres e nunca toca o heap. A forma base64 do seu token pode literalmente morar num stack frame, que é o tipo de almoço grátis que a biblioteca padrão não anuncia. - O comando
openssl base64ao qual você pode recorrer numa shell não é um comando de jeito nenhum. É o programaencconferindo o próprio nome emargv[0]e trocando de personalidade. Um alias por comparação de string, que é o jeito C++ de fazer as coisas, em C. - Identificadores de vídeo do YouTube são base64url: onze caracteres, sem padding, nenhum
+ou/por perto de uma URL. O formato de codificação mais assistido do planeta roda na variante "Seguro para URL e Nome de Arquivo" que o RFC 4648 adicionou numa seção que cabe numa página. - Quatro As -
AAAA- codificam três bytes zero, porque A é o zero do alfabeto. Se você já viu um blob base64 feito inteiramente de um caractere, agora você sabe o que ele estava dizendo: nada. - O mesmo par de funções aparece nas respostas a uma pergunta de 2008 no Stack Overflow, na fonte do Boost.Beast com um rodapé de crédito, e nos headers de incontáveis codebases privados. Pergunte a um desenvolvedor C++ de onde veio o base64 dele e a resposta mais honesta é "eu não sei, e a internet também não".
A outra direção
Tudo o que você acabou de empacotar será desempacotado pela mesma caixa de ferramentas do outro lado, e o lado do desempacotamento tem o seu próprio conjunto de hábitos: a função one-shot do OpenSSL que enche a cauda com zeros, o bugfix de 2025 que mudou o que o decodificador streaming retorna para entrada com padding, o decode do Boost.Beast que para num caractere solto e nunca diz uma palavra, o iterador que joga exceção num único espaço, e o decodificador estrito de quarenta linhas que aponta para o byte exato que machucou. A história completa do desempacotamento - os temperamentos dos quatro decodificadores, transcode base64url, arquivos, o hábito dos 76 caracteres do MIME e as duas ferramentas de linha de comando que falham em silêncio - mora no guia de decodificação C++ no site irmão. Vá ler, depois volte e empacote algo grande. Esse é o jogo inteiro: sem biblioteca padrão, quatro fornecedores com quatro opiniões diferentes sobre quebras de linha e NULs, uma fórmula que dimensiona todo buffer do artigo e um imposto de 33 por cento que cada receptor pode devolver. Bom empacotamento.
Última atualização: 2026-09-08
Artigo relacionado: Decodificação Base64 em C++ (Cpp): um guia completo