Você tem que lidar com o formato Base64? Então esse site é perfeito para você! Use o nosso ferramenta online super útil para codificar ou decodificar os seus dados.

Decodificação Base64 em C# (CSharp): um guia completo

Você reconhece na hora: um rio de letras e dígitos, o ocasional + ou /, e talvez um ou dois = pendurados no final. Em algum lugar entre uma resposta de API, um anexo de e-mail, um arquivo de configuração e um JWT, alguém empacotou dados binários em texto, e agora cabe a você abri-lo. Essa é a face da decodificação do Base64 em C#, e a primeira boa notícia é que você não precisa de nada além do framework. O decodificador mora no namespace System há mais de vinte anos, e todo runtime moderno do .NET ainda o traz de fábrica, com mais opções e melhor desempenho do que o original.

Um rápido refresco, porque a página inicial deste site explica o formato por completo: quatro caracteres de um alfabeto de 64 símbolos carregam três bytes de dados, e um ou dois caracteres = na cauda marcam os bytes sobrados. Decodificar é correr essa troca ao contrário, então o resultado tem mais ou menos três quartos do tamanho da entrada. Com a forma do problema em mente, vamos abrir alguns pacotes.

A família de decodificadores: conheça suas opções

Antes do primeiro exemplo, aqui está a família inteira de APIs de decodificação para as quais você pode recorrer, e a situação para a qual cada uma foi construída. Tudo o que está listado aqui faz parte do próprio runtime do .NET, exceto a classe URL-safe nos frameworks mais antigos, que chega junto com um pequeno pacote NuGet:

API Disponível desde Para que serve
Convert.FromBase64String(string) .NET Framework 1.1 (2003) O clássico. Uma string entra, uma byte[] nova sai. Pula espaços em branco comuns e joga exceção em qualquer outra coisa.
Convert.FromBase64CharArray(char[], int, int) .NET Framework 1.1 (2003) O mesmo decode, lendo de um recorte de um buffer de caracteres que você já possui.
Convert.TryFromBase64String, Convert.TryFromBase64Chars .NET Core 2.1 (2018) Booleano em vez de exceções, escrevendo num span que você fornece. O guarda amigável para entrada não confiável.
System.Buffers.Text.Base64 .NET Core 2.1 (2018) A API estrita de span: códigos de status em vez de exceções, decodificação in-place e pré-verificações com IsValid.
System.Buffers.Text.Base64Url .NET 9 (2024) O alfabeto URL-safe (- e _ em vez de + e /), com ou sem padding. No .NET Framework 4.6.2+ e no .NET Standard 2.0: o pacote NuGet Microsoft.Bcl.Memory.
FromBase64Transform + CryptoStream .NET Framework 1.1 (2003) Decodificação em streaming: arquivo para arquivo, rede para disco, pedaço por pedaço, sem carregar o payload inteiro.

Se o seu projeto mira uma versão do .NET de 2018 em diante, as quatro primeiras linhas já estão na caixa. O Base64Url precisa do .NET 9 ou mais novo, ou do pacote Microsoft.Bcl.Memory em qualquer versão anterior. E um aviso para frente: as bibliotecas do .NET 11, em preview na data de escrita com lançamento geral esperado para o fim de 2026, adicionam mais APIs e sobrecargas de conveniência Base64 aos tipos existentes, então a família continua crescendo. Nada mais neste artigo exige um pacote.

O cavalo de batalha: Convert.FromBase64String

Noventa por cento da vida de decodificação em C# é uma única chamada. Entregue uma string a ele, e ele devolve os bytes exatos que estavam empacotados dentro:

using System;
using System.Text;
string packed = "TWFu";
byte[] bytes = Convert.FromBase64String(packed);
string text = Encoding.UTF8.GetString(bytes);
Console.WriteLine(text);
// Man

Três detalhes valem a pena guardar na memória. Primeiro, o valor retornado é bytes, não texto: é uma byte[], o decodificador é orientado a bytes do começo ao fim, e é exatamente o que você quer, porque o payload pode ser uma frase, um PNG, um certificado ou um hash, e nenhum deles deve ser tratado como especial. O salto de bytes de volta para texto legível é um passo separado e deliberado através da Encoding, e é nesse passo que vivem as decisões de charset (mais sobre isso abaixo). Segundo, o decodificador aloca um array novo a cada chamada, dimensionado para o comprimento decodificado, então nunca te entrega um buffer com capacidade sobrando. Terceiro, o contrato é pequeno e honesto: uma string vazia decodifica para um array vazio, uma referência null joga ArgumentNullException, e qualquer coisa que não seja Base64 válido joga FormatException. Tudo o mais é uma elaboração dessas três regras.

O que ele perdoa e o que ele recusa

Aqui é onde o decodificador do C# tem personalidade, e uma personalidade marcante. Ele é generoso em exatamente uma coisa - espaços em branco - e implacável com tudo o resto. O decodificador pula exatamente quatro caracteres onde quer que apareçam na string: o espaço (U+0020), a tabulação (U+0009), a quebra de linha (U+000A) e o retorno de carro (U+000D). Essa política é uma referência deliberada ao e-mail, onde payloads Base64 chegam embrulhados em linhas de 76 caracteres, e isso significa que um anexo embrulhado em MIME decodifica com zero pré-processamento. Qualquer coisa fora do alfabeto de 64 símbolos, qualquer coisa que quebre as regras de comprimento, ou qualquer coisa com padding no lugar errado leva uma exceção. Veja o mesmo decodificador em ação com algumas entradas diferentes:

Entrada Resultado
"TWFu" Decodifica para Man (3 bytes).
"TWF\nu" (uma quebra de linha no meio) Decodifica para Man. Espaços em branco são invisíveis para o decodificador.
"TWFu\u00A0" (um espaço não separável no final) FormatException. Apenas os quatro caracteres de espaço em branco acima são pulados; o NBSP não é um deles.
"TWE" (comprimento 3, não um múltiplo de 4) FormatException. O comprimento do payload, ignorando espaços em branco, tem que ser um múltiplo de 4.
"TWFu=" (padding extra depois dos dados) FormatException. No máximo dois caracteres de padding, e só bem no final.
"-_88" (alfabeto URL-safe) FormatException. O decodificador padrão só conhece os 64 caracteres do alfabeto padrão.
null ArgumentNullException: Value cannot be null. (Parameter 's')

