¿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.

Codificación Base64 en PowerShell: una guía completa

Tienes un string, un archivo, un certificado o un token, y el otro lado del cable lo quiere como una larga ristra de letras y dígitos: imprimible, pegable en un correo, una URL o un archivo de configuración, sin un solo byte binario capaz de romper el transporte. Eso es Base64. Es una traducción, no una compresión y no un candado: tres bytes de entrada se convierten en cuatro caracteres de salida, así que el texto que envías ocupa aproximadamente un 33% más de espacio que lo que lo empezó, usando un alfabeto de 64 caracteres más el signo de igualdad como relleno al final.

La página de inicio de este sitio cubre el alfabeto, la matemática de bits y las variantes en detalle. Este artículo cubre la dirección de la codificación desde el lado de PowerShell: el único método de .NET que vas a llamar, el paso faltante que hace tropezar a todo el mundo en su primer script, las convenciones de envolvimiento de líneas que difieren según el protocolo, el alfabeto seguro para URLs, y el puñado de trabajos reales donde codificar en PowerShell premia a los cuidadosos y castiga a los descuidados.

El método y el paso faltante

PowerShell no trae ningún cmdlet propio de Base64. El trabajo lo hace un método que forma parte del framework de .NET desde .NET Framework 1.1 en 2003, tres años antes de que PowerShell mismo llegara:

$bytes = [System.Text.Encoding]::UTF8.GetBytes("Hello")
[System.Convert]::ToBase64String($bytes)
# SGVsbG8=

Esa es la API entera: entra un array de bytes, sale un string, en cualquier PowerShell de cualquier sistema operativo, porque es simplemente .NET. El paso faltante es la primera línea del ejemplo, y ahí es donde los principiantes pierden su primera hora. El método no acepta tu string. Acepta bytes, y la pregunta "¿qué bytes significa mi string?" es una pregunta de codificación que solo tú puedes responder. Aquí va el contrato de las sobrecargas que de verdad puedes llamar desde PowerShell:

Lo que pasas Lo que obtienes
byte[] Una línea larga de Base64 estándar, con relleno = donde la longitud lo exija
byte[] más InsertLineBreaks Los mismos datos, partidos a los 76 caracteres con CRLF entre líneas
byte[], offset, count Solo el trozo solicitado del array, codificado
Un string como "Hello" Una excepción de conversión. PowerShell no puede convertir un string en un array de bytes por su cuenta
$null Una ArgumentNullException, envuelta para ti en una MethodInvocationException

Fíjate en lo que no está en esa tabla: no hay ninguna sobrecarga que diga "codifica este texto". Codificar texto en PowerShell es siempre un proceso de dos pasos. Tú decides la codificación, tú produces los bytes, y solo entonces el método de Base64 entra en la conversación. Mantén esas dos decisiones visiblemente separadas en el script, porque la segunda es invisible y la primera es donde viven los bugs.

Codificar texto: elige la codificación primero

El valor seguro por defecto para cualquier cosa que cruce el internet moderno es UTF-8. Las APIs web, el JSON, los JWTs, todo lo que un navegador o un servidor escribiera en la última década esperará bytes UTF-8 debajo del Base64, y el patrón de dos pasos es el hábito que quieres construir:

$text = "Hello, PowerShell!"
$bytes = [System.Text.Encoding]::UTF8.GetBytes($text)
$encoded = [System.Convert]::ToBase64String($bytes)
# SGVsbG8sIFBvd2VyU2hlbGwh

Cuando acudes a otra codificación, normalmente estás atendiendo a un sistema legado, y la tabla de abajo es la guía práctica:

