Приходится иметь дело с форматом Base64? Тогда этот сайт идеально вам подойдет! Воспользуйтесь нашим невероятно удобным онлайн-инструментом для кодирования или декодирования ваших данных.

Кодирование Base64 в PowerShell: полное руководство

У вас есть строка, файл, сертификат или токен, и другая сторона провода хочет получить это длинным рядом букв и цифр: печатаемым, вставляемым в письмо, URL или конфиг-файл, без единого байта бинарного, который сломал бы транспорт. Это Base64. Это перевод, а не сжатие и не замок: три входных байта становятся четырьмя выходными символами, так что текст, который вы отправляете, примерно на 33% крупнее того, с чего всё началось, при алфавите из 64 символов плюс знак равенства в качестве завершающего заполнения.

Главная страница этого сайта подробно разбирает алфавит, побитовую арифметику и варианты формата. Эта статья разбирает направление кодирования со стороны PowerShell: тот единственный метод .NET, который вы вызовете, недостающий шаг, на котором спотыкаются все в первом скрипте, конвенции переноса строк, которые отличаются от протокола к протоколу, URL-безопасный алфавит, и горстка реальных задач, где кодирование в PowerShell вознаграждает внимательных и наказывает небрежных.

Метод и недостающий шаг

PowerShell не имеет собственного cmdlet для Base64. Работу выполняет метод, который входит в платформу .NET ещё с .NET Framework 1.1, выпущенного в 2003 году, за три года до выхода самого PowerShell:

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

Вот и весь API: байтовый массив на входе, строка на выходе, в любом PowerShell на любой операционной системе, потому что это просто .NET. Недостающий шаг - это первая строка примера, и именно здесь новички теряют свой первый час. Метод не принимает вашу строку. Он принимает байты, и вопрос «какие байты означает моя строка» - это вопрос о кодировке, на который можете ответить только вы. Вот контракт перегрузок, которые вы действительно можете вызвать из PowerShell:

Что вы передаёте Что вы получаете
byte[] Одна длинная строка стандартного Base64, с заполнением = там, где этого требует длина
byte[] плюс InsertLineBreaks Те же данные, разбитые на строки по 76 символов с CRLF между строками
byte[], offset, count Только запрошенный срез массива, закодированный
Строка, например "Hello" Исключение о преобразовании. PowerShell не умеет сам превращать строку в байтовый массив
$null ArgumentNullException, обёрнутый для вас в MethodInvocationException

Обратите внимание, чего нет в этой таблице: нет перегрузки, которая говорила бы «закодируй этот текст». Кодирование текста в PowerShell - всегда двухшаговый процесс. Вы решаете кодировку, вы производите байты, и только тогда метод Base64 вступает в разговор. Держите эти два решения в скрипте явно разделёнными, потому что второе невидимо, а первое - то место, где живут ошибки.

Кодирование текста: сначала выберите кодировку

Безопасное значение по умолчанию для всего, что пересекает современный интернет, - UTF-8. Веб-API, JSON, JWT, всё, что за последние десятилетие записал браузер или сервер, будет ожидать под Base64 байты в UTF-8, и двухшаговая схема - привычка, которую стоит вырастить:

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

Когда вы берётесь за другую кодировку, вы обычно обслуживаете легаси-систему, и таблица ниже - практичный ориентир:

Кодировка Когда использовать Если выбрать не ту
UTF8 Веб-API, JSON, JWT, всё современное. Выбор по умолчанию Декодер на той стороне увидит кракозябры вместо вашего текста
Unicode (UTF-16LE) Потребитель - это Windows- или .NET-компонент, кодирующий строки .NET, либо -EncodedCommand Ваши данные вдвое длиннее, чем ожидает потребитель, и полны сюрпризов
ASCII Классические 7-битные протоколы, такие как учётные данные HTTP Basic Всё, что выше значения 127, заменяется ещё до начала кодирования
Latin1 Легаси-европейские системы, старше UTF-8 Один байт на символ, и каждый символ вне Latin-1 становится вопросительным знаком