Uma última particularidade que vale a pena memorizar: todo crime de formato leva a mesma mensagem de erro única, The input is not a valid Base-64 string as it contains a non-base 64 character, more than two padding characters, or an illegal character among the padding characters. A mensagem lista as três causas possíveis e não diz qual delas você pegou, nem te conta onde. Se você está depurando um payload que falha, conte os caracteres, verifique o alfabeto e verifique o padding, nessa ordem.

Decodificando sem exceções: as APIs Try

O fluxo de controle dirigido por exceções é um padrão legítimo, mas para volume alto ou entrada não confiável a família Try é a cidadã melhor. Ela foi adicionada no .NET Core 2.1 e vem em dois sabores: um que lê de uma string e um que lê de um span de caracteres. Ambos escrevem num buffer que você fornece e relatam quanto dele encheram:

using System;
using System.Text;
string payload = "TWFu"; // qualquer payload, válido ou não
Span<byte> buffer = stackalloc byte[4096];
if (Convert.TryFromBase64String(payload, buffer, out int written))
{
  string text = Encoding.UTF8.GetString(buffer[..written]);
  Console.WriteLine(text);
}
else
{
  Console.WriteLine("Not a valid Base64 payload.");
}

Dois comportamentos fazem as variantes Try parecerem uma espécie diferente. Entrada inválida retorna false em vez de jogar exceção, então um fluxo de payloads malformados custa um branch em vez de uma exceção. Uma ressalva: uma entrada null não faz parte do contrato - ela joga ArgumentNullException - então o guarda Try cobre payloads quebrados, e um valor que pode estar faltando ainda precisa do próprio null check antes. O método irmão Convert.TryFromBase64Chars faz o mesmo trabalho a partir de um ReadOnlySpan<char>, o que é útil quando o payload vive num buffer de caracteres maior e você não quer cortar um substring antes. Dimensione o buffer de saída com folga: o comprimento decodificado tem no máximo três quartos do comprimento da entrada (sem espaços em branco), e o parâmetro de saída written te diz exatamente quanto saiu.

Decodificação baseada em span com System.Buffers.Text.Base64

Quando você está contando alocações, ou quando quer que o decodificador descreva suas falhas em vez de jogá-las, a classe System.Buffers.Text.Base64 é a ferramenta. Ela é uma classe estática na biblioteca padrão desde o .NET Core 2.1, e trabalha com spans em vez de arrays gerenciados. Seu método de decode retorna um valor OperationStatus com quatro humores: Done (sucesso), DestinationTooSmall (seu buffer era pequeno demais), NeedMoreData (a entrada ainda não é um múltiplo de 4, continue lendo) e InvalidData (isso não é Base64). O último parâmetro booleano, isFinalBlock, é o que distingue esses dois: ele diz ao decodificador se ainda vai chegar mais entrada. Aqui está a forma one-shot, dimensionada com o próprio helper da classe:

using System.Buffers;
using System.Buffers.Text;
using System.Text;
string payload = "TWFu";
byte[] input = Encoding.ASCII.GetBytes(payload);
byte[] output = new byte[Base64.GetMaxDecodedFromUtf8Length(input.Length)];
OperationStatus status = Base64.DecodeFromUtf8(input, output,
  out int consumed, out int written, isFinalBlock: true);
if (status == OperationStatus.Done)
{
  Console.WriteLine(Encoding.UTF8.GetString(output.AsSpan(0, written)));
  // Man
}

Dois membros dessa classe merecem um parágrafo. O primeiro é IsValid, que valida um payload sem decodificá-lo. Ele vem em sabores de span de bytes e de span de caracteres, e uma sobrecarga relata o comprimento decodificado junto com o veredito, então você pode dimensionar um buffer a partir de uma única verificação:

using System.Buffers.Text;
string payload = "TWFu";
if (Base64.IsValid(payload, out int decodedLength))
{
  Console.WriteLine("Valid, decodes to " + decodedLength + " bytes.");
  // Valid, decodes to 3 bytes.
}
else
{
  Console.WriteLine("Rejecting payload before allocating anything.");
}

O segundo é DecodeFromUtf8InPlace, para a situação em que o texto Base64 já está num buffer que você possui e você não se importa em sobrescrevê-lo. Decodificar encolhe os dados, então o resultado é escrito no início do mesmo buffer e o método relata qual é o comprimento:

using System.Buffers;
using System.Buffers.Text;
using System.Text;
byte[] data = Encoding.ASCII.GetBytes("TWFu");
OperationStatus status = Base64.DecodeFromUtf8InPlace(data, out int written);
if (status == OperationStatus.Done)
{
  Console.WriteLine(Encoding.ASCII.GetString(data, 0, written));
  // Man, agora morando nos três primeiros bytes do mesmo buffer
}

Um comportamento para guardar no bolso: essa classe também pula os quatro caracteres comuns de espaço em branco (espaço, tabulação, quebra de linha, retorno de carro), então um payload quebrado por linhas decodifica igualmente bem. Ela é estrita nas coisas que importam: um payload cujo comprimento sem espaços em branco não é múltiplo de quatro é InvalidData quando é o bloco final, e caracteres fora do alfabeto padrão são rejeitados na hora. Não existe limpeza silenciosa em lugar nenhum dessa classe.

Base64 URL-safe: a classe Base64Url

