MD5 和 API token 外表相似但功能相反
外表的欺骗
API token 看起来像 MD5 哈希值。都是字符串。十六进制数字排列成某种模式。对未经训练的眼睛来说,它们可以互换。
这种相似性是欺骗性的。
MD5 接收输入,通过计算产生输出。它是确定性的。给它相同的数据,得到相同的结果。它是一个函数——纯粹的转换,没有记忆或状态。
API token 是被生成的,不是被计算的。它被分配给你。它代表身份和权限。它携带权威。改变一个字符,你就变成了另一个人——或者根本不是任何人。
💡 Click the maximize icon to view in fullscreen
格式匹配。功能分离。
为什么我们混淆它们
两者都表现为不透明的字符串。你无法通过观察从中读出意义。哈希值是 5d41402abc4b2a76b9719d911017c592。token 是 ghp_16C7e42F292c6912E7710c838347Ae178B4a。两者都不会告诉你它代表什么。
这种不透明性造成了混淆。我们把它们当作黑盒。我们复制粘贴它们到配置文件中。我们通过网络发送它们。我们把它们存储在数据库里。
但它们的不透明性服务于不同的目的。
MD5 的不透明性是关于不可逆性。你无法从输出反推到输入。这保护了数据完整性。你验证数据没有改变,而不需要透露数据是什么。
token 的不透明性是关于保密性。它必须是不可猜测的。如果有人预测了你的 token,他们就变成了你。随机性不是为了验证——而是为了通过混淆和熵来保证安全。
你可能会想:如果 MD5 产生不透明的、看起来独特的字符串,为什么不用它来生成 token?
答案在于可逆性和可预测性。MD5 是确定性的——相同的输入总是产生相同的输出。如果你的 token 生成使用了可预测数据的 MD5(比如用户名或时间戳),攻击者可以计算可能的 token 并尝试它们。
Token 必须从高熵随机性中生成,而不是从输入中计算。
现代 token 生成使用加密安全的随机数生成器(CSPRNG),而不是哈希函数。目标不是转换——而是不可预测性。
功能的分野
考虑一下使用每一个时会发生什么:
使用 MD5:
- 你有数据
- 你应用一个函数
- 你得到一个指纹
- 任何有相同数据的人都得到相同的指纹
- 指纹证明数据没有改变
使用 token:
- 有人信任你
- 他们给你一个凭证
- 你出示那个凭证
- 他们验证它与他们的记录匹配
- 凭证证明你声称的身份
MD5 是数学的。Token 是社会的。
哈希值不在乎谁计算它。Token 绝对在乎谁持有它。
相似性的幻觉
我们看到两个十六进制字符串就假设等价。但格式不是功能。外表不是本质。
这是技术中的一个更广泛的模式。看起来相似的东西服务于不同的目的:
- 随机 UUID 和加密的 ID 都看起来像乱码——一个是标签,另一个是受保护的信息
- base64 编码的图像和 base64 编码的凭证都看起来像字母数字字符串——一个是数据,另一个是权威
- 加密签名和消息摘要都把输入转换为输出——一个证明作者身份,另一个证明完整性
危险在于把它们互换使用。在需要认证的地方使用哈希值。在需要验证的地方存储 token。把计算误认为身份。
永远不要把 MD5 哈希值当作 token 使用:
- 不要把用户 ID 的哈希值当作 API 密钥 — 可预测的输入产生可猜测的输出
- 不要使用 MD5 存储密码 — 彩虹表可以反向破解常见密码
- 不要把哈希值当作秘密 — 它们是指纹,不是凭证
也永远不要把 token 当作哈希值使用:
- 不要从数据计算 token — 它们必须是随机的
- 不要假设 token 能验证数据完整性 — 它们只验证身份
- 不要公开分享 token — 与哈希值不同,它们授予访问权限
什么决定了身份与完整性
核心问题是:你在解决什么问题?
如果你需要知道"这个改变了吗?"——使用哈希值。你在验证完整性。你在确保数据没有被篡改。你在创建输入和输出之间的数学关系。
如果你需要知道"这是谁?"——使用 token。你在验证身份。你在确保持有者有权限。你在创建用户和系统之间的社会关系。
格式——一串字符——掩盖了这个根本差异。两者看起来都很技术性。两者都涉及密码学。两者都处理敏感操作。
但一个是关于数学。另一个是关于信任。
启示
相似不是等价。看起来相似的工具可以服务于相反的目的。
当你看到 MD5 哈希值时,你看到的是计算——一个把输入转换为输出的确定性函数。它是可重复的、可验证的、数学的。
当你看到 API token 时,你看到的是凭证——一个由权威分配的唯一标识符。它是随机的、单一的、社会的。
差异很重要。理解它可以防止安全错误。它澄清架构。它分离关注点。
技术充满了这些错误的等价。看起来相似但意义不同的字符串。格式匹配但功能分离。
启示很简单:看穿表面。格式欺骗。功能揭示。
参考资料
Related Posts
Articles you might also find interesting
管理后台需要两次设计
第一次设计回答"发生了什么",第二次设计回答"我能做什么"。在第一次就试图解决所有问题,结果是功能很多但都不够深入。
告警分级与响应时间
不是所有问题都需要立即响应。RPC失败会在凌晨3点叫醒人。安全事件每15分钟检查一次。支付成功只记录,不告警。系统的响应时间应该匹配问题的紧急程度。
文档标准是成本计算的前提
API文档不只是写给开发者看的。它定义了系统的边界、成本结构和可维护性。统一的文档标准让隐性成本变得可见。
BullMQ 队列
队列不是技术选型,而是对时间的承认,对顺序的尊重,对不确定性的应对
BullMQ Worker
Worker 的本质是对时间的重新分配,是对主线的解放,也是对专注的追求
配置不会自动同步
视频生成任务永远pending,代码完美部署,队列正确配置。问题不在代码,在于配置的独立性被低估。静默失败比错误更危险。
CRUD 操作
四个字母背后,是数据的生命周期,是权限的边界,也是系统设计的基础逻辑
数据库参数国际化:从 13 个迁移学到的设计原则
数据不该懂语言。当数据库参数嵌入中文标签时,系统的边界就被语言限制了。这篇文章从 13 个参数对齐迁移中提炼出设计原则——国际化不是功能,是系统设计的底层约束。
Stripe Webhook中的防御性编程
三个Bug揭示的真相:假设是代码中最危险的东西。API返回类型、环境配置、变量作用域——每个看似合理的假设都可能导致客户损失。
双重验证:Stripe生产模式的防御性切换
从测试到生产不是更换API keys,而是建立一套双重验证系统。每一步都在两个环境中验证,确保真实支付不会因假设而失败。
错误隔离
失败是必然的。真正的问题不是失败本身,而是失败如何蔓延。错误隔离不是为了消除失败,而是为了控制失败的范围。
在运行的系统上生长新功能
扩展不是推倒重来,而是理解边界,找到生长点。管理层作为观察者和调节器,附着在核心系统上,监测它,影响它,但不改变它的运行逻辑。
实现幂等性处理,忽略已处理的任务
在代码层面识别和忽略已处理的任务,不是简单的布尔检查,而是对时序、并发和状态的深刻理解
单例模式管理 Redis 连接
连接不是技术细节,而是系统与外部世界的第一次握手,是可靠性的起点
缺失值的级联效应
一个NULL值如何在调用链中传播,最终导致错误的错误消息。理解防御层的设计,在失败传播前拦截。
监控观察期法
部署不是结束,而是验证的开始。修复代码只是假设,监控数据才是证明。48小时观察期:让错误主动暴露,让数据证明修复。
Props Drilling
数据在组件树中层层传递,每一层都不需要它,却必须经手。这不是技术债,是架构对真实需求的误解。
队列生产者实例的工厂函数
工厂函数不是设计模式的炫技,而是对重复的拒绝,对集中管理的追求,对变化的准备
监听 Redis 连接事件 - 让不可见的脆弱变得可见
连接看起来应该是透明的。但当它断开时,你才意识到透明不等于可靠。监听不是多余,而是对脆弱性的承认。
资源不会消失,只会泄露
在积分系统中,用户的钱不会凭空消失,但会因为两个时间窗口而泄露:并发请求之间的竞争,和回调永不到达的沉默。
RPC函数的原子化处理
当一个远程函数做太多事情,失败就变得难以理解
RPC函数
关于远程过程调用的本质思考:当你试图让远方看起来像眼前
使用Secret Token验证回调请求的合法性
在开放的网络中,信任不能被假设。Secret Token 是对身份的确认,对伪装的识别,也是对安全边界的坚守
第三方回调的状态映射完整性
KIE.AI 视频生成的三个修复揭示了一个本质问题:回调不只是接收结果,是建立状态映射。没有 vendor_task_id,系统就失去了追溯能力。
What Monitoring Systems See
Production logs showed errors everywhere. But most weren't errors at all. When test webhooks generate error-level logs, and successful validations leak customer emails, the monitoring system loses its ability to tell you what's actually wrong.