図とかにするブログ

監査系コンサル会社にて、IT エンジニアとして勤務している人のブログです。 AWS・Azure などのクラウドインフラと、バックエンド関連の技術を触っています。 なるべく図解と結論ファーストを意識した、読み手にとってタイパの良い技術発信を心がけます。

Powershell で迂闊に pem ファイルをリダイレクトしてはいけない

背景

案件によっては、Windows を踏み台として作成し、Linux へ接続を行う、といったケースがあると思います。

このとき、Teraterm を使って SSH 接続を行うことが多いので、pem ファイルを Windows へ運ぶということがあるでしょう。

RDP へコピペが許されていれば特に問題はないのですが、場合によっては pem ファイルをダウンロードして持ってくる必要があるかと思います。

例えば AWS でいくと、閉域網を構成するとなった場合、VPC エンドポイントを用意してシークレットマネージャーに鍵を格納して引っ張ってくるのが定番でしょう。 (AWS CDK なんかでも Secret Manager に秘密鍵を出せたりしますね)

この場合、powershell 上から AWS CLI を叩いて、ファイルを持ってくることになりますね。

Secret Manager から秘密鍵を取る

起こりがちな問題

Powershell 上で AWS CLI を利用したら、以下のようにリダレクトを行って pem ファイルとして書き込みを行いたくなると思います。

aws secretsmanager get-secret-value --secret-id <ARN> --query 'SecretString' --output text > hoge.pem

そのファイルを使って、Teraterm から接続を行おうとすると以下のようなエラーが発生します。

read error SSH2 private key file
error:09FFF06C:PEM routines:CRYPTO_internal:no start line

こんなエラーがでる

Web 上を検索してみると、秘密鍵のフォーマットが間違っており、改行周りを調整すればいける!みたいなことを書かれていますが、どんだけ改行周りを調整してもうまくいきません。

これは、BOM というデータが Powershell のリダイレクトによって付加されているためです。

結論

Powershell 上でリダイレクトを行うと、実は BOM (Byte Order Mark)というものが挿入されます。 なので、リダイレクトを行わずコピペをするか、リダイレクトしたファイルから BOM を取り除く必要があります。

BOM を取り除きたい場合、メモ帳から保存形式として BOM なしでの保存が可能です。

BOMなしが選べる

プログラムだけでやり切りたい場合は、自前でやるしかないようです、マジか。 バイナリとしてファイルを読み込んで、対象の BOM を削除すればいけるようです。

ちなみにこのリトルエンディアン UTF-16 は Powershell の仕様できっちりとドキュメントに書いてあります。 (あんまりこの仕様を知らずにこのドキュメントにたどり着けるとは到底思えないけど)

In general, Windows PowerShell uses the Unicode UTF-16LE encoding by default. However, the default encoding used by cmdlets in Windows PowerShell is not consistent.

Note Using any Unicode encoding, except UTF7, always creates a BOM.

learn.microsoft.com

結論としてはこれで終わりですが、BOM とは何かしっかり確認しましょう。

BOM とは

BOM(Byte Order Mark)は、テキストファイルの先頭に付加される特殊な文字列で、基本は以下 2 つの目的で挿入されます。

  • エンコーディング:対象のファイルの文字エンコーディングを指定します(UTF-8,16,32 など)

  • バイトオーダーの指定:UTF-16 ã‚„ UTF-32 のように複数バイトを使用するケースでは、バイトをどのような順序で格納するかも示します

挿入される値は以下のように決まっています。 - UTF-8: EF BB BF - UTF-16 (Big Endian): FE FF - UTF-16 (Little Endian): FF FE - UTF-32 (Big Endian): 00 00 FE FF - UTF-32 (Little Endian): FF FE 00 00

learn.microsoft.com

バイトオーダーって言われても最初はなかなかピンとこないのでこちらも確認しましょう。

リトルエンディアンとビッグエンディアン

バイトオーダーとは、その名前の通り、バイトをどのように並べるか?という順番です。 これは複数のバイトを1つの単位として認識して扱うエンコーディングの場合、この複数のバイトをどのように並べるのか?によって全く意味が異なってきます。 バイトオーダーには、リトルエンディアンとビッグエンディアンの2種類があります。

まだ、何となく意味が分かりづらいのでもう少し詳しく見てみます。

