¿Tiene que ocuparse del formato Base64? Entonces esta página es perfecta para Ud. Utilice nuestra práctica herramienta en línea para codificar o decodificar sus datos.

Decodificación Base64 en C# (CSharp): una guía completa

Lo reconoces al instante: un río de letras y dígitos, el ocasional + o /, y quizá un = o dos colgando al final. En algún punto entre una respuesta de API, un adjunto de email, un archivo de configuración y un JWT, alguien metió datos binarios dentro de texto, y ahora te toca a ti abrirlo. Esta es la parte de decodificación del Base64 en C#, y la primera buena noticia es que no necesitas nada más que el framework. El decodificador vive en el espacio de nombres System desde hace más de veinte años, y todos los runtimes .NET modernos lo siguen incluyendo, con más opciones y mejor rendimiento que el original.

Un repaso rápido, porque la página de inicio de este sitio explica el formato a fondo: cuatro caracteres de un alfabeto de 64 símbolos cargan tres bytes de datos, y uno o dos caracteres = al final marcan los bytes sobrantes. Decodificar es hacer ese intercambio al revés, así que el resultado es aproximadamente tres cuartas partes del tamaño de la entrada. Con la forma del problema en la cabeza, abramos algunos paquetes.

La familia de decodificadores: conoce tus opciones

Antes del primer ejemplo, aquí tienes toda la familia de APIs de decodificación a las que puedes recurrir, y la situación para la que cada una está hecha. Todo lo listado aquí es parte del propio runtime de .NET, excepto la clase URL-safe en los frameworks antiguos, que viaja dentro de un pequeño paquete NuGet:

API Disponible desde Para qué sirve
Convert.FromBase64String(string) .NET Framework 1.1 (2003) El clásico. Entra una cadena, sale un byte[] nuevo. Salta los espacios en blanco ordinarios y lanza excepción ante cualquier otra cosa.
Convert.FromBase64CharArray(char[], int, int) .NET Framework 1.1 (2003) La misma decodificación, leyendo de una porción de un buffer de caracteres que ya es tuyo.
Convert.TryFromBase64String, Convert.TryFromBase64Chars .NET Core 2.1 (2018) Booleano en vez de excepciones, escribiendo en un span que tú proporcionas. La guardia amable para entrada no confiable.
System.Buffers.Text.Base64 .NET Core 2.1 (2018) La API span estricta: códigos de estado en vez de excepciones, decodificación in-place y comprobaciones previas con IsValid.
System.Buffers.Text.Base64Url .NET 9 (2024) El alfabeto URL-safe (- y _ en vez de + y /), con o sin padding. En .NET Framework 4.6.2+ y .NET Standard 2.0: el paquete NuGet Microsoft.Bcl.Memory.
FromBase64Transform + CryptoStream .NET Framework 1.1 (2003) Decodificación en streaming: de archivo a archivo, de red a disco, trozo a trozo, sin cargar todo el payload.

Si tu proyecto apunta a una versión de .NET de 2018 o posterior, las cuatro primeras filas ya vienen en la caja. Base64Url necesita .NET 9 o más nuevo, o el paquete Microsoft.Bcl.Memory en cualquier cosa anterior. Y una nota hacia adelante: las librerías de .NET 11, en preview en el momento de escribir esto con una salida general prevista para finales de 2026, añaden más APIs y sobrecargas de conveniencia de Base64 a los tipos existentes, así que la familia sigue creciendo. Nada más en este artículo requiere un paquete.

El caballo de batalla: Convert.FromBase64String

El noventa por ciento de la vida de decodificación en C# es una sola llamada. Pásale una cadena, y te devuelve los bytes exactos que estaban empaquetados dentro:

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

Hay tres detalles que merecen quedarse en la memoria. Primero, el valor devuelto son bytes, no texto: es un byte[], el decodificador está orientado a bytes de principio a fin, y eso es exactamente lo que quieres, porque el payload puede ser una frase, un PNG, un certificado o un hash, y ninguno de ellos debería tratarse como especial. El salto de vuelta de los bytes al texto legible es un paso separado y deliberado a través de Encoding, y ese es el lugar donde viven las decisiones de charset (más sobre eso abajo). Segundo, el decodificador asigna un array nuevo en cada llamada, con el tamaño exacto de la longitud decodificada, así que nunca te entrega un buffer con capacidad sobrante. Tercero, el contrato es pequeño y honesto: una cadena vacía decodifica a un array vacío, una referencia null lanza ArgumentNullException, y cualquier cosa que no sea Base64 válido lanza FormatException. Todo lo demás es una elaboración de esas tres reglas.

Lo que perdona y lo que se niega

Aquí es donde el decodificador de C# muestra su personalidad, y bastante marcada. Es generoso con exactamente una cosa - los espacios en blanco - y despiadado con todo lo demás. El decodificador salta exactamente cuatro caracteres dondequiera que aparezcan en la cadena: el espacio (U+0020), el tabulador (U+0009), el salto de línea (U+000A) y el retorno de carro (U+000D). Esa política es un guiño deliberado al email, donde los payloads Base64 llegan envueltos en líneas de 76 caracteres, y significa que un adjunto envuelto en MIME decodifica con cero preprocesamiento. Cualquier cosa fuera del alfabeto de 64 símbolos, cualquier cosa que rompa las reglas de longitud, o cualquier cosa con padding en el lugar equivocado recibe una excepción. Mira al mismo decodificador en acción con unas pocas entradas distintas:

Entrada Resultado
"TWFu" Decodifica a Man (3 bytes).
"TWF\nu" (un salto de línea en medio) Decodifica a Man. Los espacios en blanco son invisibles para el decodificador.
"TWFu\u00A0" (un espacio no separable al final) FormatException. Solo se saltan los cuatro espacios en blanco de arriba; el NBSP no es uno de ellos.
"TWE" (longitud 3, no es múltiplo de 4) FormatException. La longitud del payload, ignorando espacios en blanco, debe ser un múltiplo de 4.
"TWFu=" (padding extra después de los datos) FormatException. Como máximo dos caracteres de relleno, y solo al final.
"-_88" (alfabeto URL-safe) FormatException. El decodificador estándar solo conoce los 64 caracteres del alfabeto estándar.
null ArgumentNullException: El valor no puede ser nulo. (Parámetro 's')

Un capricho más que merece la pena memorizar: cada delito de formato recibe el mismo único mensaje de error, La entrada no es una cadena Base-64 válida, ya que contiene un carácter que no es base 64, más de dos caracteres de relleno, o un carácter ilegal entre los caracteres de relleno. El mensaje lista las tres causas posibles y no dice cuál has tocado, ni te dice dónde. Si estás depurando un payload que falla, cuenta los caracteres, comprueba el alfabeto y comprueba el padding, en ese orden.

Decodificación sin excepciones: las API Try

El flujo de control basado en excepciones es un patrón legítimo, pero para entradas de alto volumen o no confiables la familia Try es la vecina mejor educada. Se añadió en .NET Core 2.1 y viene en dos sabores: uno que lee de una cadena y otro que lee de un span de caracteres. Ambos escriben en un buffer que tú proporcionas e informan de cuánto lo llenaron:

using System;
using System.Text;
string payload = "TWFu"; // cualquier payload, válido o no
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.");
}

Dos comportamientos hacen que las variantes Try parezcan una especie distinta. Una entrada inválida devuelve false en vez de lanzar, así que un caudal de payloads malformados te cuesta una rama en vez de una excepción. Un matiz: una entrada null no forma parte del contrato - lanza ArgumentNullException - así que la guardia Try cubre los payloads rotos, y un valor que puede faltar sigue necesitando su propia comprobación de nulo antes. El método hermano Convert.TryFromBase64Chars hace el mismo trabajo desde un ReadOnlySpan<char>, lo cual viene de perlas cuando el payload vive en un buffer de caracteres más grande y no quieres cortar primero un subconjunto de cadena. Dimensiona el buffer de salida con generosidad: la longitud decodificada es como máximo tres cuartas partes de la longitud de la entrada (sin espacios en blanco), y el parámetro de salida written te dice exactamente cuánto salió.

Decodificación basada en span con System.Buffers.Text.Base64

Cuando estás contando asignaciones, o cuando quieres que el decodificador describa sus fallos en vez de lanzarlos, la clase System.Buffers.Text.Base64 es la herramienta. Es una clase estática de la biblioteca estándar desde .NET Core 2.1, y trabaja con spans en vez de arrays gestionados. Su método de decodificación devuelve un valor OperationStatus con cuatro estados de ánimo: Done (éxito), DestinationTooSmall (tu buffer era demasiado pequeño), NeedMoreData (la entrada aún no es un múltiplo de 4, sigue leyendo) y InvalidData (esto no es Base64). El último parámetro booleano, isFinalBlock, es lo que distingue esos dos: le dice al decodificador si va a llegar más entrada. Aquí está la forma one-shot, dimensionada con el propio helper de la clase:

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
}

Dos miembros más de esta clase merecen un párrafo. El primero es IsValid, que valida un payload sin decodificarlo. Viene en sabores de span de bytes y de span de caracteres, y una sobrecarga informa de la longitud decodificada junto con el veredicto, así que puedes dimensionar un buffer con una sola comprobación:

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.");
}

El segundo es DecodeFromUtf8InPlace, para la situación donde el texto Base64 ya está en un buffer que es tuyo y no te importa sobrescribirlo. Decodificar encoge los datos, así que el resultado se escribe al frente del mismo buffer y el método informa de lo largo que es:

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, ahora viviendo en los tres primeros bytes del mismo buffer
}

Un comportamiento que conviene guardar en el bolsillo: esta clase también salta los cuatro espacios en blanco ordinarios (espacio, tabulador, salto de línea, retorno de carro), así que un payload con saltos de línea decodifica igual de bien. Es estricto donde importa: un payload cuya longitud sin espacios en blanco no es múltiplo de cuatro es InvalidData cuando es el bloque final, y los caracteres fuera del alfabeto estándar se rechazan sin más. No hay ninguna limpieza silenciosa en toda esta clase.

Base64 seguro para URLs: la clase Base64Url

