---

title: HTTP消息签名与验证(Signature-Input/Signature)最佳实践

keywords:

  • HTTP Message Signatures
  • Signature-Input
  • Signature
  • 组件列表
  • ES256

description: 基于Signature-Input与Signature头的HTTP消息签名与验证,通过组件列表与规范化拼接、ES256签名与窗口校验,保障请求完整性与不可抵赖性。

categories:

  • 文章资讯
  • 技术教程

---

背景与价值

HTTP消息签名以标准化组件列表构建签名基串并下发签名头,资源端可验证消息完整性与来源可信度,降低重放与篡改风险。

统一规范

  • 组件列表:明确签名包含的头、方法与路径(如 @method@pathcontent-digest)。
  • 算法限定:采用 ES256keyid 标识公钥并支持轮换。
  • 时间窗口:可选 created/expires 提供短期有效窗口。

核心实现

组件与基串构建

type Req = { method: string; path: string; headers: Record<string, string | undefined> }

type Component = '@method' | '@path' | string // header name in lower-case

function h(req: Req, k: string): string { return (req.headers[k] || '').trim() }

function buildBase(req: Req, components: Component[]): string {
  const lines: string[] = []
  for (const c of components) {
    if (c === '@method') lines.push(`"@method": ${req.method.toLowerCase()}`)
    else if (c === '@path') lines.push(`"@path": ${req.path}`)
    else lines.push(`"${c}": ${h(req, c)}`)
  }
  return lines.join('\n')
}

function buildSignatureInput(components: Component[], params: { alg: 'ES256'; keyid: string; created?: number; expires?: number }): string {
  const items = components.map(c => c).join(' ')
  const p: string[] = [`alg=${params.alg}`, `keyid="${params.keyid}"`]
  if (params.created) p.push(`created=${params.created}`)
  if (params.expires) p.push(`expires=${params.expires}`)
  return `(${items});` + p.join('; ')
}

签名与验证(ES256)

function enc(s: string): Uint8Array { return new TextEncoder().encode(s) }
function b64u(b: ArrayBuffer): string { const u = new Uint8Array(b); let s=''; for (let i=0;i<u.length;i++) s+=String.fromCharCode(u[i]); return btoa(s).replace(/\+/g,'-').replace(/\//g,'_').replace(/=+$/,'') }
function buf(s: string): ArrayBuffer { const b = atob(s.replace(/-/g,'+').replace(/_/g,'/')); const u = new Uint8Array(b.length); for (let i=0;i<b.length;i++) u[i]=b.charCodeAt(i); return u.buffer }

async function importPrivateKey(pkcs8: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('pkcs8', pkcs8, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['sign']) }
async function importPublicKey(spki: ArrayBuffer): Promise<CryptoKey> { return crypto.subtle.importKey('spki', spki, { name: 'ECDSA', namedCurve: 'P-256' }, false, ['verify']) }

async function signBase(base: string, key: CryptoKey): Promise<string> { const raw = await crypto.subtle.sign({ name: 'ECDSA', hash: 'SHA-256' }, key, enc(base)); return b64u(raw) }
async function verifyBase(base: string, sigB64u: string, key: CryptoKey): Promise<boolean> { return crypto.subtle.verify({ name: 'ECDSA', hash: 'SHA-256' }, key, buf(sigB64u), enc(base)) }

窗口校验

function timeNow(): number { return Math.floor(Date.now()/1000) }
function within(created?: number, expires?: number, leeway = 300): boolean { const n = timeNow(); if (created && created - leeway > n) return false; if (expires && expires + leeway < n) return false; return true }

落地建议

  • 在请求包含 Signature-InputSignature;组件与参数明确,启用时间窗口。
  • 资源端按组件构建基串并验签,校验 created/expireskeyid 轮换策略。

验证清单

  • 组件列表是否正确;签名算法与基串校验是否通过;时间窗口是否有效。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部