Полезный трюк для отладки работает в обе стороны: заполнение и длина Base64 говорят вам, сколько байтов было закодировано, а внешний вид декодированного текста говорит, из какого мира - двухбайтного или однобайтного - он пришёл. Данные подозрительно чётной длины, усеянные чередованием обычных и похожих на пробелы символов, - это обычно UTF-16 в костюме UTF-8, или наоборот.

UTF-16 сюрприз

PowerShell внутренне хранит строки в UTF-16, и этот факт протёкает в работу с Base64 в одном конкретном, очень частом месте: вы пишете Base64 для потребителя, который сам является компонентом .NET или Windows, и кодируете в Unicode, потому что именно так устроены строки .NET. Для некоторых потребителей это верная интуиция, а для всех остальных - ошибка, удваивающая размер. Одни и те же четыре видимых символа, две кодировки:

$same = "Café"
[System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($same))
# Q2Fmw6k=  пять байтов
[System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes($same))
# QwBhAGYA6QA=  восемь байтов

Тот же текст, размер вдвое больше, и две строки не взаимозаменяемы: потребитель, ожидающий одну и получающий другую, не упадёт с громким криком; он просто прочитает мусор. Правило, которое не даст этому вас укусить, - считать кодировку частью протокола, а не локальной деталью. Если принимающая система - браузер, REST API или современный сервер, это UTF-8, пока документация не говорит обратного. Если это сам хост PowerShell через -EncodedCommand, или строка .NET в конвейере, живущем только в Windows, это UTF-16LE. Когда протокол молчит, спросите другую сторону, с чем она вызовет GetString, потому что именно этот вопрос и решает всё.

Числа, байты и всё остальное

Метод объявлен как принимающий байтовый массив, но преобразование типов в PowerShell снисходительно к тому, что считается массивом, и знание краёв спасает вас от сюрпризов:

[System.Convert]::ToBase64String([byte[]](1, 2, 3, 250, 251))
# AQID+vs=
[System.Convert]::ToBase64String([char[]]"Café")
# Q2Fm6Q==  один байт на символ, числовое значение символа
[System.Convert]::ToBase64String([int[]](72, 101, 108, 108, 111))
# SGVsbG8=
[System.Convert]::ToBase64String(123)
# ew==  одиночное число принимается там, где ожидается целый массив

Два края в этом блоке заслуживают внимания. Массивы символов конвертируются по одному байту на символ, используя числовое значение символа; для латиницы это ровно то, чего ждут легаси-системы, использующие этот приём, а для всего, что дальше, молча производятся неверные байты. Целое число больше 255 - это край, который вместо этого падает громко: байтовое преобразование PowerShell отказывается от значений вне 0-255 с исключением, так что 256 останавливает скрипт на приведении типа, вместо того чтобы тихо испортить ваши данные. Если ваш источник - числа, делайте приведение явным: [byte[]](1, 2, 3) говорит ровно то, что значит.

Передайте методу строку - и вы получите то же громкое обращение по другой причине: невозможно узнать, какие байты означает строка, и движок преобразований PowerShell сдаётся. Передайте $null - и .NET выбросит исключение ещё до начала работы. И то, и другое - корректное поведение, и оба факта - причина, по которой двухшаговая схема из первого раздела - единственная схема, которой стоит пользоваться.

Перенос строк: 76, 64 и без переноса

По умолчанию кодировщик выдаёт одну длинную строку, сколько бы данных вы ему ни дали. Для файла в несколько килобайт это нормально. Для данных, которые будет читать человек, вставлять в письмо или сравнивать в диффе системы контроля версий, стена из восьми миллионов символов - практическая проблема, и конвенция - складывать строки. PowerShell и протоколы, которые он обслуживает, знают три ширины, и они не взаимозаменяемы:

Ширина Кто этого ожидает Окончание строки
76 символов MIME, почта и большинство текстовых транспортов. Значение по умолчанию у InsertLineBreaks CRLF
64 символа Файлы PEM: сертификаты, закрытые ключи и остальная семья -----BEGIN Конвенционно LF
Без переноса API, токены, конфиг-файлы, всё, где данные обрабатываются машиной Строк вообще нет