Existe un segundo alfabeto para los mismos 64 valores, y lo vas a encontrar constantemente en el trabajo web con C#. En el alfabeto estándar, los valores 62 y 63 son + y /, dos caracteres que causan problemas en las URLs: un + en una cadena de consulta se decodifica rutinariamente como espacio, y / y = cada uno necesita codificación por porcentaje. El RFC 4648, sección 5, lo arregla cambiando a - y _, que no tienen ningún significado especial en ningún contexto de URL, y hace opcional el padding final de =. El resultado se llama base64url, y es el alfabeto de los JWT, los tokens de API, los IDs de subida de archivos y un montón de URLs (los identificadores de video de 11 caracteres de YouTube son base64url sin padding).

Desde .NET 9 la biblioteca estándar incluye una clase dedicada para ello: System.Buffers.Text.Base64Url. Es la gemela URL-safe de la clase Base64, con sus propias ayudas de decodificación, validación y longitud:

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

Fíjate en lo que la API clásica no habría hecho con ese ejemplo. Los mismos tres bytes se codifican como +//8 en el alfabeto estándar, y Convert.FromBase64String("+//8") funciona, pero Convert.FromBase64String("-__8") lanza, porque los caracteres URL-safe están fuera de su alfabeto. Y los payloads base64url suelen llegar sin padding, lo que el decodificador clásico también rechaza, porque exige el grupo completo de cuatro. La clase Base64Url maneja nativamente ambas variantes del problema: decodifica TWE (tres caracteres, sin padding) a los dos bytes Ma, y decodifica TWE= igual de bien.

Si tu proyecto corre en un runtime más antiguo, hay dos caminos prácticos. En .NET Framework 4.6.2 y superior, añade el paquete NuGet Microsoft.Bcl.Memory, que Microsoft publica precisamente para retroportar Base64Url (junto con unos pocos tipos modernos más):

dotnet add package Microsoft.Bcl.Memory

O, sin ningún paquete, normaliza el payload antes de pasarlo al decodificador clásico: intercambia los caracteres URL-safe de vuelta a sus gemelos estándar, y completa el padding que falta. Este pequeño helper es el decodificador base64url hecho a mano más común en el código C#, y vale la pena conocerlo porque funciona en todos los runtimes desde .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

La fórmula (4 - length % 4) % 4 es toda la aritmética del padding: añade cero, uno o dos caracteres = para que la longitud caiga en un múltiplo de cuatro, y el módulo exterior impide que una entrada ya rellena gane caracteres extra.

De bytes a palabras: texto, Unicode y charsets

Decodificar te da bytes, y los bytes son una cosa perfectamente neutral. Solo se convierten en "texto" cuando eliges un charset para leerlos, y esa elección es tuya, porque Base64 no lleva ninguna información sobre qué charset usó el autor original. En la práctica eso significa: asume UTF-8 a menos que tengas una razón para no hacerlo, y sé explícito al respecto en el código, porque una llamada explícita a Encoding.UTF8 es la diferencia entre un programa que es correcto por accidente y uno que es correcto por diseño:

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 viaja de ida y vuelta sin perder nada

La trampa sutil es lo que pasa cuando los bytes no son UTF-8 válido, porque el payload era en realidad Latin-1, o binario, o simplemente corrupto. Por defecto, el decodificador UTF-8 de .NET sustituye cada secuencia malformada por el carácter de reemplazo Unicode (U+FFFD) y sigue adelante. Sin excepción, sin aviso: los datos simplemente se van, convertidos en signos de interrogación en tu base de datos. Si necesitas saber cuándo pasa eso, construye la codificación con un fallback estricto, que convierte la sustitución silenciosa en una ruidosa DecoderFallbackException:

using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // los bytes FF FE, no es UTF-8 válido
Encoding strictUtf8 = Encoding.GetEncoding(
  "utf-8",
  new EncoderExceptionFallback(),
  new DecoderExceptionFallback());
string text = strictUtf8.GetString(bytes);
// Lanza DecoderFallbackException, porque FF FE no es una secuencia UTF-8

Para los payloads donde prefieres sobrevivir a fallar, los fallbacks de reemplazo son la opción más amable, y además eliges tú el texto de reemplazo:

using System.Text;
byte[] bytes = Convert.FromBase64String("//4="); // los bytes FF FE, no es 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] en vez de la sustitución silenciosa por U+FFFD

Una lección de historia más, específica de C#: Encoding.Default significa cosas distintas en distintos runtimes. En .NET Framework en Windows es la página de código ANSI del sistema (a menudo Windows-1252), mientras que en .NET (Core) es UTF-8 sin BOM. Un código que hace el viaje de ida y vuelta de un payload a través de Encoding.Default puede por lo tanto producir bytes distintos en una máquina de 2010 y una de 2025, y Base64 codificará con ganas el conjunto que le pases. Si algún día ves una cadena decodificada llena de mojibake con acentos, Encoding.Default es el primer lugar a mirar.

Archivos y payloads binarios

Los archivos son el objetivo de decodificación más directo, porque la pregunta del charset ni existe: los bytes que decodificas son el archivo, byte a byte, ceros y todo. El patrón son dos llamadas y un archivo, y aparece en todo, desde subidas de imágenes hasta herramientas de respaldo:

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.");

