base64 改行はmacOSとOpenSSLでどう違う?

要点

  • macOS付属のbase64は折り返さず末尾に改行を付け、OpenSSLの既定は64文字ごとに改行した(出典: 自社の実測・2026-10-20)。
  • OpenSSLとLibreSSLは、末尾改行のない1行を既定のデコードに渡すと、終了コード0で出力が空になった(出典: 自社の実測・2026-10-20)。
  • -A付きで改行入りの入力を戻した結果は、OpenSSLとLibreSSLで異なった(出典: 自社の実測・2026-10-20)。

何が困るのか:折り返しなしでも末尾改行は残る

Base64の折り返しとは、符号化した出力を一定の文字数ごとに改行で区切ることだ。ここで分けて確認したいのは、途中の折り返しと、末尾に付く改行である。

今回、macOS付属のbase64は、既定では途中で折り返さなかった。しかし、末尾には改行を付けた。一方、openssl base64 -Aは途中にも末尾にも改行を付けなかった。「1行にする」という条件だけでは、この違いを区別できない。

比較に使った入力は、0x00〜0x63の100バイトである。測定環境はmacOS 26.5.1(BuildVersion 25F80・arm64)。macOS付属のFreeBSD base64、HomebrewのOpenSSL 3.6.4、macOS付属のLibreSSL 3.3.6を実行した。言語側はPython 3.9.0、Node v22.22.3、Go 1.26.2で確認している。

出力は改行文字\nで分割し、各要素の長さをバイト数で数えた。最後の要素が0なら、末尾が改行で終わっている。この測り方により、途中で折り返していない出力でも、末尾改行の有無を分けて記録できる。

出典: 自社の実測(2026-10-20)

公式はどう書いているか:OpenSSLの既定と-A

OpenSSL 3.6のマニュアルは、-Aを指定しない場合のエンコードとデコードについて、次のように記している。

When the -A option not given, on encoding a newline is inserted after each 64 characters, and on decoding a newline is expected among the first 1024 bytes of input.

出典: https://docs.openssl.org/3.6/man1/openssl-enc/ (取得日 2026-10-20)

エンコードでは64文字ごとに改行を挿入し、デコードでは入力の先頭1024バイト以内に改行を期待する、という記述である。改行は出力の見た目だけの話ではなく、デコード側の入力条件にも関係している。

同じマニュアルの-Aの説明には、次の記述がある。

If the -a option is set then base64 encoding produces output without any newline character, and base64 decoding does not require any newlines.

出典: https://docs.openssl.org/3.6/man1/openssl-enc/ (取得日 2026-10-20)

この説明では、-Aを付けるとエンコード結果に改行を含めず、デコード時も改行を必要としない。ただし、これはOpenSSLのマニュアルである。別製品のLibreSSLについて、同じ挙動を保証する根拠にはできない。

macOSの手元のman base64では、-b countは指定文字数ごとの改行を挿入するオプションで、既定値は0、折り返しのないストリームを生成すると説明されていた。実測では、その折り返しのない出力の末尾に改行が付いた。

出典: 自社の実測(2026-10-20)

比較表:同じ入力でも改行の配置が違う

同じ100バイトを符号化した結果を並べると、途中の折り返しと末尾改行の組み合わせが見える。次の表は実測記録から抜粋したものだ。

道具・関数 出力の総バイト数 行の長さ(\nで区切った各要素) 末尾の改行
Python base64.b64encode 136 [136] なし
Python base64.encodebytes 138 [76, 60, 0] あり
Node Buffer.from(...).toString('base64') 136 [136] なし
Go base64.StdEncoding.EncodeToString 136 [136] なし
/usr/bin/base64 < in100.bin 137 [136, 0] あり
/usr/bin/base64 -b 76 < in100.bin 138 [76, 60, 0] あり
/opt/homebrew/bin/openssl base64 < in100.bin 139 [64, 64, 8, 0] あり
/opt/homebrew/bin/openssl base64 -A < in100.bin 136 [136] なし

出典: 自社の実測(2026-10-20)

Pythonでも、関数を変えると結果が変わる。b64encodeは改行なしだったが、encodebytesは76文字で折り返し、末尾にも改行を付けた。Pythonの公式ドキュメントにも、encodebytesは出力の76バイトごとに改行を挿入し、末尾の改行を保証すると記載されている。