Existe um segundo alfabeto para os mesmos 64 valores, e você vai encontrá-lo constantemente em trabalho web com C#. No alfabeto padrão, os valores 62 e 63 são + e /, dois caracteres que causam problemas em URLs: um + em uma query string é rotineiramente decodificado como espaço, e / e = cada um precisa de percent-encoding. O RFC 4648, seção 5, resolve isso substituindo por - e _, que não carregam significado especial em nenhum contexto de URL, e torna o padding final com = opcional. O resultado se chama base64url, e é o alfabeto dos JWTs, tokens de API, IDs de upload de arquivo e de uma grande quantidade de URLs (os identificadores de vídeo de 11 caracteres do YouTube são base64url sem padding).

Desde o .NET 9 a biblioteca padrão traz uma classe dedicada para isso: System.Buffers.Text.Base64Url. Ela é a gêmea URL-safe da classe Base64, com seus próprios helpers de decode, validação e comprimento:

using System.Buffers.Text;
using System.Text;
string token = "-__8";
byte[] bytes = Base64Url.DecodeFromChars(token);
Console.WriteLine(BitConverter.ToString(bytes));
// FB-FF-FC

Note o que a API clássica não teria feito com aquele exemplo. Os mesmos três bytes codificam como +//8 no alfabeto padrão, e Convert.FromBase64String("+//8") funciona, mas Convert.FromBase64String("-__8") joga exceção, porque os caracteres URL-safe estão fora do alfabeto dela. E payloads base64url frequentemente chegam sem padding, o que o decodificador clássico também rejeita, porque ele insiste no grupo completo de quatro. A classe Base64Url lida com as duas variantes do problema de forma nativa: ela decodifica TWE (três caracteres, sem padding) nos dois bytes Ma, e decodifica TWE= igualmente bem.

Se o seu projeto roda num runtime mais antigo, há dois caminhos práticos. No .NET Framework 4.6.2 para cima, adicione o pacote NuGet Microsoft.Bcl.Memory, que a Microsoft publica especificamente para backportar o Base64Url (junto com alguns outros tipos modernos):

dotnet add package Microsoft.Bcl.Memory

Ou, sem nenhum pacote, normalize o payload antes de entregá-lo ao decodificador clássico: troque os caracteres URL-safe de volta para seus gêmeos padrão e complete o padding que falta. Esse pequeno helper é o decodificador base64url artesanal mais comum em código C#, e vale a pena conhecer porque funciona em todo runtime desde o .NET Framework 1.1:

using System;
using System.Text;
string segment = "TWE";
segment = segment.Replace('-', '+').Replace('_', '/');
segment += new string('=', (4 - segment.Length % 4) % 4);
byte[] bytes = Convert.FromBase64String(segment);
Console.WriteLine(Encoding.ASCII.GetString(bytes));
// Ma

A fórmula (4 - length % 4) % 4 é a aritmética inteira do padding: ela adiciona zero, um ou dois caracteres = para o comprimento cair num múltiplo de quatro, e o módulo externo impede que uma entrada já com padding ganhe caracteres extras.

De bytes para palavras: texto, Unicode e charsets

Decodificar te dá bytes, e bytes são uma coisa perfeitamente neutra. Eles só viram "texto" quando você escolhe um charset para lê-los, e essa escolha é sua para fazer, porque o Base64 não carrega nenhuma informação sobre qual charset o autor original usou. Na prática isso significa: assuma UTF-8 a menos que você tenha um motivo para não, e seja explícito sobre isso no código, porque uma chamada explícita de Encoding.UTF8 é a diferença entre um programa que está certo por acidente e um que está certo por design:

using System;
using System.Text;
string original = "h\u00e9llo \u4e16\u754c";
byte[] utf8 = Encoding.UTF8.GetBytes(original);
string packed = Convert.ToBase64String(utf8);
byte[] decoded = Convert.FromBase64String(packed);
string restored = Encoding.UTF8.GetString(decoded);
Console.WriteLine(restored == original);
// True: h\u00e9llo \u4e16\u754c faz a ida e volta sem perder nada

A armadilha sutil é o que acontece quando os bytes não são UTF-8 válido, porque o payload era na verdade Latin-1, ou binário, ou simplesmente corrompido. Por padrão, o decodificador UTF-8 do .NET substitui toda sequência malformada pelo caractere de substituição Unicode (U+FFFD) e segue em frente. Sem exceção, sem aviso: os dados simplesmente somem, transformados em interrogações no seu banco de dados. Se você precisa saber quando isso acontece, construa a encoding com um fallback estrito, que transforma a substituição silenciosa numa DecoderFallbackException bem barulhenta:

using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // os bytes FF FE, não é UTF-8 válido
Encoding strictUtf8 = Encoding.GetEncoding(
  "utf-8",
  new EncoderExceptionFallback(),
  new DecoderExceptionFallback());
string text = strictUtf8.GetString(bytes);
// Joga DecoderFallbackException, porque FF FE não é uma sequência UTF-8

Para payloads nos quais você prefere sobreviver a falhar, os fallbacks de substituição são a opção mais branda, e você escolhe o texto de substituição você mesmo:

using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // os bytes FF FE, não é UTF-8 válido
Encoding forgivingUtf8 = Encoding.GetEncoding(
  "utf-8",
  EncoderFallback.ReplacementFallback,
  new DecoderReplacementFallback("[bad]"));
string text = forgivingUtf8.GetString(bytes);
Console.WriteLine(text);
// [bad][bad] em vez da substituição silenciosa por U+FFFD

Uma última lição de história específica do C#: Encoding.Default significa coisas diferentes em runtimes diferentes. No .NET Framework no Windows é a code page ANSI do sistema (frequentemente Windows-1252), enquanto no .NET (Core) é UTF-8 sem BOM. Código que faz ida e volta de um payload através de Encoding.Default pode portanto produzir bytes diferentes numa máquina de 2010 e numa máquina de 2025, e o Base64 vai codificar de bom grado qualquer conjunto que você lhe entregue. Se você algum dia vir uma string decodificada cheia de mojibake acentuado, Encoding.Default é o primeiro lugar para olhar.

Arquivos e payloads binários

Arquivos são o alvo de decodificação mais direto, porque a questão do charset nem existe: os bytes que você decodifica são o arquivo, byte por byte, zeros e tudo. O padrão é duas chamadas e um arquivo, e ele aparece em tudo, de uploads de imagem a ferramentas de backup:

using System.IO;
string b64 = File.ReadAllText("payload.b64");
byte[] original = Convert.FromBase64String(b64);
File.WriteAllBytes("restored.bin", original);
Console.WriteLine("Restored " + original.Length + " bytes.");

Duas notas práticas. Se o arquivo pode conter espaços em branco ou quebras de linha (o que, sendo um arquivo de texto, quase certamente contém), o decodificador clássico resolve de graça, como você viu antes. E se o payload for grande, não passe por uma string em momento algum: pule o passo de arquivo-para-string e decodifique direto do stream, que é a próxima seção. Para um payload decodificado que é texto e cujo charset você acontece de saber, o exemplo de arquivo é a solução inteira, e o passo de Encoding.UTF8.GetString da seção de charsets encaixa certinho entre o decode e o uso.

Decodificando de um stream: FromBase64Transform

Os métodos Convert são projetados para payloads que cabem numa string, e a documentação oficial diz isso em palavras: para dados em streaming, use as classes de transform. O FromBase64Transform faz parte do System.Security.Cryptography desde o .NET Framework 1.1 (2003), e ele se encaixa no CryptoStream, o cano de propósito geral do framework para transformar dados enquanto fluem. O decode de arquivo-para-arquivo inteiro é um setup de quatro linhas:

using System.IO;
using System.Security.Cryptography;
using FileStream source = File.OpenRead("payload.b64");
using FromBase64Transform transform =
  new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using CryptoStream reader = new CryptoStream(source, transform, CryptoStreamMode.Read);
using FileStream target = File.Create("payload.bin");
reader.CopyTo(target);
Console.WriteLine("Done, " + target.Length + " bytes written.");

O construtor recebe um modo, e os dois modos valem a pena conhecer pelo nome. IgnoreWhiteSpaces (o padrão, alinhado à política de espaços em branco do decodificador clássico) pula os quatro caracteres comuns de espaço em branco enquanto o stream flui, o que é o que você quer para payloads embrulhados em e-mail ou repletos de quebras de linha. DoNotIgnoreWhiteSpaces é estrito: o primeiro caractere fora do alfabeto que ele encontra joga uma FormatException, o que é o que você quer quando um espaço solto no payload deveria ser um bug, não um encolher de ombros. Por baixo dos panos a transform processa a entrada em grupos de quatro caracteres e devolve os três bytes que cada grupo produz, com o TransformFinalBlock cuidando da cauda. Raramente você chama esses métodos você mesmo, porque o CryptoStream faz isso por você, mas o fato do grupo-de-quatro importa: se algum dia você for alimentar a transform manualmente, alimente em múltiplos de quatro, ou o último grupo parcial vai parar no bloco final.

JWTs: três segmentos, um ponto

Um JSON Web Token é o payload base64url com mais tráfego no desenvolvimento web em C#, e sua forma é enganosamente simples: três segmentos separados por pontos. O primeiro é o header codificado, o segundo o payload codificado (também chamado de claims), e o terceiro a assinatura. Cada um dos dois primeiros é base64url de um documento JSON UTF-8, sem padding, conforme a especificação JWS. Dividir e decodificar são duas linhas de C#:

using System;
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
string jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsIm5hbWUiOiJBZGEifQ.c2lnbmF0dXJl";
string[] parts = jwt.Split('.');
string headerJson = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(parts[0]));
string payloadJson = Encoding.UTF8.GetString(Base64Url.DecodeFromChars(parts[1]));
using JsonDocument doc = JsonDocument.Parse(payloadJson);
Console.WriteLine(doc.RootElement.GetProperty("name").GetString());
// Ada

Em runtimes anteriores ao .NET 9, o mesmo trabalho passa pelo helper de normalização da seção URL-safe: troque - e _ de volta para + e /, complete o segmento até um múltiplo de quatro e decodifique com Convert.FromBase64String. As duas abordagens te dão o mesmo JSON; escolha a que combina com o seu framework alvo.

Um limite para manter afiado: decodificar um JWT não é verificar um JWT. O decode acima vai ler de bom grado os claims de um token com uma assinatura de lixo, porque a assinatura é uma verificação criptográfica separada sobre os dois primeiros segmentos. Para trabalho com tokens em produção, não faça parsing à mão de jeito nenhum: o pacote System.IdentityModel.Tokens.Jwt (da família Microsoft.IdentityModel) cuida de parsing, validação e expiração de uma vez, e o tratamento de base64url dele é exatamente o alfabeto que esta seção descreve. Decodifique à mão para depuração e utilitários pequenos; verifique com a biblioteca para tudo o que um usuário pode alcançar.

Data URIs e imagens embutidas

Existe uma classe inteira de código C# cujo trabalho é receber um URI data:, porque HTML, CSS e uma grande quantidade de APIs web os usam para embutir conteúdo binário inline. O scheme, padronizado pelo RFC 2397, é data:[mediatype][;base64],payload: tudo antes da primeira vírgula é metadado (o tipo MIME e a flag ;base64), tudo depois é o payload. Quando a flag ;base64 está presente, o payload é uma string Base64, e dividir na vírgula é o parse inteiro:

using System;
using System.Text;
string dataUri = "data:image/png;base64,iVBORw0KGgo=";
int comma = dataUri.IndexOf(',');
string mediaType = dataUri[..comma];        // data:image/png;base64
string b64 = dataUri[(comma + 1)..];        // iVBORw0KGgo=
byte[] imageBytes = Convert.FromBase64String(b64);
Console.WriteLine(imageBytes.Length);
// 8: os bytes da assinatura PNG 89 50 4E 47 0D 0A 1A 0A