Dos notas prácticas. Si el archivo puede contener espacios en blanco o saltos de línea (siendo un archivo de texto, casi seguro que los contiene), el decodificador clásico lo maneja gratis, como viste antes. Y si el payload es grande, no pases por una cadena en absoluto: omite el paso de archivo a cadena y decodifica directamente de la stream, que es la siguiente sección. Para un payload decodificado que es texto y cuyo charset sucede que conoces, el ejemplo de archivo es la solución completa, y el paso de Encoding.UTF8.GetString de la sección de charsets encaja justo entre la decodificación y el uso.

Decodificación desde una stream: FromBase64Transform

Los métodos de Convert están diseñados para payloads que caben en una cadena, y la documentación oficial lo dice sin rodeos: para datos en streaming, usa las clases transform. FromBase64Transform es parte de System.Security.Cryptography desde .NET Framework 1.1 (2003), y se enchufa en CryptoStream, el tubo de propósito general del framework para transformar datos mientras fluyen. Toda la decodificación de archivo a archivo es una configuración de cuatro líneas:

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.");

El constructor recibe un modo, y los dos modos merecen conocerse por su nombre. IgnoreWhiteSpaces (el predeterminado, coherente con la política de espacios en blanco del decodificador clásico) salta los cuatro espacios en blanco ordinarios mientras fluye la stream, que es lo que quieres para payloads envueltos por email o llenos de saltos de línea. DoNotIgnoreWhiteSpaces es estricto: el primer carácter fuera del alfabeto que encuentra lanza FormatException, que es lo que quieres cuando un espacio suelto en el payload debería ser un bug y no un encogimiento de hombros. Por dentro, la transform procesa la entrada en grupos de cuatro caracteres y devuelve los tres bytes que produce cada grupo, con TransformFinalBlock encargándose del final. Raramente llamas a esos métodos tú mismo, porque CryptoStream lo hace por ti, pero el hecho del grupo de cuatro importa: si algún día alimentas la transform a mano, aliméntala en múltiplos de cuatro, o el último grupo parcial se quedará sentado en el bloque final.

JWTs: tres segmentos, un punto

Un JSON Web Token es el payload base64url con más tráfico en el desarrollo web con C#, y su forma es engañosamente simple: tres segmentos separados por puntos. El primero es el encabezado codificado, el segundo el payload codificado (también llamado claims) y el tercero la firma. Cada uno de los dos primeros es base64url de un documento JSON en UTF-8, sin padding, según la especificación JWS. Separar y decodificar son dos líneas 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

En runtimes anteriores a .NET 9, el mismo trabajo pasa por el helper de normalización de la sección URL-safe: intercambia - y _ de vuelta a + y /, rellena el segmento hasta un múltiplo de cuatro y decodifica con Convert.FromBase64String. Ambos enfoques te dan el mismo JSON; elige el que se ajuste a tu framework objetivo.

Un límite que conviene mantener nítido: decodificar un JWT no es verificar un JWT. La decodificación de arriba leerá con gusto los claims de un token con una firma basura, porque la firma es una comprobación criptográfica separada sobre los dos primeros segmentos. Para trabajo de tokens en producción, no analices a mano en absoluto: el paquete System.IdentityModel.Tokens.Jwt (de la familia Microsoft.IdentityModel) maneja análisis, validación y caducidad en uno, y su manejo de base64url es exactamente el alfabeto que describe esta sección. Decodifica a mano para depurar y para utilidades pequeñas; verifica con la librería para todo lo que un usuario pueda alcanzar.

Data URIs e imágenes incrustadas

Existe toda una clase de código C# cuyo trabajo es recibir una URI data:, porque HTML, CSS y un montón de APIs web las usan para incrustar contenido binario en línea. El esquema, estandarizado por el RFC 2397, es data:[mediatype][;base64],payload: todo antes de la primera coma es metadatos (el tipo MIME y la bandera ;base64), todo lo que va después es el payload. Cuando la bandera ;base64 está presente, el payload es una cadena Base64, y separar en la coma es todo el análisis:

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: los bytes de la firma PNG 89 50 4E 47 0D 0A 1A 0A

El prefijo iVBORw0KGgo= del ejemplo es la forma Base64 del número mágico PNG de ocho bytes, y es una huella útil: cualquier data URI de un PNG real empieza así, así que es una comprobación rápida de sentido común cuando analizas HTML no confiable. Dos notas prácticas para desarrolladores de C#. Primera, la clase Uri entiende las data URIs nativamente en .NET: new Uri("data:text/plain;base64,TWFu") se analiza sin problemas e informa Scheme == "data", así que si tu código enruta sobre URIs, las data URIs aparecerán en el pipeline y deberías decidir cómo manejarlas. Segunda, recuerda qué es realmente una data URI: una copia completa del archivo, inflada un tercio, sentada dentro de tu documento. Para un favicon de 4 KB está bien y para un logo de 4 MB duele, así que cuando seas tú quien las genere (el artículo de codificación cubre ese lado), dimensiona la imagen antes de codificarla.

HTTP: autenticación Basic e intercambios de API

Base64 está tejido en HTTP al menos en un lugar al que tocarás en cualquier trabajo de API: el esquema de autenticación Basic. El cliente envía Authorization: Basic seguido de la codificación Base64 de username:password, unidos por dos puntos. En el lado del servidor, decodificar un encabezado entrante es por lo tanto: quitar el prefijo Basic , decodificar y separar en el primer signo de dos puntos:

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

