En cada proyecto .NET en el que he trabajado, tarde o temprano aparece la necesidad de decodificar Base64 — ya sea extrayendo cadenas de conexión de secretos de Kubernetes, leyendo payloads binarios de webhooks o inspeccionando tokens JWT durante una sesión de depuración. Para decodificar Base64 en C#, el método principal es Convert.FromBase64String(), que retorna un byte[] que luego pasas a Encoding.UTF8.GetString() para obtener texto legible. Para una verificación rápida sin escribir código, el decodificador Base64 de ToolDeck lo resuelve al instante en el navegador. Esta guía cubre Convert.FromBase64String(), el TryFromBase64String() basado en spans para .NET 5+, la API de alto rendimiento System.Buffers.Text.Base64, la extracción de payloads JWT, la decodificación de archivos y respuestas de API, el streaming con CryptoStream y los cuatro errores que más frecuentemente cometen los desarrolladores de C#.
- ✓Convert.FromBase64String(s) + Encoding.UTF8.GetString(bytes) es el pipeline estándar de dos pasos — funciona en todas las versiones de .NET.
- ✓Convert.TryFromBase64String() evita excepciones ante entradas no válidas y escribe en un Span<byte> — ideal para rutas críticas en .NET 5+.
- ✓System.Buffers.Text.Base64.DecodeFromUtf8() ofrece decodificación sin asignaciones para buffers de bytes UTF-8 en servicios de alto rendimiento.
- ✓Los tokens JWT usan Base64url (- y _ en lugar de + y /) — debes normalizar la entrada antes de llamar a Convert.FromBase64String().
- ✓CryptoStream con FromBase64Transform gestiona la decodificación en streaming de archivos grandes sin cargar todo en memoria.
¿Qué es la decodificación Base64?
La codificación Base64 convierte datos binarios en un alfabeto ASCII de 64 caracteres para que puedan sobrevivir al transporte por canales de solo texto — campos JSON, cabeceras HTTP, cuerpos de correo electrónico, atributos XML. Cada 3 bytes de entrada se convierten en 4 caracteres Base64, por eso la salida Base64 siempre es aproximadamente un 33% más grande que el original. La decodificación invierte esta transformación. El relleno = al final indica al decodificador cuántos bytes eliminar del último grupo. Un solo = significa que el último bloque tenía 2 bytes; == significa que tenía 1 byte. Base64 no es cifrado — cualquiera puede revertirlo. Su propósito es el transporte seguro a través de canales que alteran los datos binarios sin formato, no la confidencialidad.
cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ==
redis://cache-prod.internal:6379/session-store
Convert.FromBase64String() — El método de decodificación estándar
El método Convert.FromBase64String() existe en .NET desde los tiempos del Framework 1.1. Sin paquetes NuGet, sin importaciones adicionales más allá de System — solo llámalo y obtienes un byte[]. El pipeline de dos pasos para decodificar Base64 a un string en C# es siempre el mismo: Convert.FromBase64String() para obtener los bytes, luego Encoding.UTF8.GetString() para interpretar esos bytes como texto. El detalle es que el método retorna bytes sin procesar, no un string. Tienes que elegir el Encoding correcto para convertir esos bytes de vuelta a texto, y la elección importa más de lo que la mayoría espera. Una codificación incorrecta produce silenciosamente mojibake — caracteres ilegibles sin ninguna excepción que te avise.
Ejemplo mínimo funcional
using System; using System.Text; // Cadena de conexión almacenada como Base64 en un secreto de Kubernetes string encoded = "cmVkaXM6Ly9jYWNoZS1wcm9kLmludGVybmFsOjYzNzkvc2Vzc2lvbi1zdG9yZQ=="; byte[] decodedBytes = Convert.FromBase64String(encoded); string connectionString = Encoding.UTF8.GetString(decodedBytes); Console.WriteLine(connectionString); // redis://cache-prod.internal:6379/session-store
Usa siempre Encoding.UTF8 salvo que tengas una razón específica para no hacerlo. El runtime de .NET representa los strings internamente como UTF-16, pero la mayoría de los datos que cruzan límites del sistema (respuestas de API, archivos de configuración, secretos) están codificados en UTF-8. Usar Encoding.ASCII en datos que contienen caracteres multibyte los reemplaza silenciosamente por ? — sin excepción, solo salida corrupta.
Verificación de ida y vuelta
using System; using System.Text; string original = "postgres://db-admin:Kx8!mQ@db-prod.us-east-1.internal:5432/orders"; // Codificar string encoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(original)); Console.WriteLine(encoded); // cG9zdGdyZXM6Ly9kYi1hZG1pbjpLeDghbVFAZGItcHJvZC51cy1lYXN0LTEuaW50ZXJuYWw6NTQzMi9vcmRlcnM= // Decodificar byte[] decoded = Convert.FromBase64String(encoded); string recovered = Encoding.UTF8.GetString(decoded); Console.WriteLine(recovered == original); // True
Elegir el Encoding correcto
El Encoding que pasas a GetString() debe coincidir con el que se usó al codificar los datos originalmente. Si eliges el incorrecto obtienes caracteres basura sin ninguna excepción — el decodificador produce felizmente sinsentidos. Aquí está el resumen práctico:
Encoding.UTF8— valor predeterminado seguro. Maneja ASCII y todo Unicode. Úsalo salvo que sepas lo contrario.Encoding.ASCII— solo para datos ASCII puro de 7 bits. Los caracteres multibyte se convierten en?.Encoding.Unicode— esto es UTF-16LE en .NET. Algunos strings internos de Windows y exportaciones de PowerShell usan esto.Encoding.Latin1— codificación legada del Europa occidental. Aparece en servicios SOAP antiguos e integraciones con mainframe.
using System;
using System.Text;
// Los mismos bytes, distintas codificaciones — resultados distintos
byte[] decoded = Convert.FromBase64String("w7bDvMOk");
Console.WriteLine(Encoding.UTF8.GetString(decoded)); // öüä (correcto)
Console.WriteLine(Encoding.ASCII.GetString(decoded)); // ?????? (perdido)
Console.WriteLine(Encoding.Latin1.GetString(decoded)); // öüä (mojibake)Un helper de decodificación reutilizable con manejo de errores
Como Convert.FromBase64String() lanza excepciones ante entradas incorrectas y la eliminación de espacios en blanco ocurre constantemente, mantengo un pequeño helper en la mayoría de los proyectos:
using System;
using System.Text;
static class Base64Helper
{
public static string? DecodeToString(string encoded)
{
if (string.IsNullOrWhiteSpace(encoded))
return null;
// Eliminar espacios en blanco que el decodificador de .NET rechaza
string cleaned = encoded
.Replace("
", "")
.Replace("
", "")
.Replace(" ", "")
.Trim();
try
{
byte[] bytes = Convert.FromBase64String(cleaned);
return Encoding.UTF8.GetString(bytes);
}
catch (FormatException)
{
return null; // o lanzar una excepción específica del dominio
}
}
}
// Uso
string? decoded = Base64Helper.DecodeToString(" cmVkaXM6Ly9jYWNoZQ== \n");
Console.WriteLine(decoded); // redis://cacheConvert.FromBase64String() lanza FormatException si la entrada contiene caracteres fuera del alfabeto Base64 — incluyendo espacios, saltos de línea y caracteres URL-safe como - y _. El helper anterior gestiona los espacios en blanco automáticamente.Decodificar Base64 en tipos no estándar
El decodificador siempre te da un byte[]. Lo que haces con esos bytes depende de los datos originales. A veces es un GUID almacenado como 16 bytes sin formato, a veces es un mensaje protobuf serializado, a veces es una marca de tiempo en formato binario. Estas son las conversiones a las que recurro con más frecuencia.
Base64 a GUID
using System; // Algunas APIs envían GUIDs como Base64 de 22 caracteres en lugar de strings hex de 36 caracteres string compactGuid = "C0HqetxMckKlZw4CssPUeQ=="; byte[] guidBytes = Convert.FromBase64String(compactGuid); Guid recovered = new Guid(guidBytes); Console.WriteLine(recovered); // 7aea41c0-4cdc-4272-a567-0e02b2c3d479
Base64 a JSON deserializado con System.Text.Json
using System;
using System.Text;
using System.Text.Json;
// Payload JSON codificado en Base64 de una cola de mensajes
string encoded = "eyJzZXJ2aWNlIjoicGF5bWVudC1nYXRld2F5IiwicmVnaW9uIjoiZXUtd2VzdC0xIiwicmVwbGljYXMiOjR9";
byte[] jsonBytes = Convert.FromBase64String(encoded);
string json = Encoding.UTF8.GetString(jsonBytes);
Console.WriteLine(json);
// {"service":"payment-gateway","region":"eu-west-1","replicas":4}
// Deserializar en un record
var deployEvent = JsonSerializer.Deserialize<DeployEvent>(jsonBytes);
Console.WriteLine($"{deployEvent!.Service} in {deployEvent.Region}");
// payment-gateway in eu-west-1
record DeployEvent(string Service, string Region, int Replicas);Base64 a string hexadecimal
using System; // Hash SHA-256 almacenado como Base64 string hashBase64 = "n4bQgYhMfWWaL+qgxVrQFaO/TxsrC4Is0V1sFbDwCgg="; byte[] hashBytes = Convert.FromBase64String(hashBase64); string hex = Convert.ToHexString(hashBytes); Console.WriteLine(hex); // 9F86D081884C7D659A2FEAA0C55AD015A3BF4F1B2B0B822CD15D6C15B0F00A08
BinaryFormatter. Tiene vulnerabilidades conocidas de ejecución remota de código y está obsoleto en .NET 8+. Si el contenido codificado proviene de una fuente externa, parsea en JSON o protobuf en lugar de usar la serialización binaria de .NET.Referencia de métodos de decodificación Base64
.NET ofrece múltiples métodos de decodificación en dos namespaces. La clase Convert gestiona la decodificación de propósito general, mientras que System.Buffers.Text.Base64 está orientada a escenarios de alto rendimiento donde ya trabajas con buffers de bytes UTF-8 sin procesar.
Convert.TryFromBase64String() — Decodificación sin excepciones
El patrón Try es estándar en .NET para operaciones que pueden fallar con entrada de usuario. Convert.TryFromBase64String(), disponible desde .NET 5, retorna un bool en lugar de lanzar FormatException. Escribe los bytes decodificados en un Span<byte> proporcionado por el llamador, lo que significa que puedes usar memoria asignada en el stack para payloads pequeños y evitar el heap por completo.
using System;
using System.Text;
string userInput = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=";
// Asignar en el stack para payloads pequeños (< 1 KB es una regla práctica segura)
Span<byte> buffer = stackalloc byte[256];
if (Convert.TryFromBase64String(userInput, buffer, out int bytesWritten))
{
string result = Encoding.UTF8.GetString(buffer[..bytesWritten]);
Console.WriteLine(result);
// {"host":"10.0.1.50","port":8443}
}
else
{
Console.WriteLine("Invalid Base64 input — skipping");
}Este enfoque brilla en middleware de solicitudes o pipelines de validación donde las entradas incorrectas son esperadas, no excepcionales. Lanzar y capturar una FormatException en cada solicitud malformada añade una sobrecarga medible a escala — TryFromBase64String() lo evita por completo.
Validar entrada Base64 sin decodificar
Un patrón habitual en controladores de API: verificar si la entrada es Base64 válida antes de pasarla al siguiente paso. Puedes usar TryFromBase64String() como validador asignando un buffer descartable:
using System;
static bool IsValidBase64(string input)
{
// Calcular el tamaño máximo decodificado
Span<byte> buffer = stackalloc byte[((input.Length + 3) / 4) * 3];
return Convert.TryFromBase64String(input, buffer, out _);
}
// Uso en un controlador de API
Console.WriteLine(IsValidBase64("eyJob3N0IjoiMTAuMC4xLjUwIn0=")); // True
Console.WriteLine(IsValidBase64("not!!valid!!base64")); // False
Console.WriteLine(IsValidBase64("")); // True (vacío es válido)TryFromBase64String() retorna false incluso cuando la entrada es válida. Calcula el tamaño requerido como (inputLength / 4) * 3 para estar seguro.Decodificar Base64 desde un archivo y una respuesta de API
Leer un archivo codificado en Base64 desde disco
Los certificados, blobs cifrados y archivos de exportación de datos a veces se distribuyen como texto Base64. El patrón habitual: leer el archivo como string, eliminar cualquier espacio en blanco o salto de línea que cause FormatException, decodificar a bytes y escribir la salida binaria. Presta atención al manejo de errores — los errores de E/S de archivos y los errores de formato Base64 deben capturarse por separado.
using System;
using System.IO;
string inputPath = "tls-cert.pem.b64";
string outputPath = "tls-cert.pem";
try
{
string encoded = File.ReadAllText(inputPath).Trim();
// Eliminar saltos de línea — el decodificador de .NET los rechaza
encoded = encoded.Replace("
", "").Replace("
", "");
byte[] decoded = Convert.FromBase64String(encoded);
File.WriteAllBytes(outputPath, decoded);
Console.WriteLine($"Decoded {decoded.Length} bytes -> {outputPath}");
}
catch (IOException ex)
{
Console.Error.WriteLine($"File error: {ex.Message}");
}
catch (FormatException ex)
{
Console.Error.WriteLine($"Invalid Base64: {ex.Message}");
}Decodificar un campo Base64 de una respuesta de API HTTP
Las APIs en la nube (Azure Key Vault, AWS Secrets Manager, GitHub Contents API) devuelven frecuentemente datos binarios como strings Base64 embebidos dentro de JSON. El flujo de trabajo es siempre el mismo: realizar la solicitud HTTP, parsear la respuesta JSON, extraer el campo Base64 y decodificarlo. El ejemplo siguiente usa HttpClient y System.Text.Json — ambos integrados en .NET 6+.
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", "Bearer sk-prod-9f8e7d6c");
try
{
var response = await client.GetAsync("https://api.example.com/secrets/db-password");
response.EnsureSuccessStatusCode();
string body = await response.Content.ReadAsStringAsync();
// La API retorna: {"name":"db-password","value":"cG9zdGdyZXM6eGs5bVAycVI=","version":3}
using var doc = JsonDocument.Parse(body);
string encodedValue = doc.RootElement.GetProperty("value").GetString()!;
byte[] decoded = Convert.FromBase64String(encodedValue);
string secret = Encoding.UTF8.GetString(decoded);
Console.WriteLine($"Secret: {secret}");
// Secret: postgres:xk9mP2qR
}
catch (HttpRequestException ex)
{
Console.Error.WriteLine($"HTTP error: {ex.Message}");
}
catch (FormatException ex)
{
Console.Error.WriteLine($"Base64 decode failed: {ex.Message}");
}catch para errores de red y FormatException. Mezclarlos dificulta saber si la API retornó datos incorrectos o si la solicitud en sí misma falló. En producción, registra el valor Base64 sin procesar (o al menos su longitud y los primeros 20 caracteres) cuando capturas FormatException — facilita enormemente la depuración.Decodificación Base64 desde la línea de comandos
No siempre necesitas un proyecto compilado. La herramienta dotnet-script y PowerShell permiten decodificar Base64 con una sola línea. Para una inspección rápida durante la depuración, son más rápidos que crear una aplicación de consola.
# PowerShell (integrado en Windows, disponible en Linux/macOS)
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0="))
# {"host":"10.0.1.50"}
# Comando base64 nativo de Linux / macOS
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 --decode
# {"host":"10.0.1.50"}
# macOS usa -D en lugar de --decode
echo "eyJob3N0IjoiMTAuMC4xLjUwIn0=" | base64 -D
# dotnet-script (instalar: dotnet tool install -g dotnet-script)
echo 'Console.WriteLine(System.Text.Encoding.UTF8.GetString(Convert.FromBase64String("eyJob3N0IjoiMTAuMC4xLjUwIn0=")));' | dotnet-script eval
# Decodificar e imprimir JSON con formato usando jq
echo "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=" | base64 --decode | jq .Para pegar strings codificados directamente en un navegador, el decodificador Base64 de ToolDeck gestiona tanto las variantes estándar como las URL-safe sin configuración alguna.
Alternativa de alto rendimiento: System.Buffers.Text.Base64
La clase System.Buffers.Text.Base64, disponible desde .NET Core 2.1, opera sobre spans de bytes UTF-8 sin procesar en lugar de strings de .NET. Esto evita por completo la sobrecarga de la conversión de string a bytes — sin asignación de strings intermedios, sin paso de codificación UTF-16. La uso en middleware de ASP.NET Core donde los datos entrantes ya son un ReadOnlySpan<byte> del cuerpo de la solicitud. Crear un string solo para pasarlo a Convert.FromBase64String() duplica las asignaciones sin ningún motivo. En pruebas con BenchmarkDotNet sobre .NET 8, el camino basado en spans es aproximadamente 2-3 veces más rápido para payloads menores de 1 KB y la diferencia aumenta con entradas más grandes porque la presión sobre el GC se mantiene constante.
using System;
using System.Buffers.Text;
using System.Text;
// Simular bytes UTF-8 sin procesar del cuerpo de una solicitud HTTP
byte[] utf8Input = Encoding.UTF8.GetBytes("eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0M30=");
byte[] output = new byte[Base64.GetMaxDecodedFromUtf8Length(utf8Input.Length)];
OperationStatus status = Base64.DecodeFromUtf8(
utf8Input, output, out int bytesConsumed, out int bytesWritten);
if (status == OperationStatus.Done)
{
string result = Encoding.UTF8.GetString(output.AsSpan(0, bytesWritten));
Console.WriteLine(result);
// {"host":"10.0.1.50","port":8443}
Console.WriteLine($"Consumed: {bytesConsumed}, Written: {bytesWritten}");
// Consumed: 44, Written: 31
}
else
{
Console.WriteLine($"Decode failed: {status}");
// Posibles valores: InvalidData, DestinationTooSmall, NeedMoreData
}El tipo de retorno es OperationStatus — un enum con cuatro valores: Done, InvalidData, DestinationTooSmall y NeedMoreData. El último es útil para la decodificación parcial de datos en streaming. Para cero asignaciones absolutas, combínalo con ArrayPool<byte>.Shared.Rent() en lugar de new byte[].
DecodeFromUtf8InPlace — sobreescribir el buffer de entrada
using System;
using System.Buffers.Text;
using System.Text;
// El buffer de entrada se sobreescribe con los bytes decodificados
byte[] data = Encoding.UTF8.GetBytes("c2VydmVyLWNvbmZpZw==");
OperationStatus status = Base64.DecodeFromUtf8InPlace(data, out int bytesWritten);
if (status == OperationStatus.Done)
{
Console.WriteLine(Encoding.UTF8.GetString(data.AsSpan(0, bytesWritten)));
// server-config
}DecodeFromUtf8InPlace modifica el buffer de entrada. No lo uses si necesitas el string Base64 original después — los primeros bytesWritten bytes del array ahora contienen los datos decodificados y el resto es basura.Hay algo a tener en cuenta: System.Buffers.Text.Base64 no gestiona directamente Base64 URL-safe. Si la entrada usa los caracteres - y _ (tokens JWT, por ejemplo), aún necesitas reemplazarlos por + y / antes de llamar a estos métodos. Las APIs basadas en spans son estrictamente el alfabeto estándar RFC 4648. No existe una variante DecodeFromUtf8Url — una brecha sorprendente dado lo común que es Base64url en las APIs modernas.
Salida en terminal con resaltado de sintaxis
La librería Spectre.Console ofrece salida enriquecida en terminal incluyendo resaltado de JSON — útil al crear herramientas CLI que decodifican Base64 y muestran el resultado. Instálala con dotnet add package Spectre.Console.
using System;
using System.Text;
using Spectre.Console;
using Spectre.Console.Json;
string encoded = "eyJob3N0IjoiMTAuMC4xLjUwIiwicG9ydCI6ODQ0MywibWF4Q29ubiI6MTAwfQ==";
byte[] decoded = Convert.FromBase64String(encoded);
string json = Encoding.UTF8.GetString(decoded);
// Imprimir con resaltado de sintaxis en el terminal
AnsiConsole.Write(new JsonText(json));
// Muestra JSON con colores:
// {
// "host": "10.0.1.50",
// "port": 8443,
// "maxConn": 100
// }Esto es especialmente útil para herramientas CLI que obtienen y decodifican configuración de servicios remotos. Decodifica la configuración Base64, deserializa a JSON e imprime la salida resaltada — todo en pocas líneas. Spectre.Console también tiene renderizado de tablas, barras de progreso y vistas de árbol si necesitas mostrar estructuras de datos decodificadas más complejas en el terminal.
Streaming de archivos Base64 grandes con CryptoStream
Cargar un archivo Base64 de 500 MB con File.ReadAllText() y luego llamar a Convert.FromBase64String() asigna aproximadamente 700 MB en el heap: el propio string (UTF-16, es decir, el doble del tamaño del archivo) más el array de bytes decodificado. La combinación CryptoStream + FromBase64Transform decodifica en fragmentos, manteniendo el uso de memoria constante.
using System.IO;
using System.Security.Cryptography;
string inputPath = "database-export.sql.b64";
string outputPath = "database-export.sql";
using var inputStream = new FileStream(inputPath, FileMode.Open, FileAccess.Read);
using var transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using var base64Stream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read);
using var outputStream = new FileStream(outputPath, FileMode.Create, FileAccess.Write);
base64Stream.CopyTo(outputStream);
Console.WriteLine($"Decoded {new FileInfo(outputPath).Length} bytes -> {outputPath}");El flag FromBase64TransformMode.IgnoreWhiteSpaces gestiona el Base64 con saltos de línea (archivos PEM, exportaciones de correo) sin limpieza manual. Sin este flag, los saltos de línea en la entrada causan una FormatException. Es el equivalente más cercano en .NET al getMimeDecoder() de Java — omite silenciosamente los caracteres de espacio en blanco durante la decodificación.
El nombre CryptoStream es engañoso — no hay nada criptográfico en Base64. Microsoft colocó FromBase64Transform en el namespace System.Security.Cryptography porque implementa ICryptoTransform, la misma interfaz que usan AES y otros cifrados. El stream en sí simplemente canaliza datos a través de cualquier transformación en fragmentos. Piénsalo como un pipeline de transformación de streaming de propósito general que casualmente vive en el namespace equivocado.
Streaming asíncrono para escenarios de ASP.NET Core
using System.IO;
using System.Security.Cryptography;
async Task DecodeStreamAsync(Stream inputStream, string outputPath)
{
using var transform = new FromBase64Transform(FromBase64TransformMode.IgnoreWhiteSpaces);
using var base64Stream = new CryptoStream(inputStream, transform, CryptoStreamMode.Read);
await using var outputStream = new FileStream(
outputPath, FileMode.Create, FileAccess.Write, FileShare.None,
bufferSize: 81920, useAsync: true);
await base64Stream.CopyToAsync(outputStream);
}
// Uso en un endpoint:
// await DecodeStreamAsync(Request.Body, "uploaded-file.bin");CopyToAsync en lugar de CopyTo en los handlers de ASP.NET Core. Kestrel bloquea por defecto las E/S síncronas en el hilo de la solicitud y lanza una InvalidOperationException.Cómo decodificar el payload de un token JWT Base64 en C#
Un JWT tiene tres segmentos codificados en Base64url separados por puntos. El segmento del medio es el payload. Puedes decodificarlo sin necesidad de una librería JWT — divide por ., normaliza los caracteres Base64url, corrige el padding y llama a Convert.FromBase64String(). Esto confunde a casi todos la primera vez porque JWT usa - y _ en lugar de + y /, y elimina el padding =.
using System;
using System.Text;
static string DecodeJwtPayload(string token)
{
string[] parts = token.Split('.');
if (parts.Length != 3)
throw new ArgumentException($"Invalid JWT: expected 3 segments, got {parts.Length}");
// Tomar el payload (segundo segmento)
string payload = parts[1];
// Reemplazar caracteres URL-safe con Base64 estándar
payload = payload.Replace('-', '+').Replace('_', '/');
// Rellenar hasta un múltiplo de 4
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
return Encoding.UTF8.GetString(bytes);
}
// Probar con un token de forma real
string token = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9"
+ ".eyJzdWIiOiJ1c3ItNjcyIiwiaXNzIjoiYXV0aC5leGFtcGxlLmNvbSIsImV4cCI6MTc0MTk1NjgwMCwicm9sZXMiOlsiYWRtaW4iLCJiaWxsaW5nIl19"
+ ".SIGNATURE_PLACEHOLDER";
Console.WriteLine(DecodeJwtPayload(token));
// {"sub":"usr-672","iss":"auth.example.com","exp":1741956800,"roles":["admin","billing"]}Nota rápida: esto solo lee el payload. No verifica la firma. Para validación de autenticación en producción, usa una librería adecuada como Microsoft.IdentityModel.JsonWebTokens. Pero para depuración, logging y aserciones de pruebas, este enfoque manual es todo lo que necesitas.
Parsear el payload JWT decodificado en un objeto tipado
Una vez tienes el string JSON, deserialízalo con System.Text.Json para acceder a los claims individuales sin manipulación de strings:
using System;
using System.Text;
using System.Text.Json;
using System.Text.Json.Serialization;
record JwtPayload(
[property: JsonPropertyName("sub")] string Subject,
[property: JsonPropertyName("iss")] string Issuer,
[property: JsonPropertyName("exp")] long Expiration,
[property: JsonPropertyName("roles")] string[] Roles
);
// Después de DecodeJwtPayload() del ejemplo anterior
string json = DecodeJwtPayload(token);
var claims = JsonSerializer.Deserialize<JwtPayload>(json)!;
Console.WriteLine($"User: {claims.Subject}");
Console.WriteLine($"Issuer: {claims.Issuer}");
Console.WriteLine($"Expires: {DateTimeOffset.FromUnixTimeSeconds(claims.Expiration):u}");
Console.WriteLine($"Roles: {string.Join(", ", claims.Roles)}");
// User: usr-672
// Issuer: auth.example.com
// Expires: 2025-03-14 12:00:00Z
// Roles: admin, billingErrores comunes
He cometido cada uno de estos errores en servicios C# en producción. Los dos primeros son responsables de la mayoría de los bugs relacionados con Base64 que veo en las revisiones de código. Cada error parece obvio de forma aislada, pero es fácil pasarlo por alto cuando estás inmerso en una funcionalidad más grande y la decodificación Base64 es solo un paso del pipeline.
Problema: Los strings Base64 leídos de archivos de configuración, variables de entorno o entrada del usuario suelen tener saltos de línea al final. Convert.FromBase64String() rechaza cualquier carácter fuera del alfabeto Base64, incluyendo \r\n.
Solución: Llama a .Trim() o .Replace() para eliminar los espacios en blanco antes de decodificar.
// La variable de entorno tiene un salto de línea al final
string encoded = Environment.GetEnvironmentVariable("DB_PASS_B64")!;
byte[] decoded = Convert.FromBase64String(encoded);
// FormatException: The input is not a valid Base-64 stringstring encoded = Environment.GetEnvironmentVariable("DB_PASS_B64")!;
string cleaned = encoded.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);
Console.WriteLine(Encoding.UTF8.GetString(decoded));Problema: Los tokens JWT y algunos payloads de API usan - y _ (alfabeto URL-safe). El decodificador estándar solo acepta + y / — lanza FormatException al primer carácter -.
Solución: Reemplaza - por + y _ por / antes de decodificar. También corrige el padding.
// Payload JWT — usa Base64 URL-safe string payload = "eyJzdWIiOiJ1c3ItNjcyIn0"; byte[] decoded = Convert.FromBase64String(payload); // FormatException
string payload = "eyJzdWIiOiJ1c3ItNjcyIn0";
payload = payload.Replace('-', '+').Replace('_', '/');
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] decoded = Convert.FromBase64String(payload);
Console.WriteLine(Encoding.UTF8.GetString(decoded));
// {"sub":"usr-672"}Problema: Llamar a Encoding.UTF8.GetString() sobre bytes de imagen o protobuf decodificados produce basura. Peor aún, convertir ese string de vuelta a bytes corrompe silenciosamente los datos porque las secuencias UTF-8 inválidas son reemplazadas.
Solución: Mantén los datos binarios como byte[] a lo largo de todo el pipeline. Solo llama a GetString() cuando sepas que el contenido es texto.
byte[] decoded = Convert.FromBase64String(pngBase64);
string imageStr = Encoding.UTF8.GetString(decoded); // corrompe el binario
File.WriteAllText("image.png", imageStr); // archivo rotobyte[] decoded = Convert.FromBase64String(pngBase64);
// Escribir los bytes directamente — sin conversión a string
File.WriteAllBytes("image.png", decoded);Problema: Algunas APIs antiguas de .NET Framework usan por defecto la página de códigos ANSI del sistema, que varía entre máquinas. Un servidor Windows con la página de códigos 1252 y un contenedor Linux con UTF-8 producen strings distintos a partir de los mismos bytes.
Solución: Especifica siempre Encoding.UTF8 de forma explícita. Nunca dependas del predeterminado de la plataforma.
byte[] decoded = Convert.FromBase64String(encoded); // Encoding.Default varía entre plataformas string result = Encoding.Default.GetString(decoded);
byte[] decoded = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(decoded); // consistente en Windows, Linux, macOS
Comparación de métodos
.NET ofrece más métodos de decodificación Base64 de los que la mayoría de desarrolladores conoce. La tabla siguiente cubre todas las opciones integradas más las dos alternativas de terceros más comunes. La columna «Asignación» importa más en servicios de alto rendimiento — un método que asigna un nuevo byte[] en cada llamada presiona el GC en bucles cerrados.
Para uso cotidiano: Convert.FromBase64String(). Para rutas críticas donde validas entrada de usuario: TryFromBase64String(). Para middleware de ASP.NET Core que opera sobre bytes de la solicitud sin procesar: Base64.DecodeFromUtf8(). Para archivos grandes: CryptoStream + FromBase64Transform. BouncyCastle solo tiene sentido si ya está en tu árbol de dependencias por otras operaciones criptográficas.
Para verificación rápida sin compilar nada, el decodificador Base64 online es más rápido que escribir una aplicación de consola de un solo uso.
Preguntas frecuentes
¿Cómo decodifico un string Base64 a texto en C#?
Llama a Convert.FromBase64String() para obtener un array de bytes y luego pásalo a Encoding.UTF8.GetString(). La codificación debe coincidir con la que se usó al codificar — UTF-8 es el valor predeterminado seguro para casi todos los sistemas modernos. Si la entrada puede contener espacios en blanco o saltos de línea, llama a .Trim() o elimínalos antes de decodificar.
using System; using System.Text; string encoded = "cG9zdGdyZXM6eGs5bVAycVI="; byte[] bytes = Convert.FromBase64String(encoded); string result = Encoding.UTF8.GetString(bytes); Console.WriteLine(result); // postgres:xk9mP2qR
¿Cuál es la diferencia entre Convert.FromBase64String() y Convert.TryFromBase64String()?
FromBase64String() lanza una FormatException cuando la entrada no es válida. TryFromBase64String() retorna un bool y escribe el resultado en un Span<byte> proporcionado por el llamador, lo que lo hace adecuado para rutas críticas donde quieres evitar el costo de las excepciones. TryFromBase64String() requiere .NET 5 o posterior.
using System;
string input = "maybe-not-valid-base64!!";
Span<byte> buffer = stackalloc byte[256];
if (Convert.TryFromBase64String(input, buffer, out int written))
Console.WriteLine($"Decoded {written} bytes");
else
Console.WriteLine("Invalid Base64 input");¿Cómo decodifico el payload de un JWT Base64url en C#?
Divide el token por los puntos, toma el segundo segmento, reemplaza - por + y _ por /, rellena hasta un múltiplo de 4 con = y luego llama a Convert.FromBase64String(). Los tokens JWT usan el alfabeto Base64 seguro para URL, que el decodificador estándar de .NET no maneja directamente.
string token = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ1c3ItNjcyIn0.SIG";
string payload = token.Split('.')[1];
payload = payload.Replace('-', '+').Replace('_', '/');
switch (payload.Length % 4)
{
case 2: payload += "=="; break;
case 3: payload += "="; break;
}
byte[] bytes = Convert.FromBase64String(payload);
Console.WriteLine(Encoding.UTF8.GetString(bytes));
// {"sub":"usr-672"}¿Cómo elijo el Encoding correcto al decodificar Base64 en C#?
Usa Encoding.UTF8 como predeterminado — maneja ASCII y caracteres Unicode multibyte. Usa Encoding.ASCII solo cuando estés seguro de que los datos son ASCII puro de 7 bits. Usa Encoding.Unicode (que es UTF-16LE en .NET) solo cuando los datos originales se codificaron como UTF-16, lo que ocurre a veces con strings internos de Windows y exportaciones de PowerShell.
¿Por qué Convert.FromBase64String() lanza FormatException?
Tres causas comunes: la entrada contiene espacios en blanco o saltos de línea (elimínalos antes de decodificar), la entrada usa caracteres URL-safe como - y _ (reemplázalos por + y / respectivamente), o el padding está ausente o es incorrecto (la longitud total debe ser múltiplo de 4 tras el padding). A diferencia de Java, .NET no tiene un decodificador MIME integrado que tolere espacios en blanco — debes limpiar la entrada tú mismo o usar CryptoStream con FromBase64Transform en modo IgnoreWhiteSpaces.
// Eliminar espacios en blanco
string cleaned = rawInput.Replace("\n", "").Replace("\r", "").Trim();
byte[] decoded = Convert.FromBase64String(cleaned);¿Puedo decodificar datos Base64 grandes en streaming en C#?
Sí. Usa un CryptoStream con FromBase64Transform de System.Security.Cryptography. Esto decodifica en fragmentos a medida que lees, por lo que el uso de memoria se mantiene constante independientemente del tamaño del archivo. Pasa FromBase64TransformMode.IgnoreWhiteSpaces si la entrada contiene saltos de línea. En .NET 6+, también puedes usar el patrón IAsyncEnumerable con Base64.DecodeFromUtf8() para procesamiento manual por fragmentos, aunque CryptoStream es más simple para la decodificación de archivo a archivo.
using System.IO;
using System.Security.Cryptography;
using var input = File.OpenRead("payload.b64");
using var transform = new FromBase64Transform();
using var cryptoStream = new CryptoStream(input, transform, CryptoStreamMode.Read);
using var output = File.Create("payload.bin");
cryptoStream.CopyTo(output);Herramientas relacionadas
- Codificador Base64 — codifica texto o datos binarios a Base64 en el navegador, útil para generar fixtures de prueba para pegar en tus tests unitarios de C#.
- Decodificador JWT — decodifica e inspecciona los tres segmentos JWT de una vez, con inspección del payload campo por campo — más rápido que escribir un helper en C# cuando solo necesitas leer un token.
- Decodificador URL — decodifica strings con percent-encoding, útil cuando las respuestas de API mezclan datos Base64url con parámetros de consulta con percent-encoding.
- JSON Formatter — después de decodificar un payload JWT Base64 o una configuración de API, pega el JSON aquí para darle formato y validar la estructura.