Codificación Úsala cuando Si eliges la equivocada
UTF8 APIs web, JSON, JWTs, todo lo moderno. La elección por defecto El decodificador del otro lado ve mojibake en vez de tu texto
Unicode (UTF-16LE) El consumidor es un componente de Windows o .NET que codifica strings de .NET, o -EncodedCommand Tu payload es el doble de largo de lo que el consumidor espera, y lleno de sorpresas
ASCII Protocolos clásicos de 7 bits como las credenciales HTTP Basic Cualquier cosa por encima del valor 127 se reemplaza antes de que la codificación suceda
Latin1 Sistemas europeos legados anteriores a UTF-8 Un byte por carácter, y cada carácter no Latin-1 se convierte en signo de interrogación

Un truco útil de depuración funciona en ambas direcciones: el relleno y la longitud del Base64 te dicen cuántos bytes se codificaron, y el aspecto del texto decodificado te dice de qué mundo de dos bytes o de un byte viene. Un payload sospechosamente par en tamaño y lleno de caracteres normales y vacíos alternados suele ser UTF-16 con un disfraz de UTF-8, o al revés.

La sorpresa UTF-16

PowerShell guarda los strings como UTF-16 internamente, y ese hecho se cuela en el trabajo con Base64 en un lugar concreto y muy común: escribes el Base64 para un consumidor que es a su vez un componente de .NET o Windows, y codificas con Unicode porque eso es lo que son los strings de .NET. Es un instinto correcto para algunos consumidores y un error de duplicar el tamaño para todos los demás. Los mismos cuatro caracteres visibles, dos codificaciones:

$same = "Café"
[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($same))
# Q2Fmw6k=  cinco bytes
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($same))
# QwBhAGYA6QA=  ocho bytes

Mismo texto, el doble de tamaño, y los dos strings no son intercambiables: un consumidor que espera uno y recibe el otro no fallará a lo grande; simplemente leerá basura. La regla que evita que esto te muerda es tratar la codificación como parte del protocolo, no como un detalle local. Si el sistema receptor es un navegador, una API REST o un servidor moderno, es UTF-8 a menos que la documentación diga lo contrario. Si es el propio host de PowerShell vía -EncodedCommand, o un string de .NET en una tubería solo de Windows, es UTF-16LE. Cuando el protocolo no dice nada, pregunta al otro lado con qué llamará a GetString, porque esa es la pregunta que de verdad decide.

Números, bytes y todo lo demás

El método está declarado para recibir un array de bytes, pero la conversión de tipos de PowerShell es generosa con lo que cuenta como uno, y conocer los bordes te ahorra sorpresas:

[System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# AQID+vs=
[System.Convert]::ToBase64String([char[]]"Café")
# Q2Fm6Q==  un byte por carácter, el valor del carácter como número
[System.Convert]::ToBase64String([int[]](72, 101, 108, 108, 111))
# SGVsbG8=
[System.Convert]::ToBase64String(123)
# ew==  un número suelto es aceptado donde se espera un array entero

Dos bordes de ese bloque merecen atención. Los arrays de caracteres convierten un byte por carácter usando el valor numérico del carácter, que para texto latino es exactamente lo que esperan los sistemas legados que usan este truco, y para cualquier cosa más allá produce los bytes equivocados en silencio. Un entero mayor que 255 es el borde que falla a lo grande en su lugar: la conversión a byte de PowerShell se niega a aceptar valores fuera de 0-255 con una excepción, así que 256 detiene el script en el casteo en vez de corromper tus datos en silencio. Si tu fuente son números, haz el casteo explícito: [byte[]](1, 2, 3) dice exactamente lo que quiere decir.

Pásale un string al método y recibes el mismo trato ruidoso por otra razón: no hay manera de saber qué bytes significa un string, así que el motor de conversión de PowerShell se rinde. Pásale $null y .NET lanza antes de hacer nada. Ambas son un comportamiento correcto, y ambas son la razón por la que el patrón de dos pasos de la primera sección es el único que vale la pena tener.

Envolvimiento de líneas: 76, 64 y ninguno

Por defecto el codificador produce una línea larga, por muchos datos que le des. Para un archivo de unos pocos kilobytes está bien. Para datos que leerá un humano, que se pegarán en un correo, o que se compararán en un diff de control de versiones, una muralla de ocho millones de caracteres es un problema práctico, y la convención es envolver. PowerShell y los protocolos que atiende conocen tres anchos, y no son intercambiables:

Ancho Quién lo espera Fin de línea
76 caracteres MIME, correo y la mayoría de los transportes de texto. El valor por defecto de InsertLineBreaks CRLF
64 caracteres Archivos PEM: certificados, claves privadas y el resto de la familia -----BEGIN Convencionalmente LF
Ninguno APIs, tokens, archivos de configuración, cualquier cosa donde el payload lo procesa una máquina Ni una línea en absoluto

El envolvimiento integrado es un cambio de un solo parámetro, y es el que quieres para payloads de estilo correo:

$text = "The quick brown fox jumps over the lazy dog. Base64 output arrives wrapped at different widths depending on who is reading it."
$wrapped = [System.Convert]::ToBase64String(
  [System.Text.Encoding]::UTF8.GetBytes($text),
  [Base64FormattingOptions]::InsertLineBreaks)
# 76 caracteres por línea, CRLF entre ellas, exactamente como MIME espera

PEM es la excepción al integrado, porque OpenSSL y todo el ecosistema -----BEGIN envuelven a los 64 caracteres, y ningún flag de .NET produce ese ancho. El bucle es corto y es la receta estándar:

$der = [System.IO.File]::ReadAllBytes("./certificate.der")
$b64 = [System.Convert]::ToBase64String($der)
$lines = for ($i = 0; $i -lt $b64.Length; $i += 64) {
  $b64.Substring($i, [Math]::Min(64, $b64.Length - $i))
}
$pem = @("-----BEGIN CERTIFICATE-----") + @($lines) + @("-----END CERTIFICATE-----")
Set-Content -Path "./certificate.pem" -Value ($pem -join "`n")

La razón de que el ancho importe en absoluto es que los grupos de cuatro caracteres de Base64 no respetan los saltos de línea, así que un decodificador puede ignorar los saltos por completo o puede imponerlos. El decodificador que usa este sitio los ignora, pero los consumidores estrictos, y hay muchos en los mundos de los certificados y del correo, tratan un salto inesperado como un carácter extranjero y rechazan el payload. Cuando eliges un ancho, estás haciendo un contrato con el consumidor, y vale la pena un comentario en el script que nombre con quién te estás quedando.

base64url: dos caracteres y una decisión de relleno

El plus y la barra del Base64 estándar solo son legales dentro de una URL después de pasarlos por codificación porcentual, y el relleno de signos de igualdad se lee como 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, y el relleno suele tirarse porque la longitud de los datos lo hace innecesario. Cada token de API y cada JWT que hayas manejado están escritos en esta variante, que el estándar insiste en llamar base64url y no simplemente base64.

El codificador estándar de PowerShell produce el alfabeto estándar, así que la conversión a base64url son dos intercambios de caracteres y una decisión de relleno:

$bytes = [System.Text.Encoding]::UTF8.GetBytes("Париж encoded 大阪")
$standard = [System.Convert]::ToBase64String($bytes)
$standard
# 0J/QsNGA0LjQtiBlbmNvZGVkIOWkp+mYqg==  el alfabeto estándar, relleno incluido
$url = $standard.Replace("+", "-").Replace("/", "_").TrimEnd("=")
$url
# 0J_QsNGA0LjQtiBlbmNvZGVkIOWkp-mYqg  la forma segura para URL, relleno quitado

Quitar el relleno es seguro en el mundo base64url porque el consumidor recalcula cuál habría sido el relleno a partir de la longitud del string. Eso no es cierto en todas partes, así que toma la decisión explícita: tira el relleno para tokens, segmentos de JWT y embebido en URL, consérvalo para lo que alimenta a un consumidor estricto de alfabeto estándar, y apunta cuál elegiste. El runtime de .NET sí trae una clase dedicada para este alfabeto, System.Buffers.Text.Base64Url (añadida en .NET 9), con métodos construidos 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 a estos directamente - [System.Buffers.Text.Base64Url]::EncodeToString($bytes) funciona hoy, porque el enlazador de métodos ahora convierte un argumento de array a span implícitamente - pero el intercambio de dos caracteres sigue siendo el que debes usar siempre que el script tenga que correr en Windows PowerShell 5.1, una versión antigua de PowerShell 7.x, o un host sobre un runtime anterior a .NET 9, y funciona en cada una de esas versiones.

Acuñar un JWT

Un JSON Web Token es el uso real insignia de base64url, y también es una buena prueba completa de la tubería de codificación, porque un JWT son tres segmentos codificados unidos por puntos: la cabecera, el payload y la firma. Los dos primeros son JSON compacto en base64url, y el tercero es la salida binaria de un hash sobre el texto exacto de los dos primeros. Aquí tienes un token HS256 completo construido en PowerShell, de principio a fin:

$header = @{ alg = "HS256"; typ = "JWT" } | ConvertTo-Json -Compress
$payload = @{ sub = "1234567890"; name = "John Doe"; iat = 1516239022 } | ConvertTo-Json -Compress
function UrlEncode64([byte[]]$bytes) {
  $standard = [System.Convert]::ToBase64String($bytes).TrimEnd("=")
  return $standard.Replace("+", "-").Replace("/", "_")
}
$left = (UrlEncode64 ([System.Text.Encoding]::UTF8.GetBytes($header))) + "." + (UrlEncode64 ([System.Text.Encoding]::UTF8.GetBytes($payload)))
$hmac = [System.Security.Cryptography.HMACSHA256]::new([System.Text.Encoding]::UTF8.GetBytes("secret"))
$signature = UrlEncode64 ($hmac.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($left)))
$jwt = $left + "." + $signature
# eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1MTYyMzkwMjIsInN1YiI6IjEyMzQ1Njc4OTAiLCJuYW1lIjoiSm9obiBEb2UifQ.6MWZy9doHbfyomJd4soTRUQft7PmRM2EyxxT3SLoiyE
#  en PowerShell 7.4; el orden interno de claves de los segmentos - y por tanto la firma - puede variar según la versión