El paso de UTF-8 importa más de lo que parece: el RFC 7617 no fija realmente el charset, deja el valor predeterminado sin definir por compatibilidad con versiones anteriores y solo permite una pista consultiva de UTF-8, pero esa pista es lo que todo servidor moderno espera, así que un nombre de usuario con un carácter acentuado produce una cadena de bytes distinta (y correcta) de la que produce el mismo nombre de usuario leído como Latin-1. El lado de decodificación de la autenticación Basic es el extremo simple de este patrón; en ASP.NET Core normalmente lo encontrarás a través de los manejadores de autenticación y no de encabezados crudos, pero la misma lógica de decodificación es lo que ejecutan por debajo, y es exactamente el tipo de código que necesitas cuando escribes pruebas de integración que falsifican un servidor de API. La operación espejo, construir el encabezado en el lado del cliente, es una línea en el lado de codificación, y tiene un ejemplo completo en el artículo de codificación.

Email: MIME y payloads con saltos de línea

El email es donde Base64 ganó su reputación, y sigue siendo la fuente de muchos de los payloads que reciben los servicios en C#. SMTP era originalmente un protocolo de 7 bits, así que los adjuntos binarios no pueden viajar crudos: la especificación MIME (RFC 2045) los codifica como Base64 con un encabezado Content-Transfer-Encoding: base64, envuelve la salida a 76 caracteres y separa las líneas con pares de retorno de carro y salto de línea. El cuerpo de un adjunto real se parece por lo tanto a una columna de líneas de 76 caracteres, y la buena noticia para C# es que el decodificador clásico ya sabe leerlo: como salta los espacios en blanco en cualquier parte de la cadena, puedes pasarle todo el cuerpo envuelto, saltos de línea y todo, y lo decodifica como si los saltos nunca hubieran estado:

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 los payloads que llegan por una stream en vez de por una cadena, el FromBase64Transform con su modo de ignorar espacios en blanco es la misma historia con ropa de streaming. Y cuando necesitas hacer más que decodificar el cuerpo, cuando necesitas recorrer la estructura MIME, analizar encabezados, manejar secciones multipart anidadas, o extraer cada adjunto de un archivo .eml real, la respuesta del ecosistema en C# es el paquete MimeKit: es la librería MIME estándar de .NET, maneja internamente las codificaciones de transferencia de contenido Base64 y quoted-printable, y es la herramienta a la que acudir en el momento en que "solo decodificar el cuerpo" deja de describir tu problema. La propia clase MailMessage del framework te decodificará adjuntos simples, pero su soporte MIME es deliberadamente modesto por los estándares modernos.

Certificados PEM

PEM es el formato blindado del mundo TLS: un cuerpo Base64 entre los marcadores -----BEGIN CERTIFICATE----- y -----END CERTIFICATE-----, envuelto a 64 caracteres, como especifica el RFC 7468. Los desarrolladores de C# lo encuentran como los archivos de certificado detrás de cada extremo HTTPS, y la historia de decodificación aquí es mejor de lo que esperarías, porque desde .NET 6 el framework analiza PEM por ti, cuerpo Base64 incluido:

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

Sin Base64 manual en ninguna parte: CreateFromPem encuentra los marcadores, desenrolla el cuerpo, lo decodifica y te devuelve un certificado en vivo. (La familia tiene hermanos para claves privadas y para la forma combinada de certificado-más-clave, si tu infraestructura te entrega esas.) Si estás en un runtime más antiguo, o necesitas los bytes DER crudos que hay dentro de la armadura, la versión manual es un quitar-y-decodificar de dos pasos, y vale la pena conocerla porque el mismo patrón funciona para cualquier cosa blindada con 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);
// La longitud del certificado DER dentro de la armadura

Los riesgos en esta esquina son todos de espacios en blanco: los archivos PEM traen finales de línea CRLF desde la mayoría de las herramientas de certificados, así que quita tanto \r como \n antes de decodificar, no solo los saltos de línea. Y no confundas el cuerpo del certificado con el cuerpo de una clave privada, que tiene marcadores distintos y contenidos distintos; ningún decodificador te salvará de ese error.

Configuración, variables de entorno y bases de datos

El tercer hogar del Base64 en aplicaciones C# es el almacenamiento: archivos de configuración, variables de entorno y columnas de base de datos. El patrón es el mismo en todas partes. Un valor binario o secreto se codifica en una cadena de entrada, y se decodifica de vuelta a bytes de salida. Las variables de entorno son el ejemplo más visible, porque solo pueden contener 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.");

En una base de datos la misma idea suele aparecer como una propiedad byte[] que quieres almacenar en una columna de texto por portabilidad, y Entity Framework Core tiene un mecanismo incorporado para exactamente esto: un conversor de valores que ejecuta tus funciones de codificación y decodificación de forma transparente en cada lectura y escritura:

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

