上传文件
  1. base64hub
  2. 在代码中使用 Base64
  3. Bash
代码

Bash 中的 Base64

真正干活的是 base64 命令,Bash 只负责把数据通过管道传给它。Linux 上的 GNU coreutils 和 macOS 上的 BSD 版本参数不同。下面的 Linux 输出来自 GNU coreutils 9.7。

编码和解码字符串

printf '%s' 'héllo wörld' | base64          # aMOpbGxvIHfDtnJsZA==
printf '%s' 'aMOpbGxvIHfDtnJsZA==' | base64 -d   # héllo wörld

base64 编码的是字节,所以结果取决于终端的编码。在任何现代 Linux 或 macOS 环境中,这都是 UTF-8。

最经典的错误是用 echo,它会在末尾追加一个换行符:

echo 'héllo wörld' | base64      # aMOpbGxvIHfDtnJsZAo=   (note the "o=")
echo -n hi | base64              # aGk=

echo -n 在 Bash 中有效,但并非所有 sh 都支持;printf '%s' 才是可移植的写法。here-string(base64 <<< "text")同样会追加换行符。如果有人发给你的 Base64 以 Cg== 或 o= 结尾,里面很可能带着一个结尾换行符,把它粘贴到 Base64 解码器 里看看就知道了。

GNU(Linux)与 BSD(macOS)的参数差异

任务GNU coreutils(Linux)macOS
解码-d、--decode-D、--decode(新版本也支持 -d)
不换行-w 0默认行为
每 N 个字符换行-w N(默认 76)-b N
输入文件base64 filebase64 -i file 或 base64 < file
输出文件用 > 重定向-o file 或重定向
解码时忽略无关字符-i、--ignore-garbage不支持

两个 -i 参数的含义完全不同:在 macOS 上它指定输入文件,在 Linux 上它忽略无关字符。可移植的脚本应该从 stdin 读取、向 stdout 写出:

base64 < in.bin | tr -d '\n' > out.b64     # same output on Linux and macOS
base64 --decode < out.b64 > in-copy.bin

GNU base64 默认在 76 个字符处换行,这会破坏 HTTP 头和环境变量:

head -c 60 /dev/zero | tr '\0' x | base64 -w 0
# eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4eHh4

不加 -w 0 时,第一行是 76 个字符,其余部分换到第二行。如果你需要 MIME 风格的分行,默认的换行正合适。

URL 安全的 Base64

GNU coreutils 8.31 及更高版本自带 basenc,支持 Base64URL,并且会保留填充:

printf '%s' '<<???>>' | basenc --base64url      # PDw_Pz8-Pg==
printf '%s' 'PDw_Pz8-Pg' | basenc --base64url -d   # <<???>>

在 macOS 或较旧的 Linux 上,用 tr 替换字符:

printf '%s' '<<???>>' | base64 | tr '+/' '-_' | tr -d '='   # PDw_Pz8-Pg

解码时需要反向替换;对于严格的解码器,还要把填充补回来:

s=PDw_Pz8-Pg
s=$(printf '%s' "$s" | tr -- '-_' '+/')
while [ $(( ${#s} % 4 )) -ne 0 ]; do s="$s="; done
printf '%s' "$s" | base64 -d        # <<???>>

-- 用来阻止 tr 把 -_ 当成选项。JWT 需要的也正是这种转换,JWT 解码器 会帮你自动处理。更多内容见 Base64 与 Base64URL 的区别。

编码和解码文件

base64 -w 0 hello.txt              # aGVsbG8K
base64 -d <<< 'aGVsbG8K' > copy.txt
base64 -d payload.b64 > payload.pdf

base64 是流式处理的,它按块读写,所以文件大小无所谓。为图片生成 Data URI:

printf 'data:image/png;base64,%s' "$(base64 -w 0 logo.png)"

生成 HTTP Basic Auth 请求头:

printf 'Basic %s' "$(printf '%s' 'user:pass' | base64)"   # Basic dXNlcjpwYXNz

Basic Auth 请求头生成器 也能做同样的事,而且不会在 shell 历史记录里留下你的密码。

校验、错误和退出码

GNU base64 -d 发现无效字符时,会在 stderr 上打印 base64: invalid input,并以状态码 1 退出。但它仍会写出到那一刻为止解码出的所有内容,因此解码失败可能会留下一个不完整的文件:

printf 'aGk=!!' | base64 -d        # prints "hi", then: base64: invalid input
echo $?                            # 1 (without a pipe after it)
printf 'aGk=!!' | base64 -d -i     # hi   (garbage ignored, exit 0)

coreutils 9.7 下的结果:

输入结果
aGkhi,退出码 0(容忍缺失的填充)
"aGVs\nbG8K"hello,退出码 0(忽略换行)
aGl=hi,然后 invalid input,退出码 1
ainvalid input,退出码 1
PDw_Pz8-Pg==部分输出,invalid input,退出码 1

用退出码来做校验,并先解码到临时文件,这样失败时就不会覆盖好的数据:

if printf '%s' "$b64" | base64 -d > /tmp/out.bin 2>/dev/null; then
  mv /tmp/out.bin result.bin
else
  echo "invalid Base64" >&2
fi

在管道中,$? 是最后一条命令的状态。如果 base64 -d 不在最后,请加上 set -o pipefail。从日志或邮件中复制的字符串解不出来时,修复 Base64 工具会去掉引号、空白和前缀;错误指南 逐一解释了每种失败。

命令行中的其他工具

  • openssl base64 几乎在所有系统上都有,在 Linux 和 macOS 上表现一致。见 用 OpenSSL 处理 Base64。
  • 没有 coreutils 的地方可以用 python3 -m base64 -d file 和 python3 -m base64 file,输出每 76 个字符换行。
  • 在 Kubernetes 中,常见写法是 kubectl get secret x -o jsonpath='{.data.password}' | base64 -d,也可以用 Kubernetes Secret 解码器。

常见问题

为什么 echo hello | base64 的结果和在线工具不一样?

echo 会追加一个换行符,所以你编码的是 hello\n(aGVsbG8K)而不是 hello(aGVsbG8=)。请用 printf '%s' hello 或 echo -n hello。

怎样让 base64 输出不换行?

Linux 上用 base64 -w 0。macOS 默认就不换行。想让同一条命令在两边表现一致,就再通过管道接一个 tr -d '\n'。

base64: invalid input 是什么意思?

GNU base64 -d 遇到了字母表之外的字符或损坏的填充。它会先输出已经解码的字节,再打印这条错误,并以状态码 1 退出。常见原因是 URL 安全的 - 和 _、引号以及 data: 前缀。

macOS 上的 base64 -d 和 Linux 上的一样吗?

较旧的 macOS 版本只认 -D(或 --decode),新版本也接受 -d。需要在两边都能运行的脚本里,请用 --decode。

继续阅读