Встроенный перенос - это изменение одного параметра, и именно он нужен для данных почтового стиля:

$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 символов на строку, CRLF между ними, ровно как ожидает MIME

PEM - исключение из встроенного переноса, потому что OpenSSL и вся экосистема -----BEGIN переносит по 64 символа, и ни один флаг .NET не выдаёт такую ширину. Цикл короткий, и это стандартный рецепт:

$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")

Причина, по которой ширина вообще имеет значение, в том, что группы из четырёх символов Base64 не уважают переводы строк, поэтому декодер может либо полностью игнорировать переносы, либо строго их требовать. Декодер, который использует этот сайт, игнорирует их, но строгие потребители, а их немало в мирах сертификатов и почты, считают непредвиденный перенос чужим символом и отклоняют данные. Когда вы выбираете ширину, вы заключаете контракт с потребителем, и в скрипте стоит комментарий, в котором названо, с кем именно вы заключаете этот контракт.

base64url: два символа и решение о заполнении

Плюс и слэш стандартного Base64 законны внутри URL только после процентного кодирования, а заполнение из знаков равенства читается как разделитель полей. Поэтому RFC 4648 определил алфавит, безопасный для URL и имён файлов: те же 64 символа, только плюс становится дефисом, а слэш - подчёркиванием, а заполнение обычно отбрасывают, потому что длина данных делает его ненужным. Каждый API-токен и JWT, с которыми вам доводилось иметь дело, записаны в этом варианте, который стандарт настоятельно требует называть base64url, а не просто base64.

Стандартный кодировщик PowerShell выдаёт стандартный алфавит, поэтому преобразование в base64url - это два обмена символов и решение о заполнении:

$bytes = [System.Text.Encoding]::UTF8.GetBytes("Париж encoded 大阪")
$standard = [System.Convert]::ToBase64String($bytes)
$standard
# 0J/QsNGA0LjQtiBlbmNvZGVkIOWkp+mYqg==  стандартный алфавит, включая заполнение
$url = $standard.Replace("+", "-").Replace("/", "_").TrimEnd("=")
$url
# 0J_QsNGA0LjQtiBlbmNvZGVkIOWkp-mYqg  URL-безопасная форма, заполнение удалено

Удаление заполнения безопасно в мире base64url, потому что потребитель пересчитывает, каким было бы заполнение, по длине строки. Хотя это не так везде, поэтому принимайте решение явно: отбрасывайте заполнение для токенов, сегментов JWT и встраивания в URL, оставляйте его для всего, что питает строгого потребителя стандартного алфавита, и записывайте, что вы выбрали. Рантайм .NET действительно поставляется со специализированным классом для этого алфавита, System.Buffers.Text.Base64Url (добавлен в .NET 9), с методами, построенными вокруг параметров ReadOnlySpan<T>. Актуальный PowerShell (7.4 и новее, когда он работает на версии .NET, где этот класс уже есть) на самом деле может вызывать их напрямую - [System.Buffers.Text.Base64Url]::EncodeToString($bytes) работает сегодня, потому что связыватель методов теперь неявно преобразует аргумент-массив в span, - но именно к двухсимвольному обмену стоит обращаться, когда скрипту нужно работать на Windows PowerShell 5.1, на старом выпуске PowerShell 7.x или на хосте с рантаймом до .NET 9, и он работает во всех этих версиях.

Чеканка JWT

JSON Web Token - самое знаменитое реальное применение base64url, и к тому же это хороший полный тест конвейера кодирования, потому что JWT - это три закодированных сегмента, склеенных точками: заголовок, данные и подпись. Первые два - компактный JSON в base64url, а третий - бинарный результат хеша от точного текста первых двух. Вот полноценный HS256-токен, собранный в PowerShell, от начала до конца:

$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
#  на PowerShell 7.4; внутренний порядок ключей в сегментах - а значит и подпись - может отличаться в зависимости от версии