O prefixo iVBORw0KGgo= no exemplo é a forma Base64 do número mágico PNG de oito bytes, e é uma impressão digital útil: qualquer data URI de um PNG de verdade começa assim, então é um sanity check rápido quando você está fazendo parsing de HTML não confiável. Duas notas práticas para desenvolvedores C#. Primeiro, a classe Uri entende data URIs nativamente no .NET: new Uri("data:text/plain;base64,TWFu") faz parsing sem problema e relata Scheme == "data", então se o seu código roteia por URIs, data URIs vão aparecer no pipeline e você deve decidir como tratá-los. Segundo, lembre o que um data URI realmente é: uma cópia completa do arquivo, inflada em um terço, morando dentro do seu documento. Isso é bom para um favicon de 4 KB e doloroso para um logo de 4 MB, então quando você é quem os gera (o artigo de encoding cobre esse lado), dimensione a imagem antes de codificá-la.

HTTP: autenticação Basic e trocas de API

O Base64 está tecido no HTTP em pelo menos um lugar que você vai tocar em qualquer trabalho com APIs: o esquema de autenticação Basic. O cliente envia Authorization: Basic seguido da codificação Base64 de username:password, unidos por dois-pontos. No lado do servidor, decodificar um header recebido é portanto: tirar o prefixo Basic , decodificar e dividir no primeiro dois-pontos:

using System;
using System.Text;
string header = "Basic YWRhOnMzY3JldA==";
string encoded = header["Basic ".Length..].Trim();
string credentials = Encoding.UTF8.GetString(Convert.FromBase64String(encoded));
int colon = credentials.IndexOf(':');
string user = credentials[..colon];
string password = credentials[(colon + 1)..];
Console.WriteLine(user);      // ada
Console.WriteLine(password);  // s3cret

O passo do UTF-8 importa mais do que parece: o RFC 7617 na verdade não fixa o charset, deixando o padrão indefinido por compatibilidade reversa e permitindo apenas uma dica consultiva de UTF-8, mas é essa dica que todo servidor moderno espera, então um username com caractere acentuado produz uma string de bytes diferente (e correta) do mesmo username lido como Latin-1. O lado do decode da autenticação Basic é a ponta simples desse padrão; no ASP.NET Core você normalmente vai encontrá-lo através dos handlers de autenticação em vez de headers crus, mas a mesma lógica de decode é o que eles rodam por baixo, e é exatamente o tipo de código que você precisa quando escreve testes de integração que fingem um servidor de API. A operação espelhada, montar o header no lado do cliente, é um one-liner no lado do encoding, e recebe um exemplo completo no artigo de encoding.

E-mail: MIME e payloads quebrados por linha

O e-mail é onde o Base64 ganhou sua reputação, e ele continua sendo a origem de muitos dos payloads que serviços em C# recebem. O SMTP era originalmente um protocolo de 7 bits, então anexos binários não podem viajar crus: a especificação MIME (RFC 2045) os codifica como Base64 com o header Content-Transfer-Encoding: base64, quebra a saída em 76 caracteres e separa as linhas com pares de retorno de carro e quebra de linha. O corpo de um anexo de verdade portanto parece uma coluna de linhas de 76 caracteres, e a boa notícia para o C# é que o decodificador clássico já sabe ler isso: como ele pula espaços em branco em qualquer lugar da string, você pode entregar o corpo inteiro embrulhado, quebras de linha e tudo, e ele decodifica como se as quebras nunca tivessem existido:

using System;
using System.Text;
string attachmentBody = "TWFu\r\nTWFu\r\nTWFu";
byte[] bytes = Convert.FromBase64String(attachmentBody);
Console.WriteLine(Encoding.ASCII.GetString(bytes));
// ManManMan

Para payloads que chegam por um stream em vez de uma string, o FromBase64Transform com seu modo de ignorar espaços em branco é a mesma história com roupa de streaming. E quando você precisa fazer mais do que decodificar o corpo, quando precisa percorrer a estrutura MIME, fazer parsing de headers, lidar com seções multipart aninhadas, ou extrair cada anexo de um arquivo .eml de verdade, a resposta do ecossistema em C# é o pacote MimeKit: ele é a biblioteca MIME padrão para .NET, trata internamente as codificações de transferência de conteúdo Base64 e quoted-printable, e é a ferramenta para pegar no momento em que "só decodificar o corpo" deixa de descrever o seu problema. A própria classe MailMessage do framework vai decodificar anexos simples por você, mas o suporte MIME dela é deliberadamente modesto pelos padrões modernos.

Certificados PEM

O PEM é o formato blindado do mundo TLS: um corpo Base64 entre os marcadores -----BEGIN CERTIFICATE----- e -----END CERTIFICATE-----, quebrado em 64 caracteres, conforme especificado pelo RFC 7468. Desenvolvedores C# o encontram nos arquivos de certificado por trás de todo endpoint HTTPS, e a história do decode aqui é melhor do que você talvez esperasse, porque desde o .NET 6 o framework faz parsing do PEM por você, corpo Base64 e tudo:

using System.IO;
using System.Security.Cryptography.X509Certificates;
string pem = File.ReadAllText("server.pem");
X509Certificate2 certificate = X509Certificate2.CreateFromPem(pem);
Console.WriteLine(certificate.Subject);
// CN=server.example.com

Nenhum Base64 manual em lugar nenhum disso: o CreateFromPem acha os marcadores, desembala o corpo, decodifica e te devolve um certificado vivo. (A família tem irmãos para chaves privadas e para a forma combinada de certificado-mais-chave, se a sua infraestrutura te entregar essas.) Se você está num runtime mais antigo, ou precisa dos bytes DER crus que estão dentro da armadura, a versão manual é um strip-and-decode em dois passos, e vale a pena conhecer porque o mesmo padrão funciona para qualquer coisa blindada em PEM:

using System;
using System.Text;
string pem = File.ReadAllText("server.pem");
string body = pem
  .Replace("-----BEGIN CERTIFICATE-----", "")
  .Replace("-----END CERTIFICATE-----", "")
  .Replace("\r", "")
  .Replace("\n", "");
byte[] der = Convert.FromBase64String(body);
Console.WriteLine(der.Length);
// O comprimento do certificado DER dentro da armadura