Mira el segmento de la firma: una ristra del alfabeto base64url, aquel donde el plus aparece como guion y la barra como guion bajo. Tres cosas de ese ejemplo te salvarán de incidentes de producción. Primera, la firma se calcula sobre el texto JSON exacto, incluido su orden de claves y sus espacios, así que el JSON que firmas y el JSON contra el que verificas deben ser idénticos byte a byte. El ConvertTo-Json de PowerShell decide el orden de claves por ti, y no es algo que controles, así que no reordenes a mano los segmentos de un token ni los reformatees entre firmar y comprobar. Segunda, -Compress no es cosmético: un token cuya cabecera o payload contenga un solo espacio es un token que nunca verificará contra una implementación conforme, porque la forma estándar es compacta. Tercera, el timestamp iat es segundos desde el epoch de Unix, y un payload construido desde Get-Date sin convertir estará fuera de rango por años. La dirección de decodificación, asomarse a un token que acuñó otro, se cubre en el artículo relacionado del sitio hermano.

Archivos y el flujo de bytes

Los archivos son el payload más común de todos, y la tubería es corta. Lee el archivo como bytes, codifica, escribe texto. Las dos líneas que importan son la lectura, que debe ser una lectura de bytes, y la escritura, que normalmente no debe añadir un salto de línea al final:

$bytes = [System.IO.File]::ReadAllBytes("./photo.png")
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path "./photo.b64" -Value $encoded -NoNewline
$encoded.Length
# el tamaño de texto que estás a punto de enviar