Посмотрите на сегмент подписи: ряд из алфавита base64url, того самого, где плюс показался бы дефисом, а слэш - подчёркиванием. Три вещи в этом примере спасут вас от производственных инцидентов. Во-первых, подпись вычисляется от точного JSON-текста, включая порядок ключей и пробелы, поэтому JSON, который вы подписываете, и JSON, с которым вы проверяете, должны быть идентичны побайтно. ConvertTo-Json в PowerShell решает порядок ключей за вас, и это то, что вы не контролируете, поэтому не переставляйте сегменты токена вручную и не переформатируйте их между подписью и проверкой. Во-вторых, -Compress - это не косметика: токен, в заголовке или данных которого есть хотя бы один пробел, никогда не пройдёт проверку в совместимой реализации, потому что стандартная форма - компактная. В-третьих, метка времени iat - это секунды с эпохи Unix, и данные, собранные из Get-Date без преобразования, будут мимо диапазона на годы. Направление декодирования - подглядывание в токен, который чеканит кто-то другой, - разбирается в связанной статье на сестринском сайте.

Файлы и байтовый поток

Файлы - самый частый вид данных из всех, и конвейер короткий. Прочитайте файл как байты, закодируйте, запишите текст. Важны две строки: чтение, которое должно быть байтовым, и запись, которая обычно не должна добавлять завершающий перевод строки:

$bytes = [System.IO.File]::ReadAllBytes("./photo.png")
$encoded = [System.Convert]::ToBase64String($bytes)
Set-Content -Path "./photo.b64" -Value $encoded -NoNewline
$encoded.Length
# размер текста, который вы собираетесь отправить

Два практических замечания. Первое - арифметика: Base64 делает всё больше, и для файла в 10 мегабайт отправляемый текст составит примерно 13,4 мегабайта. Если у транспорта есть предел размера, или если этот текст полетит в тело письма или в URL, сделайте арифметику до кодирования, а не после ошибки. Второе - завершающий перевод строки: Set-Content добавляет его по умолчанию, и хотя декодер, который использует этот сайт, и большинство современных декодеров его игнорируют, некоторые строгие потребители - нет. -NoNewline ничего вам не стоит и снимает вопрос.

PowerShell 6 и новее предлагают второе чтение, которое остаётся в пределах языка: Get-Content -AsByteStream -Raw возвращает файл единым байтовым массивом за один вызов, что является аккуратной альтернативой .NET-методу ReadAllBytes и для этой цели ведёт себя одинаково. На Windows PowerShell 5.1, где нет -AsByteStream, .NET-чтение - единственный вариант, и именно он ведёт себя одинаково во всех версиях оболочки.

Сертификаты: из PEM и PFX в текст

Сертификаты - самые тяжёлые граждане в повседневных операциях, потому что развёртывания любят возить их текстом. PEM-сертификат - это сложенное тело Base64 между строками брони, и рецепт из раздела о переносе - это и есть весь экспорт:

$cert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new([System.IO.File]::ReadAllBytes("./certificate.der"))
$cert.Subject
# CN=example.org
$b64 = [System.Convert]::ToBase64String($cert.RawData)
# одна длинная строка бинарной формы сертификата

Формат PFX - другая рабочая лошадка: один бинарный файл, держащий сертификат вместе с его закрытым ключом, и именно поэтому это тот формат, который вы чаще всего находите, лежащим Base64-текстом в скриптах развёртывания и хранилищах конфигураций. Кодирование - это тот же файловый конвейер из предыдущего раздела, а направление чтения в PowerShell 7 - дело одного cmdlet:

$pfxBytes = [System.IO.File]::ReadAllBytes("./certificate.pfx")
$pfxB64 = [System.Convert]::ToBase64String($pfxBytes)
# текстовая форма пакета, готовая для конфиг-файла
Get-PfxCertificate -FilePath "./certificate.pfx" -Password (ConvertTo-SecureString "secret" -AsPlainText -Force)
# живой сертификат, ручное декодирование не нужно

Одно предложение про безопасность, сказанное прямо, потому что Base64 поощряет противоположное допущение: PFX в Base64 - это закрытый ключ в тексте. Кодирование меняет форму секрета и ничего не меняет в его секретности, поэтому PFX в Base64, вставленный в чат, тикет или коммит, - это закрытый ключ, вставленный в чат, тикет или коммит. Относитесь к текстовой форме с тем же вниманием, что и к бинарной, и выбирайте хранилище сертификатов или менеджер секретов вместо той или другой.

