echoのSHA-256が一致しない理由 改行とPowerShellの文字コード
Web のハッシュ計算ツールに abc と入れると ba7816bf... になるのに、ターミナルで echo abc | sha256sum と打つと edeaaff3... になる。計算が間違っているわけではない。ハッシュ関数に渡ったバイト列が違っている。
SHA-256 は文字列ではなくバイト列を受け取る。画面で同じ abc に見えても、末尾の改行や文字コードが違えば、まったく別の値になる。この記事では、シェルごとに実際に何バイトが渡るのかを測った。とくに Windows PowerShell 5.1 は、日本語を ? に置き換えてからパイプへ流す。最後に、一致しないハッシュ値から原因のバイト列を特定するスクリプトを示す。
検証環境は Windows 11(日本語)、Node.js v24.11.1、Python 3.11.9、Windows PowerShell 5.1、Git Bash(bash 5.2、GNU coreutils 8.32 の sha256sum)である。
同じ abc で SHA-256 は 6 種類になった
バイト列は、受け取る側の Python で sys.stdin.buffer.read() を表示するか、できたファイルの先頭を読んで確かめた。
| 打ったもの | 渡ったバイト列(16進) | SHA-256 の先頭 8 桁 |
|---|---|---|
bash の printf abc、echo -n abc |
61 62 63 |
ba7816bf |
bash の echo abc、sha256sum <<< abc |
61 62 63 0a |
edeaaff3 |
cmd.exe の echo で、パイプの直前に空白なし |
61 62 63 0d 0a |
552bab68 |
cmd.exe の echo で、パイプの直前に空白あり |
61 62 63 20 0d 0a |
fff01482 |
PowerShell 5.1 の "abc" をパイプ(コードページ 932) |
61 62 63 0d 0a |
552bab68 |
PowerShell 5.1 の "abc" をパイプ(コードページ 65001) |
ef bb bf 61 62 63 0d 0a |
c3199f17 |
PowerShell 5.1 の "abc" > file |
ff fe 61 00 62 00 63 00 0d 00 0a 00 |
abbc6233 |
正しいのは ba7816bf だけである、とは言えない。どの値も、渡ったバイト列に対しては正しい。Web ツールや API と比べたいなら、相手が受け取るのと同じバイト列を作る必要がある。
1. 末尾の改行が 1 バイト入る
echo は引数の後ろに改行を付ける。abc は 3 バイトだが、echo abc の出力は 4 バイトになる。ヒアストリング(<<<)も同じで、末尾に改行が付く。
エディタで保存したファイルも同様である。最終行に改行を入れる設定なら、abc と書いたつもりのファイルは 61 62 63 0a になる。sha256sum の結果は edeaaff3 で、printf abc とは合わない。
echo -n で改行を消せるように見えるが、これは移植性がない。POSIX の echo はオプションを定めておらず、最初の引数が -n のときの動作を規格の範囲内で実装に任せている。XSI 準拠のシステムでは文字列として出力する。bash で POSIX モードと xpg_echo を有効にすると、実際にそうなった。
1 | |
2d 6e 20 は -n である。バックスラッシュの扱いも実装で分かれる。echo "a\tb" は、この環境の bash では \t がそのまま残り(61 5c 74 62 0a)、dash ではタブになった(61 09 62 0a)。POSIX も、新しく書くものには printf を使うよう勧めている。
1 | |
2. cmd.exe はパイプの前の空白も出力する
cmd.exe の echo は CRLF を付ける。さらに、| の前の空白まで出力に含める。
1 | |
読みやすくしようとパイプの前に空白を入れただけで、ハッシュ値が変わる。
3. PowerShell 5.1 は日本語を ? に置き換えてパイプへ流す
ここがいちばん気づきにくい。Windows PowerShell 5.1 では、外部コマンドへパイプするときの文字コードを $OutputEncoding が決める。Microsoft のドキュメントでは、5.1 の既定値は ASCIIEncoding である。ASCII にない文字は ? になる。
1 | |
コードページ 932 で実行した結果である。5 文字すべてが 3f(?)になり、CRLF が付いた。このまま openssl などへ流すと、こんにちは ではなく ?????\r\n のハッシュ(488fdf9c)を計算することになる。別の日本語 5 文字でも同じ値が出るので、エラーにならず、気づきにくい。
$OutputEncoding を UTF-8 に変えると、文字は正しく渡る。ただし CRLF は残る。
1 | |
コンソールのコードページを 65001(chcp 65001)にして同じことをすると、先頭に ef bb bf(BOM)まで付いた。上の表の c3199f17 がこれである。$OutputEncoding を BOM なしの UTF-8 にしても消えなかった。コードページの設定によって結果が変わるので、パイプで文字列を渡す方法は避けたほうがよい。
なお、PowerShell 7 の $OutputEncoding の既定値は、ドキュメントでは UTF8Encoding となっている。今回 PowerShell 7 では測っていない。
4. PowerShell 5.1 の > は UTF-16 のファイルを作る
"abc" > file で作ったファイルの中身は、BOM(ff fe)付きの UTF-16LE に CRLF が付いたものだった。Out-File の 5.1 のドキュメントでも、-Encoding の既定値は UTF-16LE を表す unicode である。5.1 の about_Redirection には > は UTF8NoBOM を使うと書かれているが、この環境の実測は UTF-16LE で、Out-File の説明と一致した。
Out-File -Encoding utf8 は BOM 付きの UTF-8 になった(ef bb bf 61 62 63 0d 0a)。Set-Content -NoNewline -Encoding ascii なら 61 62 63 の 3 バイトになり、Get-FileHash の結果は BA7816BF... と一致した。
5. 文字コードと正規化で変わる
日本語は、文字コードが違えば別のバイト列になる。
| 文字列 | 形式 | SHA-256 の先頭 8 桁 |
|---|---|---|
| こんにちは | UTF-8 | 125aeadf |
| こんにちは | Shift_JIS | 58f4daa9 |
| こんにちは | UTF-16LE(BOM なし) | 9e9eefc6 |
| が | NFC(e3 81 8c) |
e3b21956 |
| が | NFD(e3 81 8b e3 82 99) |
f62b28f7 |
最後の 2 行は、見た目も文字コードも同じで、Unicode の正規化形だけが違う。macOS のファイル名などから NFD の文字列が来ると、NFC の文字列と同じに見えてもハッシュは一致しない。
6. ファイルをテキストとして読むと変わる
CRLF のファイルを Python の open() で普通に開くと、universal newlines によって \r\n が \n に変換される。
1 | |
sha256sum crlf.txt と合うのは 'rb' で開いたほうである。BOM の扱いも読み方で変わる。Python の encoding='utf-8' は BOM を として文字列に残し、utf-8-sig なら取り除く。Node.js の readFileSync(p, 'utf8') は、CRLF も BOM も残した。ファイルのハッシュは、必ずバイト列のまま計算する。
Webhook の署名検証で生ボディを使う必要があるのも、同じ理由である。詳しくは Webhook 署名の HMAC-SHA256 検証の記事 に書いた。Git の設定で作業ツリーの改行が変わる話は、改行コードの記事 にまとめてある。
正しい渡し方
余計なバイトを入れない書き方を、環境別にまとめる。いずれも今回の環境で 125AEADF...(こんにちは の UTF-8)または ba7816bf...(abc)になることを確かめた。
1 | |
1 | |
1 | |
1 | |
PowerShell の例は .ps1 を日本語を含むファイルとして保存するなら、BOM 付き UTF-8 にしておく。5.1 は BOM のないスクリプトを ANSI コードページとして読むためで、今回の検証も BOM 付きで保存して実行した。
実際に試す
前提: Node.js(確認は v24.11.1)。外部パッケージは不要で、ネットワークにも接続しない。元の文字列と一致させたいハッシュ値を渡すと、文字コード 7 種類、末尾 4 種類、正規化 2 種類の組み合わせを試し、一致したバイト列を表示する。ハッシュの種類は長さから判定する(MD5、SHA-1、SHA-256、SHA-512)。
1 | |
実行結果は次のとおりである。ハッシュ値は上の表の完全な値を渡している。
1 | |
一致なし の場合は、元の文字列そのものが違うか、ここにない変換(ソルト、HMAC、JSON の整形など)が入っている。このスクリプトが調べるのは、同じ文字列の表現の違いだけである。
ハシトシステムのハッシュ生成では
ハッシュ生成 は、入力欄の文字列を TextEncoder で UTF-8 にして、crypto.subtle.digest に渡す。2026-11-08 時点のコードに前後の空白を取り除く処理はないので、入力欄の末尾に改行があれば、その改行もハッシュに入る。printf の結果と比べるときは、貼り付けた文字列の末尾に改行が残っていないか確かめるとよい。
一方で、入力欄は <textarea> なので、CRLF はハッシュに入らない。HTML Standard では、textarea の value は改行を LF に正規化した API value を返す。本番のツールで abc\r\n を値として設定すると、value の中身は abc\n になり、SHA-256 は edeaaff3 だった。CRLF を含むテキストのハッシュは、このツールでは再現できない。CRLF のファイルと比べるなら、ファイルのまま sha256sum や Get-FileHash にかける。
値が合わないときは、同じ文字列を 文字コードダンプ で UTF-8 のバイト列にして、相手のバイト列と見比べられる。改行コードは 改行コード変換、濁点の合成と分解は Unicode 正規化フォーム比較 で確かめられる。いずれもブラウザ内で処理され、入力はサーバーに送られない。
まとめ
- ハッシュが合わないときは、計算ではなくバイト列を疑う。同じ
abcが、シェルと書き方によって 6 種類の SHA-256 になった。 echoは改行を付ける。echo -nは移植性がないので、printf '%s'を使う。- cmd.exe の
echoは CRLF を付け、|の前の空白も出力する。 - Windows PowerShell 5.1 の既定では、パイプで日本語が
?になり、CRLF が付く。コードページ 65001 では BOM も付いた。>は UTF-16LE のファイルを作る。文字列のハッシュはGet-FileHash -InputStreamにバイト列を渡して計算する。 - ファイルはテキストモードで読まず、バイト列のままハッシュする。
<textarea>の値は改行が LF に正規化されるので、Web ツールでは CRLF のハッシュを再現できない。
結果は 2026-11-08 時点の上記の環境で得たものである。シェルの版やコードページの設定で変わるので、手元では xxd などでバイト列を確かめてほしい。
参考
- POSIX.1-2024(The Open Group Base Specifications Issue 8)— echo
- Microsoft Learn — about_Preference_Variables($OutputEncoding、PowerShell 5.1 / 7.5)
- Microsoft Learn — Out-File(PowerShell 5.1、-Encoding)、about_Redirection
- Python ドキュメント — open()(newline、universal newlines)
- WHATWG HTML Standard — 4.10.11 The textarea element(raw value、API value)