Dos notas prácticas. La primera es aritmética: Base64 lo hace todo más grande, y para un archivo de 10 megabytes el texto que envías son aproximadamente 13,4 megabytes. Si el transporte tiene un límite de tamaño, o si este texto va a parar al cuerpo de un correo o a una URL, haz las cuentas antes de codificar, no después del error. La segunda es el salto de línea final: Set-Content añade uno por defecto, y aunque el decodificador que usa este sitio y la mayoría de los decodificadores modernos lo ignoran, algunos consumidores estrictos no. -NoNewline no te cuesta nada y elimina la duda.

PowerShell 6 y los más nuevos ofrecen una segunda lectura que se queda en el lenguaje: Get-Content -AsByteStream -Raw devuelve el archivo como un único array de bytes en una llamada, que es una alternativa ordenada al ReadAllBytes de .NET y se comporta igual para este propósito. En Windows PowerShell 5.1, que no tiene -AsByteStream, la lectura de .NET es la única opción, y es la que se comporta igual en cada versión de la shell.

Certificados: de PEM y PFX al texto

Los certificados son los ciudadanos de la codificación más pesados en las operaciones de todos los días, porque los despliegues adoran llevarlos como texto. Un certificado PEM es un cuerpo Base64 envuelto entre líneas de blindaje, y la receta de la sección de envolvimiento es la exportación entera:

$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([System.IO.File]::ReadAllBytes("./certificate.der"))
$cert.Subject
# CN=example.org
$b64 = [System.Convert]::ToBase64String($cert.RawData)
# una línea larga de la forma binaria del certificado

El formato PFX es el otro caballo de batalla: un único archivo binario que guarda el certificado junto con su clave privada, y por eso es el formato que más a menudo ves rondando como texto Base64 dentro de scripts de despliegue y almacenes de configuración. Codificar uno es la tubería de archivo plana de la sección anterior, y la dirección de lectura en PowerShell 7 es cosa de un cmdlet:

$pfxBytes = [System.IO.File]::ReadAllBytes("./certificate.pfx")
$pfxB64 = [System.Convert]::ToBase64String($pfxBytes)
# la forma de texto del paquete, lista para un archivo de configuración
Get-PfxCertificate -FilePath "./certificate.pfx" -Password (ConvertTo-SecureString "secret" -AsPlainText -Force)
# el certificado vivo, sin decodificación manual

Una frase de seguridad, dicha sin rodeos porque Base64 invita a la suposición contraria: un PFX en Base64 es una clave privada en texto. La codificación cambia la forma del secreto y nada de su secrecía, así que un PFX Base64 pegado en una ventana de chat, un ticket o un commit es una clave privada pegada en una ventana de chat, un ticket o un commit. Trata la forma de texto con exactamente el mismo cuidado que la forma binaria, y prefiere el almacén de certificados o un gestor de secretos por encima de ambas.

Basic Auth, data URIs y las viejas costumbres

Base64 es más antiguo que el documento de estándar que lo nombró. La familia MIME de RFCs de 1996 lo metió en el correo, y la autenticación HTTP Basic lo metió en cada intercambio de cabeceras de los inicios de la web, donde el cliente todavía codifica la pareja de credenciales como un único string Base64:

$credential = [System.Text.Encoding]::UTF8.GetBytes("alice:s3cret!")
[System.Convert]::ToBase64String($credential)
# YWxpY2U6czNjcmV0IQ==
# enviado como: Authorization: Basic YWxpY2U6czNjcmV0IQ==

El alfabeto estándar es el correcto aquí, plus y barra incluidos, porque una cabecera no es una URL y no necesita el alfabeto seguro. El mismo mecanismo aparece en los data URIs, la manera en que un documento incrusta su propio binario en línea, y la forma es un prefijo literal más el Base64 estándar de los bytes:

$dataUri = "data:application/octet-stream;base64," + [System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# data:application/octet-stream;base64,AQID+vs=

Ambas costumbres valen la pena conocerlas menos como cosas que vas a construir y más como cosas que vas a encontrar: cuando una cabecera o un enlace contiene una ristra larga de Base64, estos dos formatos son los primeros que hay que comprobar, y ambos están a una única decodificación simple de lo que dicen. Que es el punto del formato, y la razón por la que existe el lado de decodificación de este sitio.

Comandos codificados y la caja de herramientas de Windows

PowerShell lleva un motivo integrado para codificar 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 corre como comando. El propósito documentado son los comandos que pelean con las comillas de la shell exterior, y el lado de la codificación son dos líneas:

$command = "Write-Host 'Hello from the encoded side'"
$encoded = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($command))
# VwByAGkAdABlAC0ASABvAHMAdAAgACcASABlAGwAbABvACAAZgByAG8AbQAgAHQAaABlACAAZQBuAGMAbwBkAGUAZAAgAHMAaQBkAGUAJwA=
pwsh -NoProfile -EncodedCommand $encoded
# Hello from the encoded side

Lee la línea de codificación con cuidado, porque es la que todo el mundo falla: el payload debe ser UTF-16LE, es decir la codificación Unicode, no UTF-8. Codifica con la equivocada y el host decodifica tus bytes como UTF-16LE de todas formas y ejecuta un comando hecho de mojibake, produciendo un error que es un retrato perfecto del fallo. El artículo de decodificación cubre ese fallo a fondo, y la solución en este lado es una sola palabra: Unicode.

Fuera del lenguaje, las herramientas nativas cada una lleva su propia decisión de codificación callada. En Windows, certutil -encode infile outfile.b64 produce un archivo Base64 estándar con las líneas de blindaje que PEM espera, -f sobrescribe una salida existente, y el flag que vale la pena recordar es -unicodetext, que hace que certutil escriba el archivo de salida en Unicode (según la documentación de Microsoft: "Escribe el archivo de salida en Unicode") - un solo conmutador que esconde una decisión de codificación. En Linux la utilidad clásica es base64 -w 0 file, donde el -w 0 es la parte que carga el peso: sin él, el base64 de GNU envuelve a los 76 caracteres y te entrega un archivo de estilo MIME cuando querías una línea. En macOS el sabor BSD no necesita tal flag, porque emite una línea continua sin cortarla por defecto.

Codificar cuando la salida es enorme

Para tamaños de todos los días, la tubería de leer-todo-codificar-todo es la rápida y simple, y es la correcta hasta que el archivo sea demasiado grande para guardarlo en memoria con comodidad o los datos lleguen a trozos desde una descarga o un socket. Entonces la herramienta documentada es la pareja de streaming: System.Security.Cryptography.ToBase64Transform envuelta en un CryptoStream, donde escribes bytes crudos por dentro y sale texto Base64 por fuera, con solo un buffer pequeño vivo en cada momento:

$source = [System.IO.File]::OpenRead("./photo.png")
$destination = [System.IO.File]::Create("./photo.b64")
$transform = [System.Security.Cryptography.ToBase64Transform]::new()
$stream = [System.Security.Cryptography.CryptoStream]::new($destination, $transform, [System.Security.Cryptography.CryptoStreamMode]::Write)
$buffer = New-Object byte[] 65536
while (($read = $source.Read($buffer, 0, $buffer.Length)) -gt 0) {
  $stream.Write($buffer, 0, $read)
}
$stream.Dispose()
$source.Dispose()
$destination.Dispose()

Una diferencia con el método de una sola pasada vale la pena anotarla: el stream produce una línea continua sin envolvimiento alguno, por grande que sea la entrada. Un decodificador que ignora el espacio en blanco no le importa, pero si el destino final es un archivo PEM, ejecuta después el bucle de 64 columnas de la sección de envolvimiento sobre el resultado. Y en C# la forma estándar es el mismo patrón ToBase64Transform + CryptoStream que ves en el artículo de C#; PowerShell lo dirige directamente, como arriba.