Basic Auth, data-URI и старые привычки

Base64 старше стандарта, который дал ему имя. Семейство RFC о MIME 1996 года поместило его в почту, а HTTP Basic-аутентификация - в каждый обмен заголовками раннего веба, где клиент до сих пор кодирует пару учётных данных одной Base64-строкой:

$credential = [System.Text.Encoding]::UTF8.GetBytes("alice:s3cret!")
[System.Convert]::ToBase64String($credential)
# YWxpY2U6czNjcmV0IQ==
# отправляется как: Authorization: Basic YWxpY2U6czNjcmV0IQ==

Здесь правильный алфавит - стандартный, с плюсом и слэшем, потому что заголовок - не URL и ему не нужен безопасный алфавит. Тот же механизм появляется в data-URI, способе, которым документ встраивает собственный бинарный контент прямо внутрь себя, и форма такова: литеральный префикс плюс стандартный Base64 байтов:

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

Обе привычки стоит знать меньше как то, что вы будете строить, а больше как то, с чем вы встретитесь: когда в заголовке или ссылке содержится длинный хвост Base64, эти два формата - первые, которые стоит проверить, и оба находятся в одном обычном декодировании от того, что они говорят. В этом и суть формата, и причина, по которой существует декодирующая половина этого сайта.

Закодированные команды и коробка инструментов Windows

У PowerShell есть встроенная причина кодировать с версии 1.0: параметр -EncodedCommand самого хоста. Вы передаёте pwsh Base64-строку, он декодирует байты как UTF-16LE, и результат выполняется как команда. Задокументированное назначение - команды, которые сражаются с кавычками внешней оболочки, а сторона кодирования - это две строки:

$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

Прочтите строку кодирования внимательно, потому что её все делают не так: данные должны быть в UTF-16LE, то есть в кодировке Unicode, а не UTF-8. Закодируйте неверной кодировкой - и хост всё равно декодирует ваши байты как UTF-16LE и выполнит команду из кракозябр, выдавая ошибку, которая является точным портретом этой ошибки. Статья про декодирование разбирает этот сбой полностью, а исправление на этой стороне - одно слово: Unicode.

Вне языка нативные инструменты каждый несут собственное тихое решение о кодировке. На Windows certutil -encode infile outfile.b64 производит файл стандартного Base64 со строками брони, которых ожидает PEM, -f перезаписывает существующий вывод, а флаг, который стоит запомнить, - -unicodetext, он заставляет certutil писать выходной файл в Unicode (согласно документации Microsoft: «пишет выходной файл в Unicode») - один переключатель, скрывающий решение о кодировке. На Linux классическая утилита - base64 -w 0 file, где -w 0 - несущая деталь: без него GNU base64 переносит по 76 символов и отдаёт вам файл в стиле MIME, когда вы хотели одну строку. На macOS вариант от BSD не нуждается в таком флаге, потому что по умолчанию он выдаёт одну непрерывную строку.

Кодирование, когда вывод огромный

Для повседневных размеров быстрый и простой конвейер - читать всё и кодировать всё; он остаётся правильным, пока файл не станет слишком большим, чтобы удобно держаться в памяти, или данные не начнут прибывать по кусочкам из загрузки или сокета. Тогда задокументированный инструмент - потоковая пара: System.Security.Cryptography.ToBase64Transform в обёртке CryptoStream, куда вы записываете сырые байты и откуда выходит Base64-текст, а живым в любой момент остаётся только небольшой буфер:

$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()

Одно отличие от одноразового метода стоит записать: поток производит одну непрерывную строку вообще без переносов, каким бы ни был большой ввод. Декодер, который игнорирует пробельные символы, не обратит на это внимания, но если конечный пункт назначения - файл PEM, прогоните результат потом через 64-колоночный цикл из раздела о переносе. А в C# стандартная форма - та же схема ToBase64Transform + CryptoStream, которую вы видите в C#-статье; PowerShell управляет ею напрямую, как показано выше.

