【PowerShell】Base64エンコード・デコードの方法|文字列・ファイル・EncodedCommandの罠

Base64は、テキストやバイナリデータを英数字と記号だけの文字列に変換する方式です。メールへの添付、設定ファイルへの埋め込み、APIでのデータ送信、画像のデータURI化など、さまざまな場面で使われます。PowerShellでは、.NETの[Convert]クラスを使ってBase64のエンコード・デコードができます。

つまずきやすいのは、文字列をそのままBase64にできない(いったんバイト列にする必要がある)こと、そして文字コード(UTF-8かShift-JISか)によってBase64の結果が変わることです。さらに、powershell.exe -EncodedCommandのBase64はUTF-16LEで作らないと動かないという独特の罠もあります。この記事では、実機のPowerShell 5.1で確認しながら、Base64の扱いを整理します。

先に結論

  • 文字列→Base64は[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s))です。
  • Base64→文字列は[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b64))です。
  • 文字列は直接変換できず、いったんバイト列(byte[])にする必要があります。
  • UTF-8とShift-JISで結果が変わります。エンコードと復号は同じ文字コードでそろえます。
  • ファイルは[IO.File]::ReadAllBytesでバイト列にしてからBase64化します。
  • powershell.exe -EncodedCommandはUTF-16LEで作る必要があります。

文字コードやエンコードの基礎は文字化けを直す方法(UTF-8・BOM)、ファイルの扱いはファイル・フォルダ操作、Web経由のデータ取得はWebからファイルをダウンロードするもあわせて参考になります。

スポンサーリンク

文字列をBase64エンコードする

文字列をBase64にするには、まず文字列をバイト列に変換し、それを[Convert]::ToBase64Stringに渡します。バイト列にするときの文字コードは、ここではUTF-8を使います。

文字列→Base64
$s = "Hello"

# 文字列 → UTF-8のバイト列 → Base64文字列
$bytes = [Text.Encoding]::UTF8.GetBytes($s)
$base64 = [Convert]::ToBase64String($bytes)

$base64    # SGVsbG8=

# 1行で書くなら
$base64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s))

実機でも、HelloをUTF-8のバイト列にしてBase64化するとSGVsbG8=になりました。ポイントは、[Convert]::ToBase64Stringは文字列ではなくバイト列(byte[])を受け取ることです。そのため、[Text.Encoding]::UTF8.GetBytes($s)で文字列をいったんバイト列に変換しています。文字列を直接渡そうとするとエラーになります。

Base64をデコードする

Base64文字列をもとに戻すには、逆の手順です。[Convert]::FromBase64Stringでバイト列に戻し、それをGetStringで文字列にします。エンコード時と同じ文字コードを使うのが重要です。

Base64→文字列
$base64 = "SGVsbG8="

# Base64文字列 → バイト列 → UTF-8で文字列に戻す
$bytes = [Convert]::FromBase64String($base64)
$text = [Text.Encoding]::UTF8.GetString($bytes)

$text    # Hello

# 1行で書くなら
$text = [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($base64))

実機でも、SGVsbG8=をデコードするとHelloに戻り、元の文字列と一致しました。エンコードでUTF-8を使ったなら、デコードでもUTF-8を使う——この対応が崩れると、次の章のように文字化けします。

UTF-8とShift-JISで結果が変わる

日本語などの非ASCII文字を含む場合、バイト列にするときの文字コードによってBase64の結果が変わります。これを意識しないと、デコード時に文字化けします。

エンコードで結果が変わる
$s = "あい"

# UTF-8でバイト列にした場合
[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s))
#  → 44GC44GE

# Shift-JIS(cp932)でバイト列にした場合
[Convert]::ToBase64String([Text.Encoding]::GetEncoding(932).GetBytes($s))
#  → gqCCog==

# 同じ「あい」でも、文字コードが違えば Base64 は別物になる
エンコードとデコードの文字コードをそろえる

実機で確認したところ、同じあいでも、UTF-8では44GC44GE、Shift-JISではgqCCog==と、まったく違うBase64になりました。さらに、Shift-JISで作ったBase64をUTF-8としてデコードすると、元のあいに戻らず文字化けしました。Base64自体は「バイト列を文字に変換するだけ」で文字コードの情報を持たないため、エンコードしたときと同じ文字コードでデコードする必要があります。相手とデータをやり取りするときは、どの文字コードでBase64化したか(通常はUTF-8)を合わせておくことが大切です。迷ったらUTF-8で統一するのが無難です。

ファイルをBase64にする・戻す

画像やバイナリファイルもBase64にできます。ファイルの場合は、[IO.File]::ReadAllBytesでファイル全体をバイト列として読み込み、それをBase64化します。文字コードは関係しません(バイトをそのまま扱うため)。

ファイル⇔Base64
# ファイル → Base64文字列
$base64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\temp\image.png"))

# Base64文字列 → ファイルに復元
[IO.File]::WriteAllBytes("C:\temp\restored.png", [Convert]::FromBase64String($base64))

# Base64をテキストファイルに保存しておくこともできる
$base64 | Set-Content "C:\temp\image.b64" 
ファイルはバイト単位で完全に復元できる