As armadilhas nesse canto são todas de espaço em branco: arquivos PEM trazem finais de linha CRLF da maioria das ferramentas de certificado, então remova tanto o \r quanto o \n antes de decodificar, não apenas os line feeds. E não confunda o corpo do certificado com o corpo de uma chave privada, que tem marcadores diferentes e conteúdo diferente; um decodificador não vai te salvar dessa.

Configuração, variáveis de ambiente e bancos de dados

O terceiro lar do Base64 em aplicações C# é o armazenamento: arquivos de configuração, variáveis de ambiente e colunas de banco de dados. O padrão é o mesmo em todo lugar. Um valor binário ou secreto é codificado em uma string no caminho de entrada, e decodificado de volta para bytes no caminho de saída. Variáveis de ambiente são o exemplo mais visível, porque só podem guardar texto:

using System;
using System.Text;
string? encoded = Environment.GetEnvironmentVariable("API_KEY_B64");
if (encoded == null)
{
  throw new InvalidOperationException("Set the API_KEY_B64 environment variable first.");
}
byte[] keyBytes = Convert.FromBase64String(encoded);
string apiKey = Encoding.UTF8.GetString(keyBytes);
Console.WriteLine(apiKey.Length + " characters of API key, ready to use.");

Num banco de dados a mesma ideia geralmente aparece como uma propriedade byte[] que você quer armazenar numa coluna de texto por portabilidade, e o Entity Framework Core tem um mecanismo embutido exatamente para isso: um conversor de valor que roda suas funções de encode e decode de forma transparente em cada leitura e escrita:

using Microsoft.EntityFrameworkCore;
modelBuilder.Entity<Avatar>()
  .Property(a => a.ImageData)
  .HasConversion(
    v => Convert.ToBase64String(v),
    v => Convert.FromBase64String(v));

Aquele único conversor é a integração de banco inteira: ImageData continua sendo byte[] no seu código C#, e o banco de dados vê uma string Base64. Duas cautelas pertencem a esta seção. Primeiro, uma coluna de determinada largura guarda cerca de um terço a menos de dados como texto codificado do que como binário cru, por causa do imposto de 4 caracteres por 3 bytes, então dimensione a coluna para o comprimento codificado se ela tiver largura fixa. Segundo, e esta é a de segurança: Base64 num arquivo de configuração é uma conveniência para manter um valor em uma única linha, não uma proteção para o valor. Qualquer um que possa ler o arquivo de configuração pode decodificar a chave com um comando, que é por isso que segredos de verdade pertencem a um cofre de segredos, e o Base64 ali é apenas o formato de transporte.

Quando o payload é grande

A decodificação Base64 tem uma propriedade agradável que a codificação não tem: a saída é sempre menor que a entrada, mais ou menos três quartos dela. Um payload de texto de 10 megabytes decodifica para cerca de 7,5 megabytes de bytes, então um decode nunca vai estourar sua memória do jeito que um encode pode. A aritmética, se você precisa dimensionar um buffer adiantado, se resume a uma de duas chamadas: Base64.GetMaxDecodedFromUtf8Length para a classe estrita de span, ou a divisão simples, length / 4 * 3 para a API clássica, mais uma margem para espaços em branco se a entrada estiver quebrada. (O helper retorna o comprimento máximo possível decodificado: o comprimento real é igual a ele apenas quando o último grupo não tem padding, e fica um ou dois bytes menor quando termina em um ou dois caracteres de padding.)

Quando o payload é genuinamente grande, porém, o movimento certo não é um buffer maior - é nenhum buffer: pule a string por completo e deixe o FromBase64Transform fazer o decode em streaming da fonte para o alvo, como mostrado na seção de streams. A única regra a respeitar é o alinhamento do grupo-de-quatro: um stream Base64 só pode ser cortado em múltiplos de quatro caracteres (depois de contabilizar os espaços em branco), então se algum dia você for alimentar a transform à mão, leia em pedaços que sejam múltiplos de quatro e deixe o TransformFinalBlock drenar o resto. Para qualquer coisa abaixo de centenas de megabytes, o decode one-shot é rápido o suficiente para que isso seja uma otimização, não uma necessidade, mas a forma em streaming também é a que se comporta bem sob limites de memória, que são exatamente os ambientes onde payloads grandes gostam de morar.

Um decodificador no seu terminal

Existe um momento satisfatório, em toda linguagem, em que um programa de console de 15 linhas vira uma ferramenta de linha de comando, e o decodificador Base64 do C# é uma boa escolha para isso, porque ler da entrada padrão o torna um drop-in para pipes do shell. Aqui está a ferramenta inteira: ela lê o payload Base64 do pipe (ou de um argumento), decodifica e grava os bytes crus num arquivo:

using System;
using System.IO;
using System.Text;
string input = args.Length > 0 ? File.ReadAllText(args[0]) : Console.In.ReadToEnd();
byte[] bytes = Convert.FromBase64String(input.Trim());
File.WriteAllBytes("output.bin", bytes);
Console.Error.WriteLine("Wrote " + bytes.Length + " bytes to output.bin.");

Compile uma vez, e ela fica ao lado da utilidade base64 do próprio shell para os dias em que você quer especificamente o decodificador do runtime .NET: passe um arquivo por ela, encadene com outras ferramentas, e as regras estritas de validação do C# (tolerante a espaços em branco, estrito no alfabeto, estrito no padding) viram parte do seu pipeline. O Trim() está fazendo trabalho discreto ali, pegando a quebra de linha final que editores de texto adoram adicionar, embora, sendo justo, o decodificador teria ignorado mesmo assim. Para os payloads URL-safe que aparecem cada vez mais em logs de API, o mesmo esqueleto com o decode Base64Url da seção URL-safe é a mudança inteira.

Velocidade: o que esperar