Где закодированные данные ломаются

  • Кодирование строки, а не байтов. ToBase64String("Hello") бросает исключение о преобразовании, и это метод сообщает вам, что первое решение, кодировка, ещё не принято. Сделайте его видимым в скрипте - и ошибка исчезнет.
  • UTF-16 там, где обещали UTF-8. Данные вдвое длиннее ожидаемого, и потребитель читает мусор. Кодировка - часть протокола, и для почти любого провода современного интернета протокол говорит UTF-8.
  • Чтение в 5.1. Windows PowerShell 5.1 читает текстовый файл без BOM кодировкой ANSI машины до того, как ваш скрипт вообще его увидит, так что UTF-8-файл-источник может быть испорчен ещё до шага кодирования. На 5.1 читайте текст явным UTF-8-чтением и проверяйте первые символы результата.
  • Неверная ширина переноса. MIME хочет 76, PEM хочет 64, API хотят ничего, а строгий потребитель считает непредвиденный перевод строки чужим символом. Выбирайте ширину по потребителю и пишите об этом в комментарии.
  • Заполнение не с той стороны обмена. Отбрасывание знаков равенства - правильно для base64url-токенов и неправильно для потребителя, который ожидает стандартное заполнение. Обмен алфавита и решение о заполнении - это два выбора, а не один.
  • Переформатирование того, что вы подписали. Подпись JWT покрывает точный JSON-текст, включая порядок ключей и пробелы. Переставьте заявления или добавьте пробел - и токен перестанет проходить проверку, без сообщения об ошибке где-нибудь рядом с причиной.
  • Значения выше 255. Преобразование целого числа в байт бросает исключение вместо того, чтобы обернуться, так что 256 останавливает скрипт на приведении типа. Если ваши исходные данные - числа, приводите тип явно и пусть ошибка будет ошибкой, которую вы видите.
  • Вера в костюм. Base64 - не шифрование и не сжатие: это перевод, который увеличивает данные на треть. Секрет в Base64 - это секрет в простом тексте, а файл в Base64 - это файл, которому нужно на 33% больше места.

Правила для кодировщиков, которым можно доверять

  • Производите байты осознанно. Первая строка любого скрипта кодирования - явный GetBytes или байтовое чтение, никогда не надежда, что PowerShell сам превратит строку в нужные байты.
  • Называйте в комментарии рядом с кодом, который принимает выбор, алфавит и ширину: стандартный или base64url, перенос по 76, 64 или без переноса. Потребитель - это человек, который будет читать скрипт через полгода, и этот человек - вы.
  • Пишите текстовый файл с -NoNewline, если потребитель не ожидает явно завершающего перевода строки, и выбирайте окончание строки (LF или CRLF) так, как ожидает документация потребителя.
  • Тестируйте полный цикл, пока строите: закодируйте, декодируйте, сравните байты. Тридцатисекундный Compare-Object по двум байтовым массивам ловит ошибки кодирования, ошибки переноса и ошибки порядка байтов разом, пока причина ещё свежа.
  • Логируйте размеры, а не данные. Число байтов до и число символов после должны держаться в соотношении примерно 1,33, и когда это не так, рассогласование размеров подскажет, где искать, - без того чтобы в лог когда-либо попали сами данные.

Как PowerShell унаследовал свой кодировщик

Кратчайшая истинная история Base64-кодирования в PowerShell состоит в том, что PowerShell не писал его никогда. Метод, который вы вызываете, Convert.ToBase64String, вышел вместе с .NET Framework 1.1 в 2003 году, и каждый PowerShell начиная с версии 1.0 в ноябре 2006 года просто раскрывал .NET, на котором он работает. Пока проект строился, его звали Monad, впервые он был показан публично на Professional Developers Conference в октябре 2003 года, а к моменту выпуска кодировщик .NET, который он оборачивает, уже три года возил веб-трафик.

