Ruby 中的 Base64 编码:完整指南
你的数据有一个不接受它当前样子的目的地。一张必须住进 JSON 文档里的图片。一枚必须穿过 URL 的令牌。一个必须活过七位文本协议设计的附件。一个必须坐在环境变量里又不能破坏引号引用的秘密。在每一个这样的地方,这里和那里之间的某个东西正要毁掉你的二进制 - 而解法有个名字:Base64。
在 Ruby 里,整个工作住在随语言发货的一个模块里。三个编码器,什么都不用装,输出在你运行代码之前就能精确预测到每一个字符。这种可预测性是大多数指南跳过的故事的一半,因为编码正是惊喜收费的地方:一个末尾换行悄悄爬进你的 JSON,一个你没要的换行劈开你的令牌,一次选错的字母表毁掉一个 URL。这份指南走一遍全部三个编码器、输出的数学,以及 Ruby 开发者真正会编码的每一种载荷,让惊喜不再成为惊喜。
开始之前快速复习一下:Base64 每三个字节重写一次数据,从 64 符号字母表中发出四个字符,当输入不能被三整除时加上一两个 = 字符作为填充 - 这也是输出最终比输入大约三分之一大的原因。本站首页已经把格式讲得很透彻,所以这篇文章把格式部分压缩成一口气,直接开始干活。
你需要哪个编码器?
Ruby 给你三个编码器,而它们之间的选择是一道三问小测验:输出可以包含换行吗?可以包含 + 或 / 吗?可以包含填充吗?这是全部阵容:
| 编码器 | 输出形状 | 换行 | 填充 | 何时伸手拿它 |
|---|---|---|---|---|
Base64.strict_encode64(bin) |
一行,标准字母表 | 从不 | 永远存在 | JSON、令牌、API、文件 - 安全的默认选择 |
Base64.encode64(bin) |
多行,标准字母表 | 每 60 个字符之后,外加末尾一个 | 永远存在 | 邮件正文和其他按行组织的文本协议 |
Base64.urlsafe_encode64(bin, padding: true) |
一行,连字符-下划线字母表 | 从不 | 你说了算,默认开启 | 任何会落在 URL、cookie 或标识符里的东西 |
如果你在时间压力下做决定,简短答案是:默认 strict_encode64,结果要在 URL 里旅行时用 urlsafe_encode64,只有接收方是想让短行的文本协议时才用 encode64。下面所有内容解释为什么,以及每个选择在哪里悄悄让你付出代价。
strict_encode64:主力干将
Base64.strict_encode64 是你真正会在绝大多数代码里使用的编码器。它产出恰好一行的输出,永远带着正确的填充,来自标准字母表:
require "base64"
Base64.strict_encode64("hello world")
# => "aGVsbG8gd29ybGQ="
Base64.strict_encode64("s")
# => "cw=="
而且因为算法是确定性的,你可以从输入预测输出的精确长度 - 不用猜,数据库列里也不会有差一错误。下面这张表就是全部算术:
| 输入长度 | 输出长度 | 末尾填充 |
|---|---|---|
| 3n 字节(整除) | 4n 个字符 | 无 |
| 3n + 1 字节 | 4n + 4 个字符 | 两个 = |
| 3n + 2 字节 | 4n + 4 个字符 | 一个 = |
所以 11 个字节变成 16 个字符,100 个字节变成 136,一个 1 MB 的文件变成大约 1.33 MB 的文本。那三分之一的增长是你发货的每个 Base64 载荷的入场费,也是每当某个列、缓存或 API 速率限制开始发紧时,该揣在后兜里的数字。
Base64.strict_encode64("123")
# => "MTIz" 3 字节进,4 个字符出
Base64.strict_encode64("1234")
# => "MTIzNA==" 4 字节进,8 个字符出,2 个填充字符
Base64.strict_encode64("12345")
# => "MTIzNDU=" 5 字节进,8 个字符出,1 个填充字符
encode64:那个会加换行的
Base64.encode64 是经典款,它有一个行为结束过不止一个下午:它给输出折行。每 60 个字符,它开始新的一行,而且永远以一个末尾换行收尾:
Base64.encode64("hello world")
# => "aGVsbG8gd29ybGQ=\n"
Base64.encode64("*" * 46)
# => "KioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioqKioq\nKg==\n"
折行不是 bug - 它是从这个方法在 MIME 世界的老家继承来的特性,在那里,长行是协议违规。Ruby 的 mail gem 刻意依赖它 - 它的 Base64 编码器甚至带着这样一条注释:Ruby 的折行让输出保持在 SMTP 行长度限制之内。如果你在编码邮件正文,encode64 是在帮你。
但在其他所有上下文里,折行是一笔税。最常见的事故,是一个 JSON 文档里的 Base64 值突然跨了两行:
payload = { "logo" => Base64.encode64(File.binread("logo.png")) }
puts payload.to_json
# logo 的值带着没人要的换行
同一个 bug 的小双胞胎是短字符串上的末尾换行:Base64.encode64("s") 返回 "cw==\n",所以你粘贴进 URL 或拿去和期望值比较的令牌,会因一些让你追二十分钟的原因而失败。解药是 strip - 但更好的解药是 strict_encode64,它从不添加一个你没挣到的字符。还有一个值得知道的讨喜不对称:空输入产生一个不带末尾换行的空字符串,所以 Base64.encode64("") 就是 ""。
urlsafe_encode64:链接安全字母表
标准字母表里有两个字符,在任何有 URL 解析器盯着的地方都会惹麻烦:+(在查询字符串里是空格)和 /(路径分隔符)。RFC 4648 用一个交换解决了这个问题 - - 取代 +,_ 取代 / - Ruby 在 Base64.urlsafe_encode64 里实现了它:
Base64.urlsafe_encode64("\xfb\xef\xbe".b)
# => "----"
Base64.urlsafe_encode64("\xff\xff\xff".b)
# => "____"
这两个例子就是字母表的展示:同样的字节,标准编码器渲染成 ++++ 或 ////,在这里输出为 ---- 和 ____ - 这些字符在 URL、路径、文件名和表单字段里都能幸存,完全不需要百分号编码。输出一行,就像 strict_encode64 一样。
该方法唯一的选项是 padding: 关键字,在 Ruby 2.3 加入,也是那个必须知道的选项。JSON Web Token 规范要求 base64url 不带填充,许多其他令牌方案也是如此:
Base64.urlsafe_encode64("*")
# => "Kg=="
Base64.urlsafe_encode64("*", padding: false)
# => "Kg"
关掉填充后,长度算术就变了:3n + 1 字节现在产生 4n + 2 个字符,3n + 2 字节产生 4n + 3。解码侧能应付 - Ruby 的 urlsafe_decode64 会自己补上缺失的填充 - 所以不带填充的输出可以安全发出,但当对方是严格的 RFC 2045 读取器时,带填充的输出是更友好的默认。一个提醒:只有当规范明确要求时才关掉填充。它省下一两个字符,却买回一整类解码器抱怨。
Ruby 实际编码的是什么:字符串是字节
在讲用例之前,先说一个塑造一切的 Ruby 特有事实:一个 Ruby 字符串是一个戴着编码标签的字节序列,而编码器只看字节。标签告诉 Ruby 如何显示和比较这个字符串;它不改变被编码的内容:
require "base64"
s = "h\u{e9}llo"
puts s.encoding
# => UTF-8
puts s.bytes.length
# => 6 带重音的 e 是 2 个字节
Base64.strict_encode64(s)
# => "aMOpbGxv"
这就是“为什么我的输出比我预期的长”背后的陷阱:你敲进去的字符串通常按字符算比按字节算更短,而 Base64 按字节收费。反方向同样安静 - 一个无效的 UTF-8 字符串会被毫无抱怨地编码,因为编码器没有任何东西可验证:
broken = "h\u{e9}llo".b.force_encoding("UTF-8")
broken.setbyte(1, 0xFF)
puts broken.valid_encoding?
# => false
Base64.strict_encode64(broken)
# => 某段 base64,没有错误,字节就是字节
对于真正的二进制,完全跳过文本那套机器,用 pack 构造字节,或者用 File.binread 读取它们。一个让人满足的例子是 PNG 签名 - 地球上每个 PNG 文件开头的那 8 个字节:
png_magic = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A].pack("C*")
Base64.strict_encode64(png_magic)
# => "iVBORw0KGgo="
JWT:签名一份同样可读的数据
JSON Web Token 是 Ruby URL 安全编码器最高调的消费者。一个令牌是三段 base64url 段用点号连接 - 头部、载荷、签名 - 而规范说得很明确:字母表必须是 URL 安全的那个,填充必须关掉。jwt gem 处理这一切:
# Gemfile: gem "jwt"
require "jwt"
token = JWT.encode(
{ sub: "1234567890", name: "Alice", exp: Time.now.to_i + 3600 },
"my-secret-key",
"HS256"
)
puts token
# => eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIi...
payload, header = JWT.decode(token, "my-secret-key", true, algorithm: "HS256")
puts header
# => {"alg"=>"HS256"}
你也可以看着 Base64 层在令牌里干活,因为这些段就是 JSON 的 base64url:
require "base64"
require "json"
payload_json = JSON.generate({ "sub" => "1234567890", "name" => "Alice" })
segment = Base64.urlsafe_encode64(payload_json, padding: false)
puts segment
# => eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFsaWNlIn0
这个用例配两条规则。永远不要在生产环境手工搓 JWT - 签名才是让令牌不只是自供状的东西 - 而且用 gem 解码时,像上面那样在选项哈希里把算法钉死,让令牌自己的头部不能替你选验证方法。
HTTP Basic 认证:构建头
HTTP 里说“我是谁”的最老方式,至今也最简单:把凭据 Base64 编码,放在 Basic 这个词后面,发送这个头。在 Ruby 里构建它只要一行:
require "base64"
credentials = Base64.strict_encode64("alice:s3cr3t!")
puts "Basic #{credentials}"
# => Basic YWxpY2U6czNjcjN0IQ==
Ruby 的标准库在 Net::HTTP 里替你做了完全一样的事,直接调用核心 pack 模板 - ["user:pass"].pack("m0") 就是 basic_auth 底层的最终形态:
require "net/http"
request = Net::HTTP::Get.new("https://example.org/api")
request.basic_auth("alice", "s3cr3t!")
puts request["Authorization"]
# => Basic YWxpY2U6czNjcjN0IQ==
还有一个安全警告,说一次以便留档:Base64 是翻译器,不是锁。Basic 认证头里的凭据,任何能读这个包的人都能读。这个头只在 HTTPS 之上才可接受,在那里,传输层做真正的保护。
Data URI:内联图片与字体
data URI 是 web 对“我想要这张图,但不要单独的文件”的回答:一个媒体类型、单词 base64、一个逗号,然后是字节。单文件 HTML 演示靠它携带 logo,favicon 靠它藏进 CSS,生成的图片靠它完全住在一个模板字符串里:
require "base64"
png = File.binread("logo.png")
data_uri = "data:image/png;base64,#{Base64.strict_encode64(png)}"
css = "background-image: url(#{data_uri});"
puts css.length
# => 你的样式表,减去一次 HTTP 请求
这里用 strict_encode64 - 载荷是一行干净的数据,不折行,无换行。还要盯住尺寸:你内联的图片会膨胀大约三分之一,所以 data URI 在小资源(favicon、logo、图标字体)上发光,在大资源上臃肿。一张 2 MB 的英雄大图变成你 HTML 文档里的 2.7 MB,你的用户会在第一次 4G 滚动时感受到它。
邮件:Base64 的来处
这篇文章里其他所有用例都是它的后裔。SMTP 在 20 世纪 80 年代为七位文本的短行设计,换句话说,它带不动 JPEG。解法 - 先是 Privacy-Enhanced Mail,然后是 1993 年的 MIME - 是用 64 符号字母表把二进制重写成文本,那正是你今天在用的格式。伤疤在 Ruby 的输出里仍看得见:encode64 在 60 个字符处折行 - 这个宽度并不服务于某个特定协议,你稍后会看到,但足够短,能让邮件保持礼貌。
实践中你会让 mail gem 做 MIME 工作。挂上一个二进制文件,gem 会选 Base64 编码器、折好行、写好头:
# Gemfile: gem "mail"
require "mail"
message = Mail.new do |m|
m.from = "dev@example.org"
m.to = "ops@example.org"
m.subject = "Binary report"
m.add_file("report.bin")
end
puts message.encoded
# 附件部分带着 Content-Transfer-Encoding: base64 头
头里的非 ASCII 文本得到同样待遇,只是换了一套略不同的行头:RFC 2047 编码词,把 Base64 包在问号之间的字符集标签里,像 =?UTF-8?B?w7wgc2VjcmV0cw==?=。如果你哪天手工构建或解析它们,里面的 Base64 是普通的那种,用 decode64 解码,再贴上这个词声明的字符集标签。
密钥与证书的 PEM 护甲
密钥和证书穿着 PEM 护甲,而护甲就是带框的 Base64:一行 BEGIN、每行 64 个字符的编码字节,加上一行 END。如果你需要从原始 DER 字节生成一个 PEM 文件,构造是一个两步包裹:
require "base64"
der_bytes = File.binread("server.der")
body_lines = Base64.strict_encode64(der_bytes).scan(/.{1,64}/)
pem = (["-----BEGIN PRIVATE KEY-----"] + body_lines +
["-----END PRIVATE KEY-----"]).join("\n") + "\n"
File.write("server.key", pem)
两条备注。第一,你几乎永远用不到这个,因为 openssl gem 替你写 PEM(key.to_pem),而且 BEGIN 和 END 行之间的标签必须与里面的东西匹配 - 搞错了会产出一个互联网上每个工具都拒绝的文件。第二,这里的行长是 64,PEM 的经典宽度;Ruby 的 encode64 折在 60,而每个像样的 PEM 解析器都完全无视行长,所以两种宽度都能正常解码。
文件:.b64 约定
Base64 世界里最常见的文件格式,就是一个带 .b64(或 .base64)扩展名的纯文本文件,里面装着一个编码后的载荷 - 把它想成“那个文件,但贴到哪里都安全”。从 Ruby 生成它是一行流:
require "base64"
File.write("payload.b64", Base64.strict_encode64(File.binread("payload.bin")))
puts File.size("payload.b64")
# => 大约是原始大小的 1.33 倍
用 strict_encode64,让文件只装一行干净的数据 - 这是大多数解码工具(以及 Ruby 的严格解码器)期望的约定。读回来是镜像操作:读、解码,然后用二进制模式写出字节,让没有任何东西在出门路上被篡改:
encoded = File.read("payload.b64")
bytes = Base64.strict_decode64(encoded)
File.binwrite("restored.bin", bytes)
如果你的 .b64 文件来自会折行的工具 - 某些 base64 CLI 变体确实如此 - 在严格解码前剥掉换行,或者用宽容的解码器,它会免费跳过换行。
配置文件、环境变量与数据库
每当二进制数据必须住进一份文本文档,Base64 就是那座桥。这个模式在三处重复出现,各带一点小变化。
环境变量和 .env 文件装不下原始字节,所以字节在离开拥有它们的那台机器之前就被编码:
require "base64"
# 在某处配置应用的地方
ENV["APP_LOGO"] = Base64.strict_encode64(File.binread("logo.png"))
# 在某处应用启动的地方
b64 = ENV.fetch("APP_LOGO")
File.binwrite("logo.png", Base64.decode64(b64))
YAML 有原生的二进制类型,Psych 替你处理 Base64 - 一个 BINARY 字符串 dump 到 YAML 会输出为 !binary 标量,加载回来字节级一致:
require "yaml"
yaml_text = YAML.dump({ "logo" => File.binread("logo.png") })
puts yaml_text.lines.first(2)
# => "---"
# => "logo: !binary |-"
data = YAML.load(yaml_text)
puts data["logo"].encoding
# => ASCII-8BIT
在数据库里,问题是存储类型,不是编码。如果你的数据库有真正的二进制列 - BLOB、BYTEA、VARBINARY - 就用它,让驱动去携带字节。把 Base64 塞进 TEXT 列,是当存储层只说字符串时的模式:某些文档型数据库、JSON 形状的 API,或者一个你改不动的遗留 schema。代价是列上三分之一的尺寸税,外加一份纪律:在每个边界上,进的时候编码,出的时候解码,绝无例外。
以文本旅行的校验和
哈希是二进制的,但校验和大多以文本旅行:文件完整性清单、缓存键、指纹、日志行。Ruby 的每个摘要类都有一个 base64digest 方法,一次调用完成编码:
require "digest"
Digest::SHA256.base64digest("hello")
# => "LPJNul+wow4m6DsqxbninhsWHlwfp0JecwQzYpOLmCQ="
输出是带填充的标准 Base64 - 与 Base64.strict_encode64(Digest::SHA256.digest("hello")) 的产物相同 - 所以存、比较、粘贴都安全。唯一的决定是保持一致:用 Base64 生成的校验和清单,必须拿 Base64 输出去核对;同一个哈希的十六进制表示和 Base64 表示是不同的字符串,所以选一个,坚持下去。
把大家伙切成小块编码
和解码器一样,编码器也是基于缓冲区的:读完整输入,发出完整输出。标准库里没有流式编码器,所以面对大载荷,计划就是内存,而这道算术有一种令人愉悦的对称性。编码让数据变大三分之一,所以最大的分配是输出 - 不是输入 - 对于一个 1 GB 的文件,你应该预期面前会摆着大约 1.33 GB 的文本。
如果一次拿不住这么多,你可以分块编码,因为 Base64 字母表在三字节边界上是自同步的:把每个 3 字节切片独立编码,拼接结果与整体编码完全相同:
require "base64"
require "securerandom"
bin = SecureRandom.random_bytes(10_001)
whole = Base64.strict_encode64(bin)
chunked = bin.scan(/.{1,3}/m).map { |slice| Base64.strict_encode64(slice) }.join
puts chunked == whole
# => true
同一个技巧还给你一个手工折行器,与 encode64 完全一致:45 个字节永远编码成恰好 60 个字符,所以在 45 字节处切分输入、用换行拼接各段,就能逐行复现经典的 MIME 输出,一次只有一个切片在内存里:
def wrap_like_encode64(bin)
lines = bin.scan(/.{1,45}/m).map { |slice| Base64.strict_encode64(slice) }
lines.join("\n") + "\n"
end
bin = SecureRandom.random_bytes(10_001)
puts wrap_like_encode64(bin) == Base64.encode64(bin)
# => true
从命令行
编码也不需要脚本文件。一行流形式读文件,把它的 Base64 写到 stdout:
ruby -rbase64 -e 'print Base64.strict_encode64(File.binread(ARGV[0]))' payload.bin > payload.b64
管道形式读 stdin,这是你包装任何命令字节流的方式:
some_command | ruby -rbase64 -e 'print Base64.strict_encode64(STDIN.read)'
两处都保留 print - 一个多余的 puts 会给你的 Base64 追加换行,对 strict_encode64 的输出而言,那会把一枚干净的令牌变成坏掉的。和解码侧一样的经验法则:如果你输出的下一个消费者是严格的,那么除 Base64 本身外,什么都不能跟着同行。
让 Ruby 开发者多付字节的陷阱
- JSON 里的末尾换行。
Base64.encode64给每个非空结果以换行收尾,所以一个本应是干净令牌的值,会带着一个意外\n抵达你的 JSON 末尾。对任何要存储、比较或单行发送的东西,用strict_encode64。 - 令牌和 URL 里的 60 字符折行。同一个方法会把长输出折成多行。URL 里一个折行的字符串是两个 URL,一枚折行的令牌是坏掉的。还是那句话:
strict_encode64,或者如果你被encode64的输出绑住,就strip/delete掉换行。 - URL 里的加号和斜杠。查询字符串里的标准 Base64 意味着出门时对
%2B、%2F、%3D做百分号编码,然后指望对方解码它们。urlsafe_encode64在源头消灭这个问题。 - 填充放错了地方。JWT 和其他令牌方案要填充关闭;MIME 读取器可能受不了缺失的填充。只有当规范明确要求时才发出
padding: false,并且知道你的每个消费者站在那道围栏的哪一侧。 - 字符不是字节。一个带一个重音字母的 5 字符字符串在 UTF-8 里是 6 字节,输出长度算术跑在字节上。当编码结果“太长”时,数字节,别数字符。
- schema 设计里的三分之一税。一个 16 KB 的 BLOB 在 TEXT 列里变成大约 22 KB 的 Base64 字符串。按编码形式 - 而不是二进制形式 - 给你的列、缓存和 API 载荷定尺寸。
- 两个字母表,两个不同的字符串。同样的字节在标准字母表和 URL 安全字母表里编码结果不同,所以一个编码值只能与同一字母表的另一个值比较。永远不要比较或混用它们。
- Base64 不是锁。编码一个秘密不会让它变成秘密。任何拿到字符串的人都有你的数据;Base64 只控制字节看起来什么样,不控制谁能读它。
省下字节和 bug 的习惯
- 把
strict_encode64设为默认。输出一旦要住进 URL、cookie 或标识符,立刻换成urlsafe_encode64;只有目的地是邮件这类按行组织的文本协议时,才换成encode64。 - 保持线路两端编码器和解码器的字母表一致。最常见的“Base64 坏了”bug,就是一个标准字母表的生产者遇到了 URL 安全的消费者,或者反过来。
- 给编码器喂你打算编码的字节:文件用
File.binread,构造的二进制用pack,字符串就是数据时用 UTF-8 字符串。编码器不会质疑你的选择 - 它只是数字节。 - 给增长留预算。每当一个 Base64 字符串跨过边界进入有尺寸的容器,乘以 4/3,再给填充留一点余量。
- 用 Base64 换可携带性,永远不要换保密性。如果目标是让数据保持私密,工具是加密,Base64 只是你对密文之后要做的事。
Base64 如何成为 gem
在生命的大部分时间里,Base64 模块只是标准库里的一个文件,和 Ruby 许多最老的助手一样。严格和 URL 安全方法在 1.9 开发线期间加入了原始的一对 - 整个 base64 库带着全部四个方法于 2008 年 9 月加入 trunk,首次在 1.9.1(2009 年)发货,padding: 关键字则在 2015 年随 Ruby 2.3 到来。到那时,你今天看到的 API 的一切都已经定型 - 剩下的故事是关于这个模块如何发货的。
2020 年,Ruby 3.0 到来,核心团队开始把标准库抽成自己的 gem,base64 成了其中之一:版本 0.1.0,由核心贡献者在 ruby/base64 仓库维护。它作为默认 gem发货 - 随 Ruby 分发、永远可用,所以 require "base64" 零仪式地继续工作。版本 0.2.0 在 2023 年随 Ruby 3.3 跟进,加入了 Base64::VERSION 常量和一套丰富得多的文档。
然后 2024 年 12 月的 Ruby 3.4 重画了线:base64 从默认 gem 列表挪到了捆绑 gem列表,与 csv 和 drb 同一层架子。捆绑 gem 仍然随语言发货,但基于 Bundler 的项目被期望声明它们,所以如果你在 Ruby 3.4 或更新版本上、应用由 Bundler 驱动,把 gem "base64" 加进 Gemfile(或运行 gem install base64)就齐了。2025 年的 Ruby 4.0 带来版本 0.3.0,加上 RBS 类型签名,让静态检查器能正眼看这个模块。
贯穿整个旅程,实现保持它一直以来的样子:几十行纯 Ruby,裹在核心 pack 和 unpack 模板外面。没有 C 扩展,没有依赖,而且 - 在 rubygems.org 上下载量以亿计 - 平台上安装量最大的 gem 之一。
有趣的 Ruby 事实
- 整个模块,编码器在内,短到可以在一个咖啡休息里读完。
encode64字面就是[bin].pack("m"),strict_encode64是[bin].pack("m0"),而urlsafe_encode64是严格编码器加上两个字符的替换,你要它去掉填充它就去掉。 encode64的 60 字符折行既不匹配 MIME 的 76 字符上限,也不匹配 PEM 的经典 64。它只是mpack 模板一向所做的,mailgem 的 Base64 编码器还赞许地评论它:Ruby 的自动折行让输出保持在 SMTP 限制之内。- Ruby 的
Net::HTTP对 Basic 认证懒得用Base64模块 - 它直接调用pack模板,这是个不错的提醒:这个模块是核心之上的便利层,而不是反过来。 - 每个摘要类都带着一个
base64digest方法,所以Digest::SHA256.base64digest是hexdigest旁边的头等公民 - 文本形式的校验和,不用第二次调用。 - YAML 的
!binary标签是乔装的 Base64。你一 dump 一个 BINARY 字符串,Psych 就做编码,这就是为什么装满二进制的配置文件长那样。 - 你正在用的模块并不总像你记忆中的模块。老 Ruby 有
b64encode(按选定宽度折行)和decode_b(RFC 2047 头解码);两者都在 1.9 线里消失了,所以你继承的任何调用它们的 2010 年之前的代码,都会死于NoMethodError。 - YouTube 的视频 ID 是不带填充的 base64url - 11 个字符,无加号,无斜杠,无等号 - 恰恰是 URL 安全字母表被设计来处理的那种短小、链接安全的标识符。
另一面
现在你有了完整的编码图景:一匹从不让你意外的默认主力干将,一个为需要它的协议折行的经典,一个带填充开关的链接安全字母表,还有那些精确决定你的输出将长什么样的字节级规则。反方向 - 把一个 Base64 字符串拆开,在 Ruby 的三个解码器之间做选择,并把结果字节变成你能用的东西 - 有着它自己安静的陷阱,从一个从不说“不”的解码器开始。街道的那一侧,在下文链接的 Base64 解码文章里被深入覆盖。
最后更新: 2026-09-08
相关文章: Ruby 中的 Base64 解码:完整指南