Ese único conversor es toda la integración con la base de datos: ImageData sigue siendo un byte[] en tu código C#, y la base de datos ve una cadena Base64. Dos precauciones pertenecen a esta sección. Primera, una columna de un ancho dado guarda un tercio menos de datos como texto codificado que como binario crudo, por el impuesto de 4 caracteres por 3 bytes, así que dimensiona la columna para la longitud codificada si tiene un ancho fijo. Segunda, y esta es la de seguridad: el Base64 en un archivo de configuración es una comodidad para mantener un valor en una sola línea, no una protección para el valor. Cualquiera que pueda leer el archivo de configuración puede decodificar la clave con un solo comando, y por eso los secretos de verdad pertenecen a un almacén de secretos, y el Base64 de ahí es solo el formato de transporte.

Cuando el payload es grande

La decodificación Base64 tiene una propiedad agradable que la codificación no tiene: la salida siempre es más pequeña que la entrada, aproximadamente tres cuartas partes. Un payload de texto de 10 megabytes decodifica a unos 7.5 megabytes, así que una decodificación nunca puede inflar tu memoria como sí puede una codificación. La aritmética, si necesitas dimensionar un buffer por adelantado, se reduce a una de dos llamadas: Base64.GetMaxDecodedFromUtf8Length para la clase span estricta, o la división plana, length / 4 * 3 para la API clásica, más un margen por los espacios en blanco si la entrada está envuelta. (El helper devuelve la longitud decodificada máxima posible: la longitud real le es igual solo cuando el último grupo no tiene padding, y es uno o dos bytes menor cuando termina en uno o dos caracteres de relleno.)

Cuando el payload es realmente grande, sin embargo, el movimiento correcto no es un buffer más grande - es ningún buffer en absoluto: omite la cadena por completo y deja que FromBase64Transform decodifique en streaming de origen a destino, como se mostró en la sección de streams. La única regla a respetar es la alineación de grupo de cuatro: una stream Base64 solo puede cortarse en múltiplos de cuatro caracteres (contados los espacios en blanco), así que si algún día alimentas la transform a mano, lee en trozos que sean múltiplos de cuatro y deja que TransformFinalBlock drene el resto. Para cualquier cosa por debajo de cientos de megabytes, la decodificación one-shot es lo bastante rápida como para que esto sea una optimización y no una necesidad, pero la forma en streaming es también la que se comporta bien bajo límites de memoria, que son exactamente los entornos donde a los payloads grandes les gusta vivir.

Un decodificador en tu terminal

Hay un momento satisfactorio, en cada lenguaje, donde un programa de consola de 15 líneas se convierte en una herramienta de línea de comandos, y el decodificador Base64 de C# es un buen candidato para hacerlo, porque leer de la entrada estándar lo convierte en un reemplazo directo en las tuberías del shell. Aquí está la herramienta completa: lee el payload Base64 de la tubería (o de un argumento), lo decodifica y escribe los bytes crudos en un archivo:

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.");

Compílalo una vez, y queda junto a la propia utilidad base64 del shell para los días en que quieres específicamente el decodificador del runtime .NET: pasa un archivo a través de él, encádenalo con otras herramientas, y las reglas estrictas de validación de C# (tolerantes con los espacios en blanco, estrictas con el alfabeto, estrictas con el padding) se convierten en parte de tu pipeline. El Trim() está haciendo trabajo silencioso allí, atrapando el salto de línea final que los editores de texto aman añadir, aunque para ser justos el decodificador lo habría ignorado de todas formas. Para los payloads URL-safe que cada vez aparecen más en los logs de API, el mismo esqueleto con la decodificación Base64Url de la sección URL-safe es todo el cambio.

Velocidad: qué esperar

El Base64 en .NET moderno es rápido, y ha ido a más. Las implementaciones del runtime tanto de los métodos de Convert como de las clases de System.Buffers.Text están optimizadas con instrucciones vectoriales SIMD donde el hardware las soporta, y procesan muchos caracteres por ciclo. En la práctica eso significa que payloads de varios megabytes decodifican en un rango de 1 a 19 milisegundos en una máquina de escritorio corriente, lo bastante rápido como para que decodificar Base64 sea efectivamente gratis en cualquier aplicación que vayas a escribir. El consejo práctico de rendimiento es por lo tanto sobre la forma de tu código, no sobre el decodificador en sí. Prefiere los métodos Try o los métodos span que devuelven estado en las rutas calientes, donde la entrada malformada es posible y las excepciones serían caras. Reutiliza buffers con las APIs in-place y span cuando decodifiques miles de payloads pequeños en un bucle, en vez de asignar un array nuevo por llamada. Y no decodifiques nunca el mismo payload dos veces: una vez es el coste, y una segunda decodificación de un campo que ya decodificaste es un derroche puro que aparece en los perfiles como un misterioso segundo pico de Base64.

Seguridad: lo que Base64 no hace

El hecho de seguridad más importante sobre Base64 es el que los principiantes más a menudo se pierden: es codificación, no cifrado. Una cadena Base64 es legible para cualquiera, con cualquier herramienta, en una fracción de segundo, y en C# leerla es cuestión de una línea, como todo este artículo ha demostrado. Base64 no tiene clave, no tiene parámetro de algoritmo y no tiene debilidad que explotar, porque nunca intentó esconder nada: es un formato de transporte, una manera de hacer que lo binario sobreviva en canales solo de texto. Trátalo en consecuencia. Nunca metas una contraseña, un token o un secreto en un archivo de configuración "protegido" por Base64, porque la protección queda a una sola llamada de Convert.FromBase64String. Si el valor debe ser secreto, necesita protección real (un gestor de secretos, un almacén cifrado, como mínimo un control de acceso del sistema operativo), y el Base64 es solo la forma que viste mientras viaja.