まず大前提として、メモリやブロック上には基本的に 1 byte 単位でデータが格納されています。

1byte 単位

UTF-16 は 2 byte が基本単位、UTF-32 は 4 byte が基本単位になります。 例えば、UTF-16 において英語の「z」は、U+007A で、「00」と「7A」の2つの byte で表されます。

UTF-16 は 2 byte 固定長

ただしこの 2byte をどのようにメモリなどで格納をするのか、上位バイトから格納するか、下位バイトから格納するかによって、2パターンあり、これがバイトオーダーと呼ばれるものです。

リトルエンディアンとビッグエンディアン

現代では基本的にリトルエンディアンが主流なので、これがリトルエンディアンで解釈されているとしましょう。 ここで、本来リトルエンディアンで解釈しなければならないものを、誤ってビッグエンディアンで解釈してしまうと、007A の逆の 7A00 として解釈されてしまいます。

7A00 (U+7A00)は漢字の「稀」にあたりますから、いわゆる文字化けが発生します。

実際に Windows 環境で試してみると、以下のような結果が得られます。 最初の 255, 254 は 0xFF, 0xFE の UTF-16 のリトルエンディアンの BOM です。 次に続く 122, 0 は 0x7A, 0x00 ですから、「z」U+7A00 がきっちりリトルエンディアンで格納されていますね。 0, 13, 0, 10 は Windows の改行である CRLF(\r\n)です。UTF-16 は 2byte 固定長なので、改行も 2byte 使われています。

PS C:\Users\Administrator> echo z > .\z.txt
PS C:\Users\Administrator> get-content .\z.txt -Encoding Byte
255
254
122
0
13
0
10
0

10進数と16進数と意味

では、「z」の部分のバイト順を逆にしてみます。 これで、本来ビッグエンディアンで「z」と表記したかったものを、リトルエンディアンで解釈することになります。

PS C:\Users\Administrator> $byteArray = [byte[]](255, 254, 0, 122, 13, 0, 10, 0)
PS C:\Users\Administrator> [System.IO.File]::WriteAllBytes(".\reverse-z.txt", $byteArray)
PS C:\Users\Administrator> get-content .\reverse-z.txt
稀

漢字の「稀」が表示されました。 これは U+7A00 の文字ですから、綺麗に文字化けを起こすことができました。 BOM が大事な役割を果たしているってのがなんとなく理解できました。

ちなみに Linux では、基本的に UTF-8 が一般的に利用されるため、特に BOM をつける必要とされません。 (むしろ pem ファイルの件のように、何かと問題になることが多いです)

UTF-8 は可変長の文字エンコーディングであり、1文字を表す際にこの文字が何 byte で表されるのかを示すための制御 bit を利用します。 そのため、前についている制御 bit をまず読まなければならないので、前から 1byte 単位で処理していくほかありません。 つまり、バイトオーダーという概念がないので、BOM などをつける必要がないわけです。 (Windows には UTF-8 でも互換性のため BOM をつけることができます)

余談1)Powershell が BOM をつける理由

こちらは以下のブログが詳しかったです。

Windows は内部的には UTF-16 で基本的に処理を行うので、BOM 付き UTF-16 を利用することが多かったのが背景のようです。 UTF-16 は 2byte 固定長なので、BOM が必要になるわけですね。

PowerShellはクロスプラットフォームなアプリケーションとなっていますが、元々はWindowsの.NET Framework上で動作するアプリケーションでした。

このためPowerShellで取り扱える文字コードは.NET FrameworkおよびWindowsの影響を大きく受けています。 リダイレクト演算子やファイル操作に関わるコマンドレットの既定のエンコーディングがBOM付きUTF-16であることやUTF-8がBOM付きなのは、基本的には.NET Frameworkにおけるエンコーディングがそうである影響です。

blog.shibata.tech

余談2)リトルエンディアンが現代では主流な理由

リトルエンディアンが主流な理由は以下の2つからっぽいです。

  • 計算が効率的 筆算を考えるとわかりやすいですが、基本的に計算は小さい桁から行っていき、繰り上げを考慮するのが普通です。 そのため、リトルエンディアンだと前から処理を行えるので、効率的に計算が行えます。

  • x86 アーキテクチャでの採用 こちらは歴史的な背景ですが、世界中で幅広く使われている Intel の x86 で採用されたことが大きな理由の一つのようです。