Dónde se equivocan los payloads codificados

  • Codificar el string, no los bytes. ToBase64String("Hello") lanza una excepción de conversión, que es el método diciéndote que la primera decisión, la codificación, no se ha tomado. Hazla visible en el script y el error desaparece.
  • UTF-16 donde se prometía UTF-8. El payload es el doble de largo de lo esperado y el consumidor lee basura. La codificación es parte del protocolo, y para casi todo cable del internet moderno el protocolo dice UTF-8.
  • La lectura de 5.1. Windows PowerShell 5.1 lee un archivo de texto sin BOM con la página de código ANSI de la máquina antes de que tu script lo vea, así que un archivo fuente UTF-8 puede corromperse antes del paso de codificación. En 5.1, lee el texto con una lectura UTF-8 explícita y comprueba los primeros caracteres del resultado.
  • El ancho de envolvimiento equivocado. MIME quiere 76, PEM quiere 64, las APIs quieren ninguno, y un consumidor estricto trata un salto de línea inesperado como un carácter extranjero. Elige el ancho según el consumidor y dilo en un comentario.
  • Relleno en el lado equivocado del intercambio. Tirar los signos de igualdad es correcto para tokens base64url y equivocado para un consumidor que espera relleno estándar. El intercambio de alfabeto y la decisión de relleno son dos elecciones, no una.
  • Reformatear lo que firmaste. La firma de un JWT cubre el texto JSON exacto, incluido el orden de claves y los espacios. Reordena las claims o añade un espacio y el token deja de verificar, sin ningún mensaje de error cerca de la causa.
  • Valores por encima de 255. Convertir un entero a byte lanza en vez de dar la vuelta, así que 256 detiene el script en el casteo. Si tus datos de origen son números, castéa explícitamente y deja que un error sea un error que ves.
  • Creerle al disfraz. Base64 no es cifrado ni compresión: es una traducción que hace crecer los datos un tercio. Un secreto en Base64 es un secreto en texto plano, y un archivo en Base64 es un archivo que necesita un 33% más de espacio.

Reglas para codificadores en los que puedes confiar

  • Produce los bytes con intención. La primera línea de cualquier script de codificación debe ser un GetBytes explícito o una lectura de bytes, nunca la esperanza de que PowerShell convierta un string en los bytes correctos.
  • Nombra el alfabeto y el ancho en un comentario junto al código que toma la elección: estándar o base64url, envuelto a 76, 64 o en absoluto. El consumidor es una persona que lee el script en seis meses, y esa persona eres tú.
  • Escribe el archivo de texto con -NoNewline a menos que el consumidor espere específicamente un salto final, y elige el fin de línea (LF o CRLF) como lo espera la documentación del consumidor.
  • Prueba el viaje de ida y vuelta mientras construyes: codifica, decodifica, compara los bytes. Un Compare-Object de treinta segundos sobre los dos arrays de bytes atrapa errores de codificación, de envolvimiento y de orden de bytes todos a la vez, mientras la causa todavía está fresca.
  • Registra tamaños, no payloads. El recuento de bytes antes y el recuento de caracteres después deberían quedar en una proporción de unos 1.33, y cuando no, la discrepancia de tamaño te dice dónde mirar sin que el log contenga nunca los datos.

Cómo PowerShell heredó su codificador

La historia verdadera más corta de la codificación Base64 en PowerShell es que PowerShell nunca escribió uno. El método que llamas, Convert.ToBase64String, 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 el momento del lanzamiento, el codificador de .NET, que envuelve ya tenía tres años y llevaba tráfico web.

El formato se estandarizó el mismo año en que la shell salió. RFC 4648, publicado en octubre de 2006, fijó el alfabeto, las reglas de relleno, la estrictez de la decodificación y la variante base64url, y todavía describe exactamente el comportamiento que la pareja de .NET implementa. Los RFC MIME que vinieron antes, en 1996, ya habían metido el envolvimiento de 76 caracteres en el correo, que es por qué ese ancho sigue siendo el valor por defecto de InsertLineBreaks hasta el día de hoy. Cuando PowerShell se hizo de código abierto y multiplataforma en agosto de 2016 como PowerShell Core, el codificador vino con todo a Linux y macOS sin cambios, porque no había nada que cambiar.