La segunda nota de seguridad es sobre tu propia ruta de decodificación. Cada payload que decodificas es una entrada no confiable hasta que se demuestre lo contrario, y los dos modos de fallo para los que diseñar son el ruidoso (entrada inválida, a lo que la API clásica responde con un FormatException que deberías atrapar y convertir en un 400, no en un 500) y el silencioso (Base64 válido que decodifica a bytes que no son lo que esperabas: no UTF-8, no el tipo de archivo que pediste, o más largo de lo que presupuestaste). Valida antes de confiar: comprueba la longitud con IsValid o la familia Try antes de asignar, comprueba los bytes decodificados contra una firma esperada (el número mágico PNG, la cabecera PKCS) antes de entregarlos a un analizador de imágenes o certificados, y dimensiona tus buffers desde la longitud codificada antes de decodificar, no después. Base64 decodificará cualquier cosa bien formada; decidir qué significa bien formada para tu aplicación es tu trabajo.

Piedras que vale la pena conocer antes de que te muerdan

Estas son las trampas específicas de C# que siguen apareciendo en código real, y cada una tiene una causa concreta en cómo funciona el framework:

  • Binario a través de una cadena. Una string de C# es una secuencia de unidades de código UTF-16, y el Base64 decodificado no lo es. En el momento en que metes bytes decodificados en una variable de cadena (un Console.WriteLine de un PNG decodificado, una concatenación de cadena con binario, una librería JSON que serializa "texto"), algo aguas abajo lo va a destrozar. Mantén el binario decodificado en byte[] hasta que llegue a un lugar que realmente quiera bytes.
  • La grieta de Encoding.Default. Un código que lee bytes decodificados con Encoding.Default produce texto distinto en .NET Framework (la página de código ANSI de Windows) y en .NET (UTF-8). El mismo payload, dos salidas distintas, sin excepción. Fija tu codificación explícitamente.
  • Segmentos JWT y el decodificador clásico. Alimentar un segmento JWT crudo a Convert.FromBase64String falla de dos maneras a la vez: los caracteres -/_ están fuera del alfabeto estándar, y el padding que falta rompe la regla de longitud. Normaliza primero, o usa Base64Url.
  • Espacios en blanco que ves y espacios en blanco que no ves. El decodificador salta el espacio, el tabulador, el salto de línea y el retorno de carro, y no salta nada más. Un espacio no separable, un separador de línea Unicode o una tabulación vertical en un payload (todos ellos sobreviven al copiar y pegar desde algunas páginas web) es un FormatException, no un encogimiento de hombros.
  • Un mensaje de error para cada delito. El FormatException del decodificador clásico no dice qué regla se rompió ni dónde. Depura comprobando la longitud, luego el alfabeto, luego el padding, en ese orden, o cambia a TryFromBase64String y IsValid para una respuesta booleana.
  • Sustitución UTF-8 silenciosa. Encoding.UTF8.GetString convierte las secuencias de bytes malformadas en U+FFFD sin quejarse. Si el payload puede que no sea UTF-8 válido, usa el fallback estricto de la sección de charsets o estarás investigando datos perdidos semanas después de que pasara.
  • Corte de stream en el lugar equivocado. Una stream Base64 solo puede cortarse en múltiplos de cuatro caracteres. Si troceas una decodificación en streaming en cualquier otro límite, el último grupo parcial caerá en TransformFinalBlock, donde o pertenece o rompe tu contabilidad de alineación.
  • Finales de línea PEM. Los archivos de certificado traen CRLF. Quita \r además de \n cuando desarman la armadura a mano, o la primera línea de tu DER "decodificado" es un retorno de carro vestido de byte de datos.
  • Doble codificación. Si un payload ya era Base64 cuando llegó a ti (una configuración que codificó en Base64 una cadena Base64, una API que codificó la salida de otro codificador), una decodificación te da más Base64, no tus datos. El viaje de ida y vuelta solo cierra después de tantas decodificaciones como hubo de codificaciones, y el lado del codificador de ese bug es el tema del artículo de codificación.

Una breve historia del Base64 en C#

