编码和解码字符串
printf '%s' 'héllo wörld' | openssl base64 # aMOpbGxvIHfDtnJsZA==
echo 'aMOpbGxvIHfDtnJsZA==' | openssl base64 -d # héllo wörld
openssl base64 是 openssl enc -base64 的简写,两种写法接受相同的参数。编码时用 printf '%s',避免数据里混进换行符;解码时用 echo,原因见下文。
结尾换行符的陷阱与 -A
默认情况下,OpenSSL 把 Base64 当作以换行符结尾的若干行文本来处理。解码时,末尾没有 \n 的最后一行会被忽略,所以下面这条命令什么也不会输出:
printf 'aGVsbG8K' | openssl base64 -d # (no output)
printf 'aGVsbG8K\n' | openssl base64 -d # hello
printf 'aGVsbG8K' | openssl base64 -d -A # hello
-A 让 OpenSSL 把整个输入当作一行。两个方向都要用到它:
- 编码时:不加
-A,输出每 64 个字符换一行。加上-A就只有一行,HTTP 头、JSON 和环境变量需要的正是这种格式。 - 解码时:加上
-A,输入必须是不含换行的单行。不加-A,PEM 正文这样的多行输入也能正常解码。
head -c 60 /dev/zero | tr '\0' x | openssl base64 # 64 chars, newline, 16 chars
head -c 60 /dev/zero | tr '\0' x | openssl base64 -A # 80 chars on one line
64 个字符的换行与 PEM 格式(RFC 7468)一致,而不是最多 76 个字符的 MIME。需要 MIME 输出时,MIME Base64 编码器 或 GNU base64(默认 76)更合适。
URL 安全的 Base64
OpenSSL 没有 Base64URL 模式。可以用 tr 转换:
printf '%s' '<<???>>' | openssl base64 -A | tr '+/' '-_' | tr -d '='
# PDw_Pz8-Pg
解码时,把映射反过来,再补回填充;Bash 中的 Base64 页面有一个实现这一步的循环。如下文所示,OpenSSL 本身会悄悄忽略 - 和 _,所以绝不要把 URL 安全的输入直接交给它。两者的区别见 Base64 与 Base64URL 的区别。
编码和解码文件
openssl base64 -in photo.jpg -out photo.b64
openssl base64 -d -in photo.b64 -out photo-copy.jpg
OpenSSL 是流式处理的,所以大文件没有问题。一个 100 KB 的随机文件经过往返转换后逐字节相同。编码后的文件每 64 个字符换行,并以换行符结尾,所以不加 -A 也能解码。如果你需要单行文件,编码和解码时都要加 -A。
生成 Data URI:
printf 'data:image/jpeg;base64,%s' "$(openssl base64 -A -in photo.jpg)"
OpenSSL 3.5 的 enc 手册的 BUGS 部分仍然列出了两个相关问题:-A 在处理大文件时“doesn’t work properly”(无法正常工作);不加 -A 解码时,如果输入的前 1024 个字节中没有换行符,前两行会被忽略。对于很大的单行文件,先用 fold -w 64 换行,再不加 -A 解码。
静默失败:校验结果
OpenSSL 不会报告无效的 Base64。它会在遇到错误数据时停下,写出已经解码的内容,然后以状态码 0 退出。OpenSSL 3.5 下的结果:
| 输入 | 输出 | 退出码 |
|---|---|---|
aGVsbG8K + 换行符 | hello\n | 0 |
aGVsbG8K(无换行符) | 无输出 | 0 |
aGk + 换行符(无填充) | 无输出 | 0 |
aGk=!! + 换行符 | 无输出 | 0 |
PDw_Pz8-Pg== + 换行符(URL 安全) | 无输出 | 0 |
not base64 at all + 换行符 | 无输出 | 0 |
因此脚本必须自己检查输出:
n=$(echo "$b64" | openssl base64 -d | wc -c)
[ "$n" -gt 0 ] || echo "decode failed or input was empty" >&2
这能发现彻底的失败,但发现不了部分失败,即 OpenSSL 在遇到错误数据之前已经解码了一段前缀的情况。
或者改用 GNU base64 -d,它会打印 base64: invalid input 并以 1 退出。想知道某个字符串错在哪里,Base64 校验工具 会标出有问题的字符,Base64 常见错误 解释了各种原因。
哈希、密钥与加密输出
OpenSSL 中 Base64 的大多数用法,都是和其他命令组合使用:
printf '%s' hi | openssl dgst -sha256 -binary | openssl base64 -A
# j0NDRmSPa5bfid2pAcUXaxCm2Dlh3TwayItZstwyeqQ=
openssl rand -base64 32 # 32 random bytes -> 44 characters
第一种写法生成 Base64 形式的 SHA-256 摘要,SRI 哈希 和许多 API 签名用的就是这种格式;浏览器版本是 SHA-256 转 Base64 工具。-binary 很关键:不加它,你编码的是哈希的 hex 文本,而不是哈希本身。
openssl rand -base64 32 是生成密钥和机密信息的常用方法;Base64 密钥生成器 可以在浏览器中做同样的事。
openssl enc 加上 -a(等同于 -base64)会对密文进行 Base64 编码:
printf '%s' 'secret text' | openssl enc -aes-256-cbc -pbkdf2 -a -pass pass:demo
# U2FsdGVkX1/eMk5T8FT9dX8OyAxLRn/ukvgmovOQOeA= (salt is random, changes every run)
U2FsdGVkX1 前缀是 Salted__ 的 Base64。没有密码,解码后也读不出任何内容。编码和加密的这种区别,见 编码与加密的区别。
PEM 文件
PEM 格式的证书和密钥,就是夹在 -----BEGIN ...----- 行之间、每 64 个字符换行的 Base64 DER。OpenSSL 可以直接转换它们,无需手动处理 Base64:
openssl x509 -in cert.pem -outform der -out cert.der
openssl x509 -in cert.der -inform der -outform pem
想在浏览器中查看 PEM 块,可以用 证书解码器 或 PEM 转 DER。
常见问题
为什么 openssl base64 -d 什么都不输出?
要么输入末尾没有换行符,要么输入无效。不加 -A 时,OpenSSL 期望每行都以换行符结尾,会悄悄丢掉末尾没有换行符的最后一行。请加上 -A,或者用 echo 代替 printf '%s' 输出字符串。
openssl base64 和 openssl enc -base64 一样吗?
一样。openssl base64 是 openssl enc -base64 的简写,两者都接受 -d、-A、-in 和 -out。
如何判断 openssl base64 -d 是否成功?
不能依赖退出码:输入无效时,它返回状态码 0,输出为空或不完整。请检查输出是否为空,或者改用 GNU base64 -d,它在输入错误时以 1 退出。
以 U2FsdGVkX1 开头的 Base64 字符串是什么?
那是 Salted__ 的 Base64,即 openssl enc 使用密码加密时写在密文前面的头部。这个字符串是密文,解码后得到的是二进制乱码,而不是明文。