Base64 no .NET moderno é rápido, e tem ficado mais rápido. As implementações do runtime, tanto dos métodos Convert quanto das classes System.Buffers.Text, são otimizadas com instruções vetoriais SIMD onde o hardware suporta, e processam muitos caracteres por ciclo. Na prática isso significa que payloads de vários megabytes decodificam em milissegundos de um dígito a dois dígitos baixos numa máquina desktop comum, o que é rápido o suficiente para que decodificar Base64 seja efetivamente grátis em qualquer aplicação que você for escrever. O conselho prático de desempenho é portanto sobre a forma do seu código, não sobre o decodificador em si. Prefira os métodos Try ou os métodos de span que retornam status em caminhos quentes, onde entrada malformada é possível e exceções seriam caras. Reutilize buffers com as APIs in-place e de span quando estiver decodificando milhares de payloads pequenos num loop, em vez de alocar um array novo por chamada. E nunca decodifique o mesmo payload duas vezes: uma vez é o custo, e um segundo decode de um campo que você já decodificou é desperdício puro que aparece em perfis como um segundo pico misterioso de Base64.

Segurança: o que o Base64 não faz

O fato de segurança mais importante sobre o Base64 é justamente aquele que os iniciantes mais frequentemente perdem: é codificação, não criptografia. Uma string Base64 é legível por qualquer pessoa, com qualquer ferramenta, em uma fração de segundo, e o C# torna a leitura um one-liner, como este artigo inteiro demonstrou. O Base64 não tem chave, não tem parâmetro de algoritmo e não tem vulnerabilidade para explorar, porque nunca tentou esconder nada: é um formato de transporte, uma forma de fazer o binário sobreviver em canais só de texto. Trate-o de acordo. Nunca coloque uma senha, um token ou um segredo num arquivo de configuração "protegido" por Base64, porque a proteção é de exatamente uma chamada de Convert.FromBase64String de profundidade. Se o valor precisa ser secreto, ele precisa de proteção de verdade (um gerenciador de segredos, um armazenamento criptografado, no mínimo um controle de acesso do sistema operacional), e o Base64 é apenas a forma que ele veste enquanto viaja.

A segunda nota de segurança é sobre o seu próprio caminho de decode. Cada payload que você decodifica é entrada não confiável até que se prove o contrário, e os dois modos de falha para os quais projetar são o barulhento (entrada inválida, para o qual a API clássica responde com uma FormatException que você deve capturar e transformar num 400, não num 500) e o silencioso (Base64 válido que decodifica para bytes que não são o que você esperava: não é UTF-8, não é o tipo de arquivo que você pediu, ou é mais longo do que o seu orçamento). Valide antes de confiar: verifique o comprimento com IsValid ou a família Try antes de alocar, confira os bytes decodificados contra uma assinatura esperada (o mágico do PNG, o cabeçalho PKCS) antes de entregá-los a um parser de imagem ou certificado, e dimensione os buffers a partir do comprimento codificado antes de decodificar, não depois. O Base64 vai decodificar qualquer coisa que esteja bem formada; decidir o que bem formada significa para a sua aplicação é com você.

Armadilhas que valem a pena conhecer antes que mordam

Essas são as armadilhas específicas do C# que continuam aparecendo em código de verdade, e todas elas têm uma causa concreta em como o framework funciona:

  • Binário através de string. Uma string do C# é uma sequência de unidades de código UTF-16, e o Base64 decodificado não é. No momento em que você enfiar bytes decodificados numa variável string (um Console.WriteLine de um PNG decodificado, concatenação de string com binário, uma biblioteca JSON que serializa "texto"), algo a jusante vai estragar. Mantenha o binário decodificado em byte[] até chegar a um lugar que realmente queira bytes.
  • A bifurcação do Encoding.Default. Código que lê bytes decodificados com Encoding.Default produz texto diferente no .NET Framework (a code page ANSI do Windows) e no .NET (UTF-8). O mesmo payload, duas saídas diferentes, sem exceção. Fixe a encoding explicitamente.
  • Segmentos de JWT e o decodificador clássico. Se você passar um segmento de JWT cru para o Convert.FromBase64String, ele falha de duas formas ao mesmo tempo: os caracteres -/_ estão fora do alfabeto padrão, e o padding ausente quebra a regra de comprimento. Normalize antes, ou use Base64Url.
  • Espaços em branco que você vê e espaços em branco que você não vê. O decodificador pula espaço, tabulação, quebra de linha e retorno de carro, e não pula nada além disso. Um espaço não separável, um separador de linha Unicode ou um tab vertical no payload (todos eles capazes de sobreviver a copiar-e-colar de algumas páginas web) é uma FormatException, não um encolher de ombros.
  • Uma mensagem de erro para todos os crimes. A FormatException do decodificador clássico não diz qual regra foi quebrada nem onde. Depure verificando o comprimento, depois o alfabeto, depois o padding, nessa ordem, ou troque para TryFromBase64String e IsValid para uma resposta booleana.
  • Substituição silenciosa do UTF-8. Encoding.UTF8.GetString transforma sequências de bytes malformadas em U+FFFD sem reclamar. Se o payload pode não ser UTF-8 válido, use o fallback estrito da seção de charsets, ou você vai estar investigando dados sumidos semanas depois que aconteceu.
  • Cortar o stream no lugar errado. Um stream Base64 só pode ser cortado em múltiplos de quatro caracteres. Se você fizer chunk do decode em streaming em qualquer outra fronteira, o último grupo parcial vai parar no TransformFinalBlock, onde ou ele pertence ou quebra sua contabilidade de alinhamento.
  • Finais de linha do PEM. Arquivos de certificado trazem CRLF. Remova o \r também junto com o \n quando desembalar a armadura manualmente, ou a primeira linha do seu DER "decodificado" é um retorno de carro vestindo as roupas de um byte de dados.
  • Codificação dupla. Se o payload já era Base64 quando chegou até você (uma configuração que fez Base64 de uma string Base64, uma API que codificou a saída de outro codificador), um decode te dá mais Base64, não seus dados. A ida e volta só fecha depois de tantos decodes quantos foram os encodes, e o lado do codificador desse bug é o assunto do artigo de encoding.

Uma breve história do Base64 no C#

