---

title: API签名与非对称验签(时间戳/Nonce/窗口)最佳实践

keywords:

  • API签名
  • RSA
  • ECDSA
  • 公钥
  • 私钥
  • 时间戳
  • Nonce
  • 重放窗口
  • kid
  • Base64URL

description: 使用RSA/ECDSA实现非对称API签名与验签,结合时间戳与Nonce重放窗口控制与kid密钥标识,附签名生成与服务端校验示例及参数约束。

categories:

  • 文章资讯
  • 技术教程

---

一、参数与风险

  • 风险:伪造与重放、过期窗口过长、kid失配与密钥轮换不当。
  • 基线:固定算法与头字段、严格时间戳窗口与唯一Nonce、kid映射公钥。

二、Base64URL与签名

import crypto from 'crypto'

function b64url(input: Buffer): string { return input.toString('base64').replace(/=/g,'').replace(/\+/g,'-').replace(/\//g,'_') }

function signRsaSha256(privateKeyPem: string, data: string): string {
  const s = crypto.createSign('RSA-SHA256')
  s.update(data)
  s.end()
  const sig = s.sign(privateKeyPem)
  return b64url(sig)
}

function verifyRsaSha256(publicKeyPem: string, data: string, sigB64u: string): boolean {
  const v = crypto.createVerify('RSA-SHA256')
  v.update(data)
  v.end()
  const sig = Buffer.from(sigB64u.replace(/-/g,'+').replace(/_/g,'/'), 'base64')
  return v.verify(publicKeyPem, sig)
}

三、头字段与数据格式

type Req = { headers: Record<string, string | undefined>; rawBody: string }
type Res = { status: (n: number) => Res; end: (b?: string) => void }

function canonicalPayload(path: string, body: string): string {
  return path + '\n' + body
}

四、Nonce窗口与kid映射

class NonceStore {
  keep = new Set<string>()
  add(n: string) { this.keep.add(n) }
  has(n: string): boolean { return this.keep.has(n) }
}

type KeyMap = Record<string, string>

五、服务端校验

function nowSec(): number { return Math.floor(Date.now()/1000) }

function verifyApiSignature(req: Req, res: Res, keys: KeyMap, windowSec: number, nonces: NonceStore): boolean {
  const kid = req.headers['x-kid'] || ''
  const sig = req.headers['x-signature'] || ''
  const ts = Number(req.headers['x-timestamp'] || '0')
  const nonce = req.headers['x-nonce'] || ''
  const pub = keys[kid]
  if (!pub) return false
  if (!Number.isFinite(ts)) return false
  if (Math.abs(nowSec() - ts) > windowSec) return false
  if (nonces.has(nonce)) return false
  const data = canonicalPayload(req.headers['x-path'] || '/', req.rawBody)
  const ok = verifyRsaSha256(pub, `${ts}.${nonce}.${data}`, sig)
  if (!ok) return false
  nonces.add(nonce)
  return true
}

六、验收清单

  • 固定算法与头字段:X-Kid/X-Signature/X-Timestamp/X-Nonce/X-Path;窗口≤300
  • kid映射到公钥并支持轮换;nonce唯一性通过;数据采用规范化格式。
  • 验签失败拒绝并记录审计,包含kid与来源路径。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部