💡 Key Takeaways
- What Base64 Actually Does (And Doesn't Do)
- When Base64 Is Actually the Right Choice
- The Performance Cost Nobody Talks About
- Security Misconceptions That Lead to Disasters
三年前,我看到我团队的一位初级开发人员将一个 50MB 的视频文件全部用 Base64 编码并直接嵌入到 JSON API 响应中。应用程序停滞不前。用户抱怨加载时间长达一分钟。我们的 CDN 成本一夜之间翻了三倍。当我问他为什么这么做时,他说:“我读过 Base64 使数据在互联网上传输是安全的。”
💡 关键要点
- Base64 实际做了什么(以及没有做什么)
- 何时 Base64 真的是正确的选择
- 没人谈论的性能成本
- 导致灾难的安全误解
他并不完全错误,但也不是对的。那次事件让我们花费了大约 $12,000 来紧急扩展基础设施,以及大约 200 小时的开发时间进行重构。这也让我学到了一些重要的东西:Base64 编码是一种表面上似乎简单但在实践中被严重误解的技术。
我是 Marcus Chen,我在过去的 14 年里一直在构建数据密集型应用程序——最初是在一家金融服务公司,每天处理数百万笔交易,然后在一家处理敏感患者数据的医疗初创公司,现在是一家向 50,000 多家企业提供服务的 SaaS 公司的首席工程师。在这段时间里,我见过 Base64 被出色地使用,也见过它的灾难性使用,往往在同一代码库中。
这篇文章是我试图澄清事实。我将解释 Base64 实际上做了什么,何时是合适的工具,何时绝对不是,以及如何做出明智的决策,以免在你的应用程序崩溃时让你在凌晨 3 点感到烦恼。
Base64 实际做了什么(以及没有做什么)
让我们从基础开始,因为我发现大多数 Base64 误用源于对它的基本误解。
Base64 是一种编码方案,它使用 64 个可打印字符(A-Z、a-z、0-9、+ 和 /)将二进制数据转换为 ASCII 文本。这就是全部。它不是加密。它不是压缩。它不是安全措施。它是一种翻译机制——就像将一本书从法语翻译成英语。内容没有改变;只有表示方式改变。
这是后台发生的事情:Base64 将每三个字节的输入数据(24 位)表示为四个 ASCII 字符(32 位)。这意味着你的数据在编码后增长了大约 33%。一个 1MB 的文件变成大约 1.33MB。一个 100MB 的数据库备份变成 133MB。这个开销不是微不足道的,而这是人们在选择 Base64 时最容易忘记的第一件事。
Base64 存在的原因是历史性的。在互联网早期,许多系统只能可靠地处理 7 位 ASCII 文本。二进制数据——图像、可执行文件、压缩文件——在通过电子邮件服务器传输、存储在用于文本设计的数据库中、或通过将某些字节值解释为控制字符的系统时会被损坏。Base64 通过确保二进制数据可以伪装成纯文本并能够完整地经历这些旅程而解决了这个问题。
我记得在 2012 年工作在一个遗留的电子邮件系统中,该系统会静默地损坏任何包含字节值大于 127 的消息。我们不得不对所有附件进行 Base64 编码才能将它们通过处理管道。但:大多数现代系统不再有这个限制。HTTP 可以很好地处理二进制数据。现代数据库有 BLOB 类型。文件系统并不关心你的数据是文本还是二进制。
然而开发人员仍然像我们还生活在 1996 年一样使用 Base64。为什么?因为这很简单,因为它很熟悉,并且看起来可以工作——直到它不这样做。
何时 Base64 真的是正确的选择
尽管我有警示性的故事,Base64 并不天生是坏的。在某些合情合理的情况下,它正是合适的工具。让我带你走过这些情况。
"Base64 是一种翻译机制,而不是安全措施。将其视为加密就像认为语言翻译者能保护你的秘密一样——它不能,只是让它们在不同的字母表中可读而已。"
最常见的有效用例是将小的二进制资产直接嵌入到基于文本的格式中。CSS 和 HTML 中的数据 URI 就是一个完美的例子。如果你有一个在应用程序的每个页面上出现的 2KB 图标,将其嵌入为 Base64 数据 URI 实际上可以通过消除一个 HTTP 请求来提高性能。计算很简单:HTTP 请求的开销(通常包含 DNS 查找、连接建立和服务器处理,通常为 50-200 毫秒)超过传输额外 667 字节的成本(2KB 的 33% 开销)。
我在重要的首屏资产上广泛使用这种技术。在一个项目中,我们通过将五个小的 SVG 图标(总共 8KB)直接嵌入到我们关键的 CSS 中,将初始页面渲染时间从 1.2 秒减少到 0.8 秒。增加的 2.6KB 的开销被消除的五个单独的 HTTP 请求完全抵消了。
另一个合法的用例是在真正只支持文本的系统中存储二进制数据。JSON 是显而易见的例子。JSON 没有本机的二进制类型,因此如果你需要在 JSON 负载中包含二进制数据——例如,在 API 响应中包含小的缩略图图像——Base64 是你唯一的选择。但请注意,我说的是“小”。我有一个硬性规定:绝不要对包含在 JSON 中的任何内容进行超过 50KB 的 Base64 编码。超过该阈值,你应该使用多部分请求、单独端点或直接二进制协议。
身份验证令牌和密码操作是另一个有效领域。JWT(JSON Web 令牌)使用 Base64URL 编码其头部和负载部分。这是有道理的,因为 JWT 需要在 HTTP 头和 URL 中传输,而这两者都是基于文本的上下文。令牌通常很小(在 2KB 以下),考虑到能够将它们作为简单字符串传递的便利性,33% 的开销是可以接受的。
我还在生成需要 URL 安全且比十六进制更紧凑的唯一标识符时使用 Base64。一个以十六进制编码的 128 位 UUID 是 32 个字符;相同的 UUID 以 Base64 编码只有 22 个字符。当你生成数百万个 ID 并将其存储在数据库索引中时,31% 的空间节省积累起来。在我建立的一个系统中,将主键从十六进制切换到 Base64URL 编码使我们的索引大小减少了 180GB。
没人谈论的性能成本
让我们谈谈数字,因为我发现关于“性能开销”的抽象警告不容易记住。具体的测量才会留下印象。
| 用例 | 何时使用 Base64 | 何时不使用 Base64 | 更好的替代品 |
|---|---|---|---|
| 小图像 | 小于 5KB 的图标,CSS/HTML 中的内联 SVG | 照片、大型图形、任何超过 10KB 的内容 | 使用适当缓存的 CDN 托管文件 |
| API 响应 | 小的二进制令牌,加密签名 | 文件下载、媒体内容、大型数据集 | 直接文件 URL 或流式端点 |
| 电子邮件附件 | MIME 编码的附件(标准协议) | 绝不能作为文件大小限制的变通办法 | 文件共享服务,云存储链接 |
| 数据库存储 | 仅支持文本的小型二进制数据的遗留系统 | 图像、文档、任何超过 1KB 的文件 | BLOB 列或单独文件存储 |
| 数据 URL | 微小资产以减少 HTTP 请求 | 任何频繁更改或大型的内容 | 单独可缓存的资源 |
我在一个典型的应用程序服务器(4 核心 Intel Xeon,16GB RAM)上运行了一系列基准测试,以编码和解码各种文件大小。将 10MB 文件编码为 Base64 的平均时间为 42 毫秒。解码它需要 38 毫秒。这听起来可能不算很多,但考虑一下:如果你在每个请求上都编码用户上传的图像,并且你处理每秒 100 个请求,你每秒只在 Base64 编码上就花费了 4.2 秒的 CPU 时间。这相当于一个完整的 CPU 核心完全用于编码开销。
内存影响更糟糕。因为 Base64 编码需要同时在内存中缓存整个输入和输出,因此编码那个 10MB 的文件实际上需要大约 23MB 的内存。