A história do Base64 no C# é também a história do crescimento da plataforma .NET, e dura mais do que a maioria das pessoas imagina:

  • .NET Framework 1.1, abril de 2003. Convert.FromBase64String e seus irmãos chegam, trazendo o design que define a API até hoje: estrito quanto ao alfabeto, generoso quanto aos quatro caracteres de espaço em branco, direto quanto aos erros. Por boa parte das duas décadas seguintes, este único método é "o" decodificador Base64 do C#.
  • .NET 2.0, 2005. O enum Base64FormattingOptions entra para a Convert, trazendo quebras de linha estilo MIME para o lado da codificação (e a correspondente tolerância a espaços em branco para o lado da decodificação, onde ela já estava trabalhando em silêncio).
  • .NET Core 2.1, 2018. A era do Span. A Convert ganha os métodos Try e uma codificação baseada em span, e a nova classe System.Buffers.Text.Base64 chega com seu contrato OperationStatus, a decodificação in-place e o IsValid, construída para o mundo de alocação zero da reescrita focada em memória.
  • .NET 5, 2020. Os irmãos em hex (Convert.ToHexString e cia.) saem, o mesmo padrão de design do Base64 aplicado a um alfabeto de 16 símbolos, um sinal de que o padrão de classe de conversão tinha virado o estilo da casa.
  • .NET 6, 2021. X509Certificate2.CreateFromPem faz do PEM uma entrada de primeira classe, e uma classe inteira de código manual de descarte de armadura torna-se opcional em runtimes modernos.
  • .NET 9, novembro de 2024. System.Buffers.Text.Base64Url finalmente chega na caixa, depois de anos de pedidos da comunidade, e o pacote Microsoft.Bcl.Memory o backporta para o .NET Framework 4.6.2 e acima, para as codebases legadas que ainda rodam tudo.
  • .NET 11, em preview na data de escrita. A próxima release, esperada para o fim de 2026, adiciona mais APIs e sobrecargas de conveniência do Base64 aos tipos existentes, continuando a marcha lenta em direção a uma superfície mais ergonômica.

Vale guardar em mente: a codificação em si é muito mais antiga que tudo isso. O primeiro uso padronizado do que hoje chamamos de MIME Base64 foi o protocolo Privacy-Enhanced Mail em 1987 (RFC 989), o MIME padronizou a forma quebrada em linhas de 76 caracteres em 1993, e o RFC 4648 em 2006 deu ao formato sua especificação moderna, consciente do alfabeto, incluindo a variante URL-safe. O C# herdou tudo: cada peculiaridade de quebra de linha e padding que você encontra num formato de e-mail de 30 anos é uma peculiaridade para a qual o decodificador C# foi desenhado para absorver.

Fatos curiosos do C#

  • O menor smoke test. "TWFu" decodifica para Man. Três bytes, sem padding, sem desculpas. É o hello world do debugging de Base64 no C#, e exercita o happy path inteiro em quatro caracteres.
  • Um decodificador com história postal. A tolerância a espaços em branco não é um acidente de implementação - é uma decisão de design herdada do MIME: um corpo de e-mail inteiro quebrado em linhas de 76 caracteres, com todos os seus pares CRLF, é um único argumento válido para o Convert.FromBase64String. O decodificador foi construído para engolir o formato que o e-mail usa há trinta anos.
  • Um erro, três causas. A mensagem clássica de FormatException lista os três modos de falha que ela pode estar relatando (caractere inválido, padding demais, padding fora do lugar), mas não diz qual deles disparou. É a única mensagem de erro na superfície da API que funciona como uma questão de múltipla escolha.
  • Um namespace que mente um pouco. System.Buffers.Text soa como se fosse sobre processamento de texto, mas na verdade é a casa da conversão de binário-para-texto em geral: o Utf8Parser e o Utf8Formatter que fazem parsing de números e datas direto para UTF-8 moram logo ao lado das classes de Base64.
  • Padding é opcional num lado da família. A classe Base64Url decodifica AQIDBA (seis caracteres, sem padding) e AQIDBA== (os mesmos bytes com padding) nos mesmos quatro bytes, enquanto o decodificador clássico aceita apenas a forma com padding. Dois decodificadores, dois contratos, um runtime.
  • Strings que não deveriam existir. Uma string do C# pode legalmente conter bytes NUL, então o Encoding.UTF8.GetString de binário decodificado pode produzir uma "string" cheia de caracteres de controle, e o console, o seu escritor de CSV e metade das bibliotecas JSON do planeta vão lidar com ela cada um de um jeito. O sistema de tipos permite; o ecossistema, em grande parte, não.
  • Um relikto do 1.1 em bom estado. Convert.FromBase64CharArray tem a mesma assinatura de três parâmetros desde abril de 2003, sobrevivendo à revolução dos generics, à revolução do Span e à revolução do URL-safe sem um único overload adicionado. A era dos char-array do C# não acabou; ela só está de descanso.
  • Onze caracteres, oito bytes. Os identificadores de vídeo do YouTube são base64url sem padding: 11 caracteres que decodificam para 8 bytes. Base64Url.GetMaxDecodedLength(11) te conta que são 8, e o decode é um one-liner, o que é uma boa forma de terminar o dia se você é o tipo de pessoa que escreve esse tipo de coisa.

A outra direção

Esse é o lado do decodificador, e é onde mora a maior parte da dor, porque decodificar é onde você encontra os dados das outras pessoas: as escolhas de padding delas, as quebras de linha, os alfabetos, os tokens. A direção oposta, pegar os seus próprios bytes e empacotá-los em Base64, é um problema mais calmo, com o seu próprio conjunto de decisões e o seu próprio conjunto de armadilhas. A codificação Base64 em C#, da questão dos 76 caracteres aos tokens URL-safe, é coberta em profundidade no artigo companheiro linkado abaixo, e é uma leitura curta e satisfatória uma vez que você sabe o que procurar.

Última atualização: 2026-09-08

Artigo relacionado: Codificação Base64 em C# (CSharp): um guia completo