出典: https://docs.python.org/3/library/base64.html (取得日 2026-10-20)

今回のエンコード結果は、改行を取り除くとすべて同じ文字列だった。したがって、この入力で観測された違いは、Base64の文字列そのものではなく、改行の入れ方である。

ただし、符号化する前の入力に含まれる改行は別に考える必要がある。zshでの記録では、echo hello | /usr/bin/base64の出力はaGVsbG8K、printf hello | /usr/bin/base64の出力はaGVsbG8=だった。echoが足した末尾改行まで入力として符号化されるためだ。これは、符号化後の文字列に付く折り返しとは異なる。

出典: 自社の実測(2026-10-20)

どちらを使うか:デコードは終了コードだけで判断しない

改行を一切含まない出力が必要なら、今回の実測ではOpenSSLの-AやPythonのb64encodeがその条件を満たした。macOS付属のbase64の既定出力には末尾改行があるため、「折り返さない」と「改行を含まない」を区別して選びたい。

デコードでは、さらに入力形式と実装の組み合わせを確認する必要がある。末尾改行のない136文字の入力と、64文字ごとに改行した入力を戻した結果は、次のとおりだった。

デコードしたもの 1行・末尾改行なし 64文字ごとに改行
/usr/bin/base64 -d 100バイト rc=0 100バイト rc=0
OpenSSL 3.6.4 base64 -d 0バイト rc=0 100バイト rc=0
OpenSSL 3.6.4 base64 -d -A 100バイト rc=0 100バイト rc=0
LibreSSL 3.3.6 base64 -d 0バイト rc=0 100バイト rc=0
LibreSSL 3.3.6 base64 -d -A 100バイト rc=0 0バイト rc=0

出典: 自社の実測(2026-10-20)

OpenSSLとLibreSSLは、どちらも-dだけで末尾改行のない1行を戻すと、出力が空になった。標準エラーも空で、記録には次の実物が残っている。

out=0 rc=0 stderr_bytes=0

終了コード0だけを見ても、元のデータが戻ったとは判断できない。また、LibreSSLでは-Aを付けると、今度は改行入りの入力が空になった。OpenSSLでは同じ指定で両方を戻せており、オプション名だけでは両者を同一視できない。

末尾に改行を足す対処にも、今回の測定では限界があった。1行が1020文字で末尾改行ありなら両方とも戻せたが、1024文字では両方とも0バイトだった。2000文字ではOpenSSLが0バイト、LibreSSLが732バイトを出した。LibreSSLが途中までのバイト数を出した理由は調べていない。

Python 3.9.0では、b64decode(data)は両方の入力を戻せた。一方、validate=Trueでは改行入りの入力に対してbinascii.Error: Non-base64 digit foundが発生し、終了コードは1だった。この版での結果を、未測定のPythonの版へ広げることはできない。

出典: 自社の実測(2026-10-20)

自分の環境で確かめる手順

まず、実行するopensslの実体を確認したい。測定したMacでは、which -a opensslがHomebrew版、macOS付属版の順に表示され、opensslと打つとOpenSSL 3.6.4が動いた。PATHによって実行対象は変わるため、openssl versionで製品名と版を記録してから比べる。

次は、読者が同じ入力を作り、改行なしで符号化してから復号するための手順である。以下の出力はここには掲載しない。最後に表示される元ファイルと復号後ファイルのハッシュを比較する。

openssl version
python3 -c "import sys; sys.stdout.buffer.write(bytes(range(100)))" > in100.bin
openssl base64 -A < in100.bin > oneline.txt
openssl base64 -d -A < oneline.txt > out.bin
shasum -a 256 in100.bin out.bin

改行入りも比較するときは、エンコード側の-Aを外して確かめる。復号側の-Aの有無も分けて記録すれば、今回差が出た組み合わせを自分の環境で確認できる。

今回測定したのは、記載したmacOS上の道具と版に限られる。GNU coreutilsのbase64、Windowsのcertutil、PowerShellは確認できなかった。そのため、この結果からそれらの既定の折り返し幅やデコード結果を判断することはできない。

出典: 自社の実測(2026-10-20)

コメント

タイトルとURLをコピーしました