La historia del Base64 en C# es también la historia del crecimiento de la plataforma .NET, y es más larga de lo que la mayoría espera:

  • .NET Framework 1.1, abril de 2003. Llegan Convert.FromBase64String y sus hermanos, y traen el diseño que todavía define la API: estricto con el alfabeto, generoso con los cuatro espacios en blanco, directo con sus errores. Durante la mayor parte de las dos décadas siguientes este único método es "el" decodificador Base64 de C#.
  • .NET 2.0, 2005. El enum Base64FormattingOptions se une a Convert, trayendo los saltos de línea al estilo MIME al lado de la codificación (y la tolerancia a espacios en blanco correspondiente al lado de la decodificación, donde ya trabajaba en silencio).
  • .NET Core 2.1, 2018. La era de los Span. Convert gana los métodos Try y una codificación basada en span, y llega la nueva clase System.Buffers.Text.Base64 con su contrato de OperationStatus, la decodificación in-place y IsValid, construida para el mundo de asignación cero de la reescritura centrada en memoria.
  • .NET 5, 2020. Salen los hermanos hex (Convert.ToHexString y compañía), el mismo patrón de diseño de Base64 aplicado a un alfabeto de 16 símbolos, una señal de que el patrón de clase de conversión se había convertido en un estilo de la casa.
  • .NET 6, 2021. X509Certificate2.CreateFromPem hace de PEM una entrada de primera clase, y una clase entera de código manual de desarme de armadura se vuelve opcional en los runtimes modernos.
  • .NET 9, noviembre de 2024. System.Buffers.Text.Base64Url por fin llega a la caja después de años de peticiones de la comunidad, y el paquete Microsoft.Bcl.Memory lo retroporta a .NET Framework 4.6.2 y superior para las bases de código legacy que todavía hacen correr todo.
  • .NET 11, en preview en el momento de escribir esto. La siguiente versión, prevista para finales de 2026, añade más APIs y sobrecargas de conveniencia de Base64 a los tipos existentes, continuando la lenta marcha hacia una superficie más ergonómica.

Conviene tenerlo en cuenta: la codificación en sí es mucho más antigua que todo esto. El primer uso estandarizado de lo que ahora llamamos MIME Base64 fue el protocolo Privacy-Enhanced Mail en 1987 (RFC 989), MIME estandarizó la forma envuelta a 76 caracteres en 1993, y el RFC 4648 en 2006 dio al formato su especificación moderna y consciente del alfabeto, incluida la variante URL-safe. C# heredó todo: cada capricho de envoltura de líneas y padding que encuentres en un formato de email de 30 años es un capricho que el decodificador de C# está diseñado para absorber.

Curiosidades de C#

  • El test de humo más pequeño. "TWFu" decodifica a Man. Tres bytes, sin padding, sin excusas. Es el hello world de la depuración de Base64 en C#, y recorre todo el camino feliz en cuatro caracteres.
  • Un decodificador con historia postal. La tolerancia a los espacios en blanco no es un accidente de implementación - es una decisión de diseño heredada de MIME: un cuerpo de email completo envuelto a 76 caracteres, con todos sus pares CRLF, es un único argumento válido para Convert.FromBase64String. El decodificador fue construido para devorar el formato que el email ha usado durante treinta años.
  • Un error, tres causas. El mensaje clásico de FormatException lista los tres modos de fallo que podría estar informando (carácter malo, demasiado padding, padding mal colocado) y no dice cuál se activó. Es el único mensaje de error de toda la superficie de la API que funciona como una pregunta de opción múltiple.
  • Un espacio de nombres que miente un poco. System.Buffers.Text suena a que va de procesamiento de texto, pero es en realidad el hogar de la conversión de binario a texto en general: el Utf8Parser y el Utf8Formatter que analizan números y fechas directamente a UTF-8 viven justo al lado de las clases de Base64.
  • El padding es opcional en un lado de la familia. La clase Base64Url decodifica AQIDBA (seis caracteres, sin padding) y AQIDBA== (los mismos bytes con padding) a los mismos cuatro bytes, mientras que el decodificador clásico solo acepta la forma rellena. Dos decodificadores, dos contratos, un runtime.
  • Cadenas que no deberían existir. Una cadena de C# puede legalmente contener bytes NUL, así que Encoding.UTF8.GetString de binario decodificado puede producir una "cadena" llena de caracteres de control que la consola, tu escritor de CSV y la mitad de las librerías JSON del planeta tratarán cada una de manera distinta. El sistema de tipos lo permite; el ecosistema, en su mayor parte, no.
  • Una reliquia de la 1.1 en pleno estado. Convert.FromBase64CharArray lleva la misma firma de tres parámetros desde abril de 2003, sobreviviendo a la revolución de los genéricos, a la revolución de los Span y a la revolución URL-safe sin añadir una sola sobrecarga. La era de los arrays de char de C# no se ha ido; solo está descansando.
  • Once caracteres, ocho bytes. Los identificadores de video de YouTube son base64url sin padding: 11 caracteres que decodifican a 8 bytes. Base64Url.GetMaxDecodedLength(11) te dice el 8, y la decodificación es una línea, que es una buena manera de terminar el día si eres de los que escriben ese tipo de cosas.

La otra dirección

Esa es la parte del decodificador, y es donde vive la mayor parte del dolor, porque decodificar es donde te encuentras con los datos de otras personas: sus decisiones de padding, sus saltos de línea, sus alfabetos, sus tokens. La dirección opuesta, tomar tus propios bytes y empaquetarlos en Base64, es un problema más sereno con su propio conjunto de decisiones que tomar y su propio conjunto de trampas. La codificación Base64 en C#, desde la pregunta de los 76 caracteres hasta los tokens URL-safe, se cubre a fondo en el artículo complementario enlazado abajo, y es una lectura corta y satisfactoria una vez que sabes qué buscar.

Última actualización: 2026-09-08

Artículo relacionado: Codificación Base64 en C# (CSharp): una guía completa