在产品开发中,API 密钥是连接客户端与后端服务的核心凭证。大多数开发者对 API 密钥的认知停留在「生成一串随机字符串」层面,但当系统复杂度提升时,一系列设计决策问题便随之浮现。本文作者在多租户分片系统中实现 API 密钥功能时,经历了格式设计、熵值计算、分片路由、性能优化等多轮权衡,最终沉淀出一套可复用的设计决策框架。
密钥格式:从行业惯例到可扩展设计
业界主流的 API 密钥格式通常由三部分组成:前缀标识符、随机主体与校验和。前缀用于标识密钥类型或环境,例如 GitHub 使用 ghp/gho 等前缀区分个人令牌与 OAuth 令牌,Stripe 使用 sk_live/sk_test 区分生产与测试环境。这一设计并非单纯的美观需求,而是为开发者和安全系统提供快速的上下文识别能力。当用户在控制台复制密钥时,前缀能帮助其判断当前使用的是哪种类型的凭证;当密钥泄露到日志或错误信息中时,安全系统也可基于前缀快速识别问题密钥的适用范围。
随机主体是密钥的核心,通常采用十六进制字符串表示,长度在 32 到 64 个字符之间不等。校验和部分则通过特定算法计算得出,附加在主体之后或通过下划线分隔。这一设计的关键价值在于前置校验:系统可在查询数据库前先验证密钥格式是否合法、校验和是否匹配,从而避免无效请求直接击穿存储层。对于大规模 API 服务而言,这一层前置过滤能显著降低数据库负载。
存储层面,API 密钥必须以哈希形式存入数据库,这一实践与密码存储完全一致。生成时将明文密钥一次性返回给用户,此后系统只保留哈希值。即便数据库被攻破,攻击者也无法恢复出有效的密钥。此外,许多系统会保留密钥的前几个字符用于展示,帮助用户在控制台区分多个密钥,而完整的哈希值则用于后续的权限校验与使用审计。
熵值计算:安全性与可用性的平衡点
熵是评估密钥随机性强度的核心指标。128 位熵是业界公认的安全基准,对于大多数应用场景已足够。对于高风险或自动化调用频繁的服务,192 至 256 位熵能提供更强的抗暴力破解能力。
在具体实现中,熵值由两个因素共同决定:字符集大小与密钥长度。以 62 字符的字母数字集为例,32 个字符可产生约 190 位熵,接近 256 位目标。若采用 URL 安全的 Base64 编码,字符集扩展至 64 个,同样长度下熵值进一步提升。作者在实践中选择将 SHAKE256 算法的 32 字节输出编码为 10 个 Base64url 字符,既满足了熵值要求,又将存储空间压缩至可接受范围。
碰撞概率是另一个需要量化的指标。基于生日悖论计算,8 字符的 Base64url 编码约存在 281 万亿种可能,10 字符则提升至 72 千万亿级别。对于百万级租户的系统而言,10 字符编码的碰撞概率已低至可忽略不计。值得注意的是,碰撞概率的量化评估应作为设计文档的一部分记录在案,而非仅凭主观直觉选择长度。
分片路由:多租户系统中的特殊挑战
当系统采用分片架构时,API 密钥设计面临一个额外的复杂度:如何在密钥进入数据库前确定目标分片?传统方案依赖会话 Cookie 中的账户 ID 进行路由,但 API 密钥本身不包含任何账户上下文信息。
作者在实践中评估了三种可行方案。第一种是直接映射法:将密钥哈希与账户 ID 的映射关系存入元分片数据库,查询时先通过哈希定位账户,再路由到对应分片。测试表明,在 2000 万条记录规模下,该方案读写性能表现优异,但存储冗余度较高 —— 元分片需要额外维护一套与各分片重复的映射关系。
第二种是前缀路由法:为每个租户分配唯一的前缀,密钥生成时将前缀固定嵌入随机字符串前方。这样,前缀本身就成为分片路由的索引键,元分片只需维护前缀到账户的映射关系,存储空间显著降低。这一方案的代价是前缀可预测,理论上增加了碰撞风险,但在实际应用中这一风险微乎其微。
第三种是编码派生法:通过 Base62 或 Base70 等编码算法处理密钥哈希,取编码结果的前几位作为路由键。作者初期曾尝试这一方案,并在测试中观察到 Base70 编码 8 字符的性能接近 SHA 哈希,但后续经更严谨的基准测试发现,JavaScript 中 BigInt 的任意精度运算导致编码性能远低于预期。B 树索引对字符串的排序查询优化使得直接使用 SHA 哈希的方案反而更快。最终作者转向 SHAKE 算法 ——SHA3 家族的可变输出变体,通过 Sponge 结构吸收全部输入后按需挤出固定长度的输出,在保持高性能的同时将输出压缩至 Base64url 编码的 10 个字符。
哈希算法选型:性能与安全的多维考量
传统 SHA-256 生成 64 字符的十六进制字符串,输出长度大、存储成本高,但 B 树索引对字符串的高效支持使其查询性能并未明显下降。Base62 或 Base70 编码方案试图通过更紧凑的表示减少存储与索引大小,却因 BigInt 运算的软件实现开销适得其反。
SHAKE256 算法的引入解决了这一矛盾。作为 SHA3 家族的一员,SHAKE 采用 Sponge 结构将输入「吸收」到 1600 比特的内部状态中,然后「挤出」任意长度的输出。这一特性允许设计者精确控制输出长度 ——32 字节输入经 SHAKE256 挤出后编码为约 43 个 Base64url 字符,若只需 10 个字符则截断使用即可。基准测试显示,SHAKE256 32 字节输出经 Base64url 编码后的 10 字符方案,在 100 万条记录、1000 个测试密钥、100 轮测试的环境下,中位数查询时间优于传统 SHA-256 方案,同时索引体积减少约 35%。
需要强调的是,SHAKE 算法相较于 SHA-2 系列提供了不同的安全模型 —— 后者基于 Merkle-Damgård 结构,而前者基于海绵结构。对于 API 密钥场景,两者的安全性均已足够,关键差异在于输出长度的灵活性与计算效率的微调。
轮换策略与运维实践
密钥轮换是从业者容易忽视但至关重要的安全实践。短周期令牌配合自动轮换机制能显著降低单点泄露的影响范围。对于必须使用长期密钥的场景,应强制实施定期轮换策略,并在控制台提供一键轮换功能。
轮换实现有两种常见模式:双密钥平滑迁移与单密钥即时失效。双密钥模式下,系统为每个客户端同时签发当前密钥与下一个密钥,客户端在后台自动切换至新密钥后,旧密钥在宽限期结束后自动失效。这一模式对客户端有一定侵入性,但用户体验最平滑。单密钥模式下,旧密钥在签发新密钥后立即失效,适用于密钥已泄露需要紧急撤销的场景。
审计日志同样是密钥系统不可或缺的组成部分。每次密钥创建、轮换、撤销以及使用事件都应记录到审计日志中,记录内容包括操作时间、操作者 IP、关联的账户与权限范围。这些日志是异常检测与事后追踪的基础数据源。
设计决策 Checklist
综合上述分析,API 密钥系统设计应覆盖以下关键决策点:密钥格式需包含明确的前缀标识、足够的随机主体与可选的校验和;熵值目标建议 128 位为基准、192 至 256 位为强化目标,通过字符集大小与长度计算达成;存储必须使用强哈希算法(如 Argon2、bcrypt 或 SHA-256)配合唯一盐值,禁止明文存储;分片路由需根据业务架构选择直接映射、前缀派生或编码派生方案,并通过基准测试验证选型;轮换策略应明确定义轮换周期、宽限期与失效机制;审计日志需覆盖全生命周期事件并确保不可篡改。
这些决策并非孤立存在,而是相互影响 —— 分片路由方案决定了索引结构,索引结构影响哈希算法选型,熵值要求又与碰撞风险直接关联。理解这些关联并在设计阶段通盘考量,才能构建出既安全又高效的 API 密钥系统。
参考资料
- Vjaylakshman K, My adventure in designing API keys