実機で、0〜255のすべてのバイトを含む256バイトのファイルをBase64化したところ、344文字のBase64になり(256バイトは約1.33倍の長さになります)、それを復元すると元のファイルとバイト単位で完全に一致しました。ファイルを扱うときは文字コードを気にする必要はありません。[IO.File]::ReadAllBytesがバイトをそのまま読み、[IO.File]::WriteAllBytesがそのまま書き戻すためです。画像をHTMLのデータURIに埋め込んだり、バイナリをテキストとして安全に受け渡したりするのに便利です。なお、Base64の文字列は長さが常に4の倍数になり、末尾が=で埋められることがあります。

【罠】-EncodedCommandはUTF-16LE

PowerShellには、スクリプトをBase64にしてpowershell.exe -EncodedCommandで実行する機能があります。クォートのエスケープを避けたいときに便利ですが、このBase64はUTF-8ではなくUTF-16LE(Unicode)で作る必要があります。ここを間違えると動きません。

-EncodedCommand は UTF-16LE で作る
$command = "Write-Output 123"

# OK: UTF-16LE(Unicode)でエンコードする
$encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.GetBytes($command))
powershell.exe -NoProfile -EncodedCommand $encoded
#  → 123(正しく実行される)

# NG: UTF-8 で作るとコマンドが壊れて実行できない
# $bad = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($command))
# powershell.exe -EncodedCommand $bad   → エラー
-EncodedCommandだけは特別にUTF-16LE

実機で確認したところ、[Text.Encoding]::Unicode(UTF-16LE)でBase64化したコマンドは-EncodedCommandで正しく実行され、123が出力されました。一方、同じコマンドを[Text.Encoding]::UTF8でBase64化して渡すと、コマンドが文字化けして実行エラーになりました。これは、-EncodedCommandがWindows内部の文字コードであるUTF-16LEを前提としているためです。通常のデータのBase64はUTF-8でよいのに、-EncodedCommandのときだけはUTF-16LE([Text.Encoding]::Unicodeを使う、という点に注意してください。間違えやすい有名な罠です。

主なポイントまとめ

PowerShellでのBase64の要点をまとめます。

やりたいこと 書き方
文字列→Base64 [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s))
Base64→文字列 [Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b))
ファイル→Base64 [Convert]::ToBase64String([IO.File]::ReadAllBytes($path))
Base64→ファイル [IO.File]::WriteAllBytes($path, [Convert]::FromBase64String($b))
-EncodedCommand用 [Text.Encoding]::Unicode(UTF-16LE)で作る

よくある失敗

文字列を直接ToBase64Stringに渡す

バイト列が必要です。[Text.Encoding]::UTF8.GetBytes($s)で変換してから渡します。

エンコードとデコードで文字コードが違う

UTF-8で作ったらUTF-8で戻します。違うと文字化けします。

-EncodedCommandをUTF-8で作る

UTF-16LE([Text.Encoding]::Unicode)が必要です。UTF-8では動きません。

ファイルをGet-Contentで読んでBase64化する

テキストとして読むと壊れます。[IO.File]::ReadAllBytesでバイトとして読みます。

不正なBase64文字列をデコードする

長さが4の倍数でない・不正な文字を含むとエラーになります。文字列が欠けていないか確認します。

よくある質問

QPowerShellで文字列をBase64にするには?
A[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s))を使います。[Convert]::ToBase64Stringはバイト列を受け取るため、先にGetBytesで文字列をバイト列に変換するのがポイントです。デコードは[Text.Encoding]::UTF8.GetString([Convert]::FromBase64String($b64))です。
Qデコードすると日本語が文字化けします。
Aエンコードとデコードで文字コードが違う可能性があります。UTF-8でBase64化したなら、デコードもUTF-8で行ってください。Base64自体は文字コードの情報を持たないため、両者をそろえる必要があります。迷ったらUTF-8で統一するのが無難です。
Q画像やバイナリファイルをBase64にできますか?
Aできます。[Convert]::ToBase64String([IO.File]::ReadAllBytes("パス"))でファイルをBase64化し、[IO.File]::WriteAllBytes("パス", [Convert]::FromBase64String($b64))で復元します。バイト単位で完全に元に戻せます。文字コードは関係しません。
Q-EncodedCommandで作ったBase64が動きません。
A-EncodedCommandはUTF-16LE(Unicode)を前提としているためです。[Text.Encoding]::Unicode.GetBytes($command)でバイト列にしてからBase64化してください。通常のデータと違い、ここだけはUTF-8ではなくUTF-16LEを使う点に注意します。
QBase64文字列の末尾の「=」は何ですか?
ABase64は3バイトを4文字に変換する方式で、データの長さが3の倍数でないときに、末尾を=で埋めて長さを4の倍数にそろえます。これはパディングと呼ばれる正常な仕様で、デコード時に正しく処理されます。

まとめ

  • 文字列→Base64は[Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($s))
  • 文字列は直接変換できず、いったんバイト列にする必要があります。
  • UTF-8とShift-JISで結果が変わるため、エンコードとデコードの文字コードをそろえます。
  • ファイルは[IO.File]::ReadAllBytesでバイト列にしてBase64化、バイト単位で復元できます。
  • -EncodedCommandだけはUTF-16LE([Text.Encoding]::Unicodeで作ります。

PowerShellのBase64は、[Convert]クラスと[Text.Encoding]の組み合わせで扱えます。「文字列はバイト列を経由する」「文字コードをそろえる」「-EncodedCommandはUTF-16LE」の3点を押さえれば、データの受け渡しやスクリプトの実行で困ることはなくなります。