Lo que sí cambió después ocurrió en .NET, y en su mayoría fuera del alcance de PowerShell. El runtime ganó ayudantes de Base64 más rápidos y basados en spans en versiones recientes, incluida la clase Base64Url y los métodos de decodificación con prefijo Try. Los spans son tipos parecidos a byref, y las versiones antiguas de PowerShell de verdad no podían enlazarse a ellos en absoluto, pero el enlazador de métodos del PowerShell actual ahora realiza una conversión implícita de array a span, así que estos atajos se pueden llamar desde un script en un host lo bastante reciente. La respuesta de la comunidad para todo lo más viejo es el módulo Microsoft.PowerShell.TextUtility de la PowerShell Gallery, cuyo ConvertTo-Base64 envuelve el mismo método de .NET y añade un parámetro -Text con valor por defecto UTF-8 y un conmutador -InsertBreakLines para el envolvimiento de 76 columnas. Instálalo con Install-Module -Name Microsoft.PowerShell.TextUtility si prefieres la forma de cmdlet, y ten en cuenta que el módulo está archivado y ya no se mantiene activamente, que es una razón más para que el método integrado siga siendo la recomendación para scripts nuevos.

Los números y nombres que conviene guardar

  • Cada tres bytes de entrada se convierten en cuatro caracteres de salida, así que los datos codificados salen unos 33% más grandes que el original, y ningún relleno es nunca más de dos signos de igualdad.
  • La salida por defecto es una línea continua sin cortarla. InsertLineBreaks envuelve a los 76 caracteres con CRLF, la convención MIME de 1996. PEM quiere 64, y ningún flag integrado produce ese ancho.
  • base64url es el Base64 estándar con el plus y la barra intercambiados por guion y guion bajo, el relleno normalmente tirado, y es el alfabeto de cada JWT y token de API.
  • "Café" son cinco bytes en UTF-8 y ocho en UTF-16LE. Mismo texto visible, el doble de tamaño, y las dos codificaciones no son intercambiables a través del cable.
  • -EncodedCommand existe desde el primer lanzamiento de PowerShell, y su payload debe ser UTF-16LE, no UTF-8. La palabra única que arregla el error más común en este lado es Unicode.
  • certutil -encode puede esconder una decisión de codificación dentro de -unicodetext, y el base64 de GNU necesita -w 0 para darte una línea en vez del envolvimiento de 76 columnas.
  • Los ayudantes de Base64 de .NET basados en spans, incluida Base64Url, fueron alguna vez inalcanzables desde PowerShell porque los spans son tipos parecidos a byref a los que el enlazador de métodos más antiguo no podía enlazarse. El PowerShell actual (7.4 o posterior, sobre un runtime de .NET lo bastante nuevo como para traer la clase) resuelve un argumento de array contra un parámetro de span sin quejarse, así que la llamada directa funciona hoy - pero el intercambio de dos caracteres sigue siendo la única receta que funciona en cada versión, vieja y nueva por igual.
  • Un solo byte, 123, se codifica como ew==: el ejemplo más pequeño posible de la regla de que la longitud de la salida te dice la longitud de la entrada.

Girando la flecha

Todo en este artículo va de tomar los datos que tienes y convertirlos en un string Base64. La operación espejo, tomar un string y recuperar tus datos, tiene su propio elenco de problemas: un decodificador que ignora cuatro tipos de espacio en blanco, un mensaje de error que cubre tres crímenes, un JWT al que asomarse, un certificado al que desenrollar, y un -EncodedCommand al que explicar. 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, Decodificación Base64 en PowerShell, enlazado abajo.

Última actualización: 2026-09-08

Artículo relacionado: Decodificación Base64 en PowerShell: una guía completa