Decodificación Base64 en PowerShell: una guía completa
En algún lugar de una línea de log, de un archivo de configuración o de un mensaje de error, te tropiezas con él: una larga ristra de letras y dígitos con el plus o la barra de cuando en cuando, y uno o dos signos de igualdad aparcados de forma sospechosa al final. Parece ruido. No lo es. Es Base64, y ya sabes lo que quieres: lo que esconde.
Base64 es una traducción, no una compresión y no un candado. Reescribe cualquier secuencia de bytes en texto imprimible, cuatro caracteres por cada tres bytes de entrada (de modo que los datos codificados salen unos 33% más grandes que el original), usando un alfabeto de 64 caracteres más el signo de igualdad como relleno al final. La página de inicio de este sitio recorre el alfabeto, la matemática de bits y las variantes a fondo, así que este artículo dedica su tiempo a donde PowerShell marca la diferencia: el único método de .NET que vas a llamar, las reglas que impone y unas doce esquinas del trabajo real donde decodificar en PowerShell se pone interesante.
El método y su contrato
PowerShell no trae ningún cmdlet propio de Base64. El trabajo lo hace un método de una clase de .NET que forma parte del framework desde .NET Framework 1.1 en 2003, tres años antes de que PowerShell mismo llegara:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
Esa es la API entera: entra un string, sale un array de bytes. Funciona en cualquier PowerShell de cualquier sistema operativo, en Windows PowerShell 5.1 y en PowerShell 7 en Windows, Linux y macOS, porque es simplemente .NET. El contrato es lo bastante corto para memorizarlo, así que aquí va en forma de tabla:
| Entrada | Lo que te llega |
|---|---|
$null |
Un array vacío, sin error. PowerShell convierte $null en silencio a un string vacío antes de la llamada |
| Un string vacío | Un array vacío, sin error |
| Un payload válido | Un byte[], nunca un string, aunque los datos sean texto |
| Un payload inválido | Una FormatException, envuelta para ti en una MethodInvocationException |
Un aviso antes de escribir cualquier manejo de errores: esa FormatException tiene un único mensaje que cubre tres pecados distintos. Un carácter fuera del alfabeto, más de dos caracteres de relleno, o un carácter que no es espacio en blanco escondido entre el relleno, todo produce exactamente la misma frase. Cuando la ves, el mensaje no te dice cuál de los tres cometiste, así que vuelves y relees tu entrada:
try {
[System.Convert]::FromBase64String("SGV!G8s=")
}
catch {
$real = $_.Exception.InnerException
$real.GetType().Name
# FormatException
$real.Message
}
Y la regla del múltiplo de cuatro tiene una arista que sorprende la primera vez que la tocan. Cuatro caracteres sin ningún relleno es perfectamente válido; solo significa que los bits sobrantes del último carácter se descartan. Tres caracteres no es un múltiplo de cuatro, y se rechaza:
[System.Convert]::FromBase64String("SGVs").Count
# 3: cuatro caracteres sin relleno está bien
[System.Convert]::FromBase64String("SGV")
# FormatException: tres caracteres no es un múltiplo de cuatro
Lo que el decodificador acepta y lo que no
El decodificador es estricto con el alfabeto y generoso con una cosa muy concreta. Los caracteres válidos son los 64 dígitos de Base64 (de A a Z, de a a z, de 0 a 9, el plus y la barra) y el signo de igualdad como relleno al final. Exactamente cuatro caracteres de espacio en blanco se ignoran estés donde estén y aparezcan las veces que sean: la tabulación, el salto de línea, el retorno de carro y el espacio. La documentación oficial de .NET los lista por sus nombres Unicode, y eso te dice que se trata de una garantía documentada y no de un golpe de suerte.
En la práctica esto es una superpotencia. MIME, la codificación de correo que puso a Base64 en el mapa, envuelve las líneas codificadas a los 76 caracteres, así que un payload que viajó por correo, por un ticket o por un archivo de log suele llegar roto en muchas líneas. Al decodificador no le importa. Pégalo tal cual:
$wrapped = "VGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZy4gQmFzZTY0IHRleHQg`r`n" +
"YXJyaXZlcyB3cmFwcGVkIGF0IHNldmVudHktc2l4IGNvbHVtbnMgaW4gbWFpbCwgc28gdGhlIGRl`r`n" +
"Y29kZXIgbXVzdCBub3QgY2FyZS4="
[System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($wrapped))
# The quick brown fox jumps over the lazy dog. El texto Base64 llega
# envuelto a 76 columnas en el correo, y el decodificador no tiene por qué preocuparse por eso.
Todo lo demás que no sea un carácter del alfabeto es un alto seco. Los infractores más comunes en la naturaleza son el espacio no divisible (el favorito del texto pegado desde páginas web) y la marca de orden de bytes (la marca invisible que te persigue cuando un archivo se leyó con la codificación equivocada). Ninguno de los dos es espacio en blanco a los ojos de este método, así que ambos lanzan:
try {
[System.Convert]::FromBase64String("SGVs`u{00A0}G8=")
}
catch {
$_.Exception.InnerException.GetType().Name
# FormatException
}
La estrictez es intencional, no manía. RFC 4648, el estándar que codificó Base64 en 2006, dice que las implementaciones deben rechazar los caracteres que no están en el alfabeto a menos que el protocolo permita explícitamente la indulgencia, porque un decodificador que se traga en silencio los caracteres extraños se puede convertir en un canal encubierto para contrabandear datos por encima de cualquier cosa que solo inspeccione el alfabeto. El decodificador de .NET sigue la regla estricta, y normalmente es lo que quieres.
Un array de bytes no es un string
El método se detiene a propósito en el array de bytes. Lo que esos bytes significan es una segunda decisión que solo tú puedes tomar, y fallar en la adivinanza es el error más famoso del trabajo con Base64 en PowerShell. El supuesto por defecto, UTF-8, acierta en casi todo lo del internet, y el viaje de ida y vuelta son dos llamadas:
$bytes = [System.Convert]::FromBase64String("SGVsbG8sIFdvcmxkIQ==")
[System.Text.Encoding]::UTF8.GetString($bytes)
# Hello, World!
Las codificaciones a las que realmente acudirás, y lo que hace cada una cuando fallas:
| Codificación | Úsala cuando | Si fallas |
|---|---|---|
UTF8 |
APIs web, JSON, JWTs, todo lo moderno. El valor seguro por defecto | El texto Latin-1 o UTF-16 vuelve como mojibake |
Unicode (UTF-16LE) |
El payload vino de herramientas de Windows, de un valor del registro, o de un string de .NET que se codificó antes de enviarlo | Cada carácter se lleva un hueco alrededor, porque leíste un byte donde iban dos |
ASCII |
Credenciales HTTP Basic clásicas y otros protocolos de 7 bits garantizados | Cualquier cosa por encima del valor 127 se convierte en signo de interrogación |
Latin1 |
Texto europeo legado anterior a UTF-8 | Las secuencias UTF-8 de varios bytes se parten en varias letras equivocadas |
Default |
Casi nunca. Es la página de código del sistema de la máquina | Tu script se comporta distinto con cada configuración regional de Windows |
El fallo clásico es texto UTF-8 decodificado como UTF-16. Los bytes son reales, el método está contento, y el resultado sigue siendo basura:
# "SGk=" son los bytes UTF-8 de "Hi"
[System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String("SGk="))
# Un único carácter ilegible: 2 bytes de UTF-8 leídos como una unidad UTF-16 de 2 bytes
La regla práctica: si el texto decodificado parece que cada carácter lleva un hueco invisible alrededor, o que viene de otro alfabeto, estás justo una codificación al lado. Pregunta dónde se produjo el dato, y en caso de duda, confía en UTF-8 pero verifica con tus ojos los primeros caracteres. Y decide la codificación antes de decodificar, no después de que el mojibake aparezca en tu log.
base64url: el alfabeto que se lleva bien con las URLs
Te cruzarás con un primo de Base64 en cada token de API, JWT e identificador embebido en URL que toques en tu vida. El plus y la barra del Base64 estándar solo son legales en una URL después de pasarlos por codificación porcentual, y el relleno de signos de igualdad parece un separador de campos. Así que RFC 4648 definió un alfabeto seguro para URLs y nombres de archivo: los mismos 64 caracteres, salvo que el plus se vuelve guion y la barra se vuelve guion bajo. El relleno suele tirarse por completo, porque la longitud de los datos lo hace innecesario. El RFC se cuida de decir que esta variante debe llamarse base64url y no simplemente "base64", y el resto de esta sección se atiene a eso.
.NET sí trae una clase dedicada para ello, System.Buffers.Text.Base64Url, añadida en .NET 9 con métodos rápidos de codificación y decodificación construidos enteramente alrededor de parámetros ReadOnlySpan<T>. El PowerShell actual (7.4 y posterior, una vez que corre sobre una versión de .NET que trae la clase) puede llamar hoy directamente a estas sobrecargas que reciben spans, gracias a una conversión implícita de array/string a span que el enlazador de métodos ahora realiza, así que [System.Buffers.Text.Base64Url]::DecodeFromChars("--__AQI") funciona sin ceremonia. No siempre fue así: Windows PowerShell 5.1 y las versiones antiguas de PowerShell 7.x no podían enlazarse a parámetros de span en absoluto, y la clase ni siquiera existía antes de .NET 9, así que cualquier script que tenga que correr en 5.1, un 7.x antiguo o un host anterior a .NET 9 todavía necesita la versión portable: intercambiar los dos caracteres y restaurar el relleno antes de entregar el texto al decodificador estándar. El relleno a añadir es lo que haga que la longitud sea un múltiplo de cuatro:
$token = "--__AQI" # base64url, sin relleno
$standard = $token.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
$bytes = [System.Convert]::FromBase64String($standard)
$bytes -join ","
# 251,239,255,1,2
Dos trampas viven en ese bloque pequeño. Primera, la matemática del relleno: un payload cuya longitud ya sea múltiplo de cuatro no necesita relleno, y la guarda -eq 4 es lo que mantiene la expresión honesta. Segunda, la dirección: cuando solo decodificas, añades relleno e intercambias; nunca quitas relleno a una entrada de Base64 estándar, porque los decodificadores estándar lo esperan ahí. Si la fuente es un JWT o un token de API, será base64url sin relleno, y la receta de arriba es exactamente la forma que quieres.
Abrir un JWT sin las claves
Un JSON Web Token son tres segmentos base64url unidos por puntos: cabecera, payload, firma. Los dos primeros son JSON plano, y Base64 no es cifrado, así que cualquiera que tenga el token puede leer ambos. Eso es una característica, no un defecto: el token está diseñado para ser inspeccionado, y la firma es lo que lo hace infalsificable. PowerShell reduce el vistazo a tres líneas:
$jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
$parts = $jwt.Split(".")
function Decode-UrlSegment([string]$segment) {
$standard = $segment.Replace("-", "+").Replace("_", "/")
$pad = 4 - ($standard.Length % 4)
if ($pad -eq 4) { $pad = 0 }
$standard = $standard.PadRight($standard.Length + $pad, "=")
return [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($standard))
}
Decode-UrlSegment $parts[0] | ConvertFrom-Json | ConvertTo-Json -Compress
Decode-UrlSegment $parts[1] | ConvertFrom-Json
# propiedad name:
(Decode-UrlSegment $parts[1] | ConvertFrom-Json).name
# John Doe
Tres cosas que conviene tener en cuenta. El tercer segmento, la firma, también es base64url, pero decodifica a bytes binarios de firma, no a texto, así que no esperes JSON bonito ahí. La cabecera normalmente solo te dice qué algoritmo firmó el token (HS256, RS256, ...), y una cabecera que dice none es una bandera roja, no una comodidad. Y leer el payload no es confiar en él: base64 te deja ver las claims, solo la firma las hace auténticas. Si tu trabajo es aceptar tokens, verifica la firma con la clave del emisor; si tu trabajo es depurar uno, el código de arriba es todo lo que necesitas.
Archivos, PEM y el largo camino hasta los bytes
La forma de archivo más común es un archivo de texto que contiene el Base64 de algo más grande: un blob de copia de seguridad, un binario descargado, un objeto serializado. El viaje de ida y vuelta son cuatro líneas, y la manera moderna de leer la salida es un array de bytes de verdad, no una suposición de texto:
$encoded = Get-Content -Path ./payload.b64 -Raw
$encoded = $encoded.Trim()
$bytes = [System.Convert]::FromBase64String($encoded)
[System.IO.File]::WriteAllBytes("./payload.bin", $bytes)
$bytes.Length
# cuántos bytes llevaba el texto
Releer el binario original es donde PowerShell 6 y los más nuevos se pagan solos. El parámetro -AsByteStream lee bytes crudos, y con -Raw te entrega un byte[] auténtico de una vez:
$bytes = Get-Content -Path ./photo.png -AsByteStream -Raw
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path ./photo.b64 -Value $encoded -NoNewline
$bytes.Length
# tamaño original, antes del impuesto de texto del 33 por ciento
Deja -Raw de lado y obtienes un stream de objetos de byte individuales (un Object[] al capturarlos), que sirve para inspeccionar pero es incorrecto para pasarlo a métodos de .NET que esperan un array. Y Windows PowerShell 5.1 no tiene -AsByteStream en absoluto, así que en 5.1 la lectura fiable es [System.IO.File]::ReadAllBytes(), que existe en todas partes.
PEM es el primo blindado que conoces de cada certificado y clave privada: un cuerpo de Base64 estándar, normalmente envuelto a los 64 caracteres, entre líneas -----BEGIN ... y -----END .... El blindaje es texto; el cuerpo es el payload. Quita el blindaje, une las líneas, decodifica:
$pem = Get-Content -Path ./certificate.pem -Raw
$body = ($pem -split "`n") | Where-Object { $_ -notmatch "^-----" } | ForEach-Object { $_.Trim() }
$der = [System.Convert]::FromBase64String(($body -join ""))
$der.Length
# el tamaño DER binario del certificado
Como el decodificador estándar ignora el espacio en blanco de todas formas, el -join "" es doble seguridad y no requisito, pero mantener el script explícito sobre qué quita hace que se comporte igual en cada máquina y cada convención de fin de línea. La otra dirección, envolver bytes DER en PEM, es solo el codificador de Base64 más dos líneas de texto, y el artículo de codificación del sitio hermano muestra el envolvimiento a 64 columnas completo.
Certificados y la caja de herramientas de Windows
Los certificados son los ciudadanos de Base64 más pesados del trabajo diario, y PowerShell puede abarcar a la familia entera. Un archivo PFX es un paquete binario de certificado más clave privada, y es el formato que más a menudo ves rondando como texto Base64 en archivos de configuración y scripts de despliegue. Decodificarlo de vuelta en un certificado vivo es un one-liner con el tipo de .NET, y funciona entre plataformas en PowerShell 7:
$bytes = [System.Convert]::FromBase64String($pfxText)
$password = ConvertTo-SecureString "secret" -AsPlainText -Force
$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($bytes, $password)
$cert.Subject
# CN=example.org
$cert.NotAfter
# cuando deja de ser verdad
PowerShell 7 también trae Get-PfxCertificate, que lee un archivo PFX directo del disco con un parámetro -Password, así que para archivos en disco puedes saltarte la decodificación manual por completo. Un certificado desnudo (sin clave) es aún más simple: los bytes DER van directo al mismo tipo X509Certificate2 sin ninguna contraseña.
Fuera del lenguaje, hay dos herramientas nativas que valen la pena conocer. En Windows, certutil -decode infile.b64 outfile decodifica un archivo Base64 con semántica de archivo-entrada/archivo-salida (añade -f para sobrescribir), lo que lo convierte en la primera opción para arreglos rápidos en una consola de comandos plana. Su hermano certutil -encode tiene un flag que vale la pena recordar: -unicodetext convierte el texto de entrada a UTF-16 antes de codificarlo en Base64, escondiendo una decisión de codificación entera dentro de un solo conmutador. En Linux y macOS la utilidad clásica es base64 -d, que decodifica un archivo o la entrada estándar, saltándose los saltos de línea por defecto; en GNU coreutils añade -i si el payload también trae espacios, tabulaciones o CRLF de correo de Windows.
Comandos en un sobre de Base64
PowerShell lleva un motivo de fábrica para hablar Base64 desde la versión 1.0: el parámetro -EncodedCommand del propio host. Le entregas a pwsh un string Base64, él decodifica los bytes como UTF-16LE, y el resultado se ejecuta como comando. El propósito oficial, tomado de la documentación, es presentar comandos que requieren comillas complejas o llaves sin pelearse con las reglas de comillas de la shell exterior:
$command = "Write-Host encoded-hello"
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
# VwByAGkAdABlAC0ASABvAHMAdAAgAGUAbgBjAG8AZABlAGQALQBoAGUAbABsAG8A
pwsh -NoProfile -EncodedCommand $encoded
# encoded-hello
Lee esa segunda línea con cuidado, porque ahí tropieza todo el mundo: el payload debe ser UTF-16LE, es decir [System.Text.Encoding]::Unicode. Si codificas el comando como UTF-8 en su lugar, PowerShell lo decodifica con gusto como UTF-16LE y ejecuta un comando hecho de mojibake, y el mensaje de error que produce es un retrato perfecto del error:
$wrong = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($command))
pwsh -NoProfile -EncodedCommand $wrong
# Error: una muralla de caracteres ininteligibles, "El término ... no se reconoce..."
Ese mismo mecanismo es por lo que los equipos de seguridad se fijan en el Base64 dentro de PowerShell. Un token largo y opaco pasado a -EncodedCommand es una forma común en el tooling automatizado, y es exactamente por eso que los productos de protección de endpoints decodifican estos payloads antes de que corran: nada en Base64 esconde el comando a un decodificador, solo lo esconde a un humano que lee una lista de procesos. Si generas comandos codificados para tu propia automatización, guarda el comando de origen junto al token, porque el token mismo no va a explicarse a las 3 de la madrugada.
Decodificar cuando la entrada es enorme
Para tamaños de todos los días, el enfoque de un solo método es el rápido. Un binario de cinco megabytes se convierte en un string de unos seis millones novecientos mil caracteres, y decodificar ese string toma milisegundos de un solo dígito en una máquina moderna. La nota de la propia documentación de .NET dice que FromBase64String está diseñado para procesar un único string que contiene todos los datos, lo cual es cierto, y también está bien hasta límites muy grandes, porque el método trabaja sobre el string en su lugar sin copias extra significativas.
Cuando el payload es más grande de lo que te sentirías cómodo sosteniendo en un solo string, o llega como un stream (una descarga, un socket, un log enorme), la herramienta documentada es System.Security.Cryptography.FromBase64Transform envuelta en un CryptoStream: le das texto Base64 y sacas bytes decodificados, y en cada momento solo hay un buffer pequeño vivo. Ten en cuenta que TransformStream, el ayudante de C# para esto, es un método de extensión, y PowerShell no ve métodos de extensión, así que instancias el CryptoStream directamente:
$inputStream = [System.IO.File]::OpenRead("./payload.b64")
$transform = [System.Security.Cryptography.FromBase64Transform]::new()
$stream = [System.Security.Cryptography.CryptoStream]::new(
$inputStream, $transform, [System.Security.Cryptography.CryptoStreamMode]::Read)
$destination = [System.IO.File]::Create("./payload.bin")
$buffer = New-Object byte[] 65536
while (($read = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
$destination.Write($buffer, 0, $read)
}
$destination.Dispose()
$stream.Dispose()
$inputStream.Dispose()
Para el noventa por ciento de los trabajos, el camino simple sigue siendo el correcto: lee el archivo de texto entero con Get-Content -Raw, córtalo, decodifícalo, escribe los bytes. Acude a la versión de stream cuando el archivo sea demasiado grande para guardarlo en memoria con comodidad, o cuando los datos lleguen a trozos. Y no intentes iterar sobre líneas y decodificar cada línea por separado: los grupos de cuatro caracteres de Base64 no respetan tus saltos de línea, así que una línea que parte un grupo a la mitad no decodifica por sí sola. Lee el texto entero, y luego decodifica una vez.
Trampas que cuestan tardes enteras
- La suposición del charset. UTF-8 leído como UTF-16, o Latin-1 leído como UTF-8, produce mojibake con total confianza. Decide la codificación por la procedencia de los datos, por defecto a UTF-8, y mira los primeros caracteres decodificados antes de confiar en el resto.
- Caracteres invisibles del web. Un espacio no divisible o una marca de orden de bytes pegados desde una página o un correo enriquecido son un carácter extranjero para el decodificador y lanzan la
FormatExceptiongenérica. Pasa la entrada por.Trim()y una comprobación de caracteres no imprimibles antes de decodificar. - Confusión de relleno. El Base64 estándar llega con
=o==al final; el base64url de los tokens llega sin ninguno. Alimentar uno con la receta hecha para el otro es la rotura silenciosa más común en el trabajo con APIs, y la comprobación de longitud de la sección base64url es la guarda. - El mensaje único, tres crímenes. Como el mensaje de
FormatExceptioncubre caracteres malos, relleno excesivo y relleno sucio todo a la vez, los bloques catch que solo registran el mensaje te mandan a dar vueltas. Registra también la longitud de la entrada y la primera zona ofensiva. - Esperar un string de vuelta. El resultado siempre es un array de bytes. En el momento en que empiezas a formatearlo directamente como string obtienes una lista de números, no texto. Convierte con una codificación explícita, una vez, al final.
- El valor por defecto de archivos en 5.1. Windows PowerShell 5.1 lee archivos sin BOM con la página de código ANSI del sistema, mientras que PowerShell 7 asume UTF-8. Si tu script lee el archivo de texto Base64 en 5.1 y el archivo es UTF-8 con no-ASCII alrededor del payload, la corrupción sucede antes de que el decodificador lo vea.
- Tratar a Base64 como candado. Es una traducción. Una contraseña, token o secreto en Base64 es texto plano con un disfraz, y cada decodificador del planeta, incluido este artículo, lo abre en una línea.
Hábitos que mantienen los scripts honestos
- Recorta la entrada externa antes de decodificar. Un solo
.Trim()elimina más incidentes de producción que cualquier manejador de errores. - Valida antes de decodificar cuando la fuente no es de confianza: después de quitar los cuatro caracteres de espacio en blanco permitidos, el string debe coincidir solo con caracteres del alfabeto y como mucho dos signos de igualdad al final. Una comprobación rápida con regex convierte una excepción misteriosa en un mensaje limpio de entrada rechazada.
- Mantén los bytes como bytes hasta el último paso. Decodifica una vez, entrega el
byte[]a la API de archivos o al codificador que lo necesite, y solo entonces convierte a texto con una codificación deliberada. - Registra longitudes, no payloads. El tamaño de la entrada y el tamaño de la salida decodificada te cuentan casi todo sobre un fallo de decodificación, sin pegar datos posiblemente sensibles al log.
- Para cualquier cosa que cruce un cable, anota en qué alfabeto va, estándar o base64url, y con qué convención de relleno, en la misma línea de código que lo decodifica. Tú del futuro eres el consumidor de esa nota.
Cómo PowerShell heredó su decodificador
La historia verdadera más corta del Base64 en PowerShell es que PowerShell nunca escribió uno. El método que usas, Convert.FromBase64String, salió con .NET Framework 1.1 en 2003, y cada PowerShell desde la versión 1.0 de noviembre de 2006 se ha limitado a exponer el .NET sobre el que corre. El proyecto se llamaba Monad mientras lo construían, se mostró al público por primera vez en la Professional Developers Conference de octubre de 2003, y para cuando se lanzó, la pareja codificador-decodificador de .NET que envuelve ya tenía tres años y estaba en uso diario.
El formato mismo se estandarizó el mismo año en que la shell salió. RFC 4648, publicado en octubre de 2006, es el documento que fijó el alfabeto, las reglas de relleno, la expectativa de decodificación estricta y la variante base64url, y todavía describe exactamente el comportamiento que FromBase64String implementa hoy. Cuando PowerShell se hizo de código abierto y multiplataforma en agosto de 2016 como PowerShell Core, el decodificador viajó gratis a Linux y macOS sin cambios, porque no había nada que cambiar.
El único añadido genuino es el módulo mantenido por la comunidad Microsoft.PowerShell.TextUtility de la PowerShell Gallery, cuyo cmdlet ConvertFrom-Base64 envuelve el mismo método de .NET y añade un conmutador -AsByteArray más un valor por defecto de texto que decodifica como UTF-8. Instálalo con Install-Module -Name Microsoft.PowerShell.TextUtility si prefieres la forma de cmdlet, pero una advertencia: el módulo está ahora archivado y ya no se mantiene activamente, que es otra razón por la que el método integrado sigue siendo la recomendación para scripts nuevos.
Hechos que valen la pena recordar
- El decodificador ignora tabulaciones, saltos de línea, retornos de carro y espacios en cualquier parte de la entrada. Cien líneas envueltas se decodifican exactamente como una línea larga.
$nully el string vacío se decodifican ambos a un array vacío sin quejarse, lo que hace aFromBase64Stringinusualmente indulgente en el borde.- El único mensaje de
FormatExceptioncubre tres modos de fallo distintos. Cuando se dispara, la entrada, no el mensaje, es donde está la respuesta. "SABpAA=="es el stringHien la codificación interna propia de PowerShell, UTF-16LE. El doble de largo que la codificación UTF-8 de las mismas dos letras, y esa proporción es la huella dactilar del texto nativo de Windows en cualquier Base64 que vayas a leer.-EncodedCommandexiste desde el primer lanzamiento de PowerShell, y su payload está mandado a ser UTF-16LE, no UTF-8. Codifica con la codificación equivocada y la shell ejecuta tu mojibake con gusto.- Los ayudantes más nuevos de Base64 de .NET basados en spans, incluida la clase
Base64Url, eran inalcanzables desde las versiones antiguas de PowerShell porque los spans son tipos parecidos a byref a los que el enlazador de métodos no podía enlazarse. Eso ha cambiado: el PowerShell actual (7.4 o posterior, sobre una versión de .NET lo bastante nueva como para traer la clase) resuelve un argumento de array o string contra un parámetroReadOnlySpan<T>sin quejarse, así que la llamada directa funciona hoy. El intercambio de dos caracteres se gana el sueldo como la versión que también corre en Windows PowerShell 5.1 y hosts más viejos, no como el único camino que queda. Get-Content -AsByteStreamsin-Rawte da un stream de objetos de byte, no un array de bytes. Añade-Rawy el tipo es exactamente lo que los métodos de .NET esperan.
El camino de vuelta
Todo en este artículo va de tomar un string Base64 y recuperar tus datos. La operación espejo, convertir datos en Base64, parece un one-liner hasta que te encuentras con el hecho de que los strings de PowerShell no son bytes, de que UTF-16 duplica tu tamaño, de que el envolvimiento de líneas tiene dos anchos convencionales, y de que la salida base64url necesita su propia cirugía de dos caracteres. Esa dirección se lleva su propio tratamiento completo, con sus propias trampas y su propia historia, en el artículo relacionado del sitio hermano, Codificación Base64 en PowerShell, al que esta página enlaza abajo.
Última actualización: 2026-09-07
Artículo relacionado: Codificación Base64 en PowerShell: una guía completa