Формат был стандартизирован в тот же год, что и запуск оболочки. RFC 4648, опубликованный в октябре 2006 года, зафиксировал алфавит, правила заполнения, строгость декодирования и вариант base64url, и по сей день он описывает ровно то поведение, которое реализует пара .NET. MIME RFC, появившиеся раньше, в 1996 году, уже поместили 76-символьный перенос в почту, и именно поэтому эта ширина до сих пор - значение по умолчанию у InsertLineBreaks. Когда в августе 2016 года PowerShell стал открытым и кроссплатформенным как PowerShell Core, кодировщик приехал вместе с ним на Linux и macOS без изменений, потому что менять было нечего.

То, что изменилось позже, произошло в .NET, и в основном вне досягаемости PowerShell. В недавних версиях рантайм получил более быстрые span-основанные Base64-помощники, включая класс Base64Url и методы декодирования с префиксом Try. Span - это byref-подобные типы, и старые выпуски PowerShell действительно не могли привязываться к ним вообще, но связыватель методов в актуальном PowerShell теперь выполняет неявное преобразование массива в span, так что эти сокращения доступны из скрипта на достаточно свежем хосте. Ответ сообщества для всего более старого - модуль Microsoft.PowerShell.TextUtility из PowerShell Gallery, чей ConvertTo-Base64 оборачивает тот же метод .NET и добавляет параметр -Text с UTF-8 по умолчанию и переключатель -InsertBreakLines для 76-колоночного переноса. Установите его командой Install-Module -Name Microsoft.PowerShell.TextUtility, если вам нравится форма cmdlet, и заметьте, что модуль в архиве и больше не поддерживается активно - это ещё одна причина, по которой встроенный метод остаётся рекомендацией для новых скриптов.

Числа и имена, которые стоит запомнить

  • Каждые три входных байта становятся четырьмя выходными символами, так что закодированные данные примерно на 33% крупнее исходных, и заполнение никогда не бывает больше двух знаков равенства.
  • Вывод по умолчанию - одна непрерывная строка. InsertLineBreaks переносит по 76 символов с CRLF, MIME-конвенция с 1996 года. PEM хочет 64, и ни один встроенный флаг не выдаёт такую ширину.
  • base64url - это стандартный Base64, в котором плюс и слэш заменены на дефис и подчёркивание, заполнение обычно отброшено, и это алфавит каждого JWT и API-токена.
  • «Café» - это пять байтов в UTF-8 и восемь в UTF-16LE. Тот же видимый текст, размер вдвое больше, и две кодировки не взаимозаменяемы по проводу.
  • -EncodedCommand существует с первого же релиза PowerShell, и его данные должны быть в UTF-16LE, а не UTF-8. Единственное слово, которое чинит самую частую ошибку на этой стороне, - Unicode.
  • certutil -encode может спрятать решение о кодировке внутри -unicodetext, а GNU base64 нужен -w 0, чтобы дать вам одну строку вместо 76-колоночного переноса.
  • Span-основанные Base64-помощники .NET, включая Base64Url, когда-то были недоступны из PowerShell, потому что span - это byref-подобные типы, к которым старый связыватель методов не мог привязаться. Актуальный PowerShell (7.4+, на рантайме .NET достаточно новом, чтобы поставлять этот класс) привязывает аргумент-массив к span-параметру без единой жалобы, так что прямой вызов сегодня работает, - но двухсимвольный обмен остаётся тем единственным рецептом, который работает во всех версиях, старых и новых.
  • Одиночный байт, 123, кодируется как ew==: самый маленький возможный пример правила, что длина вывода говорит вам длину ввода.

Разворачиваем стрелку

Всё в этой статье - о том, как взять данные, которые у вас на руках, и превратить их в Base64-строку. Зеркальная операция - взять строку и вернуть свои данные, - имеет собственный состав проблем: декодер, который игнорирует четыре вида пробельных символов, одно сообщение об ошибке, покрывающее три преступления, JWT, в который можно подглядеть, сертификат, который нужно развернуть, и -EncodedCommand, который нужно объяснить. Тому направлению посвящён отдельный полный разбор, со своими ловушками и своей историей, в связанной статье на сестринском сайте, «Декодирование Base64 в PowerShell», на которую ссылка ниже.

Последнее обновление: 2026-09-08

Связанная статья: Декодирование Base64 в PowerShell: полное руководство