登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  前端

Web Crypto Ed25519怎样生成与验证签名

来源:17golang原创

时间:2026-11-05 03:46:19 194浏览 收藏

Web Crypto 支持 Ed25519 的环境中,可用 generateKey("Ed25519", false, ["sign", "verify"]) 生成密钥对,把消息编码成字节后交给私钥签名,再用公钥、签名和相同消息字节调用 verify。成功调用的验签结果是布尔值;调用异常则通过 Promise 拒绝报告。

W3C Web Cryptography Level 2 草案列出了 Ed25519 的签名、验签和密钥操作。原生接口让页面可以直接使用 CryptoKey 对象,但是否可用仍取决于目标环境对算法的支持,不能只看 crypto.subtle 是否存在。

规范地址:https://www.w3.org/TR/webcrypto/#ed25519

用最小代码生成密钥并完成签名

以下例子为一份笔记版本声明签名。它演示完整调用关系,不建立真实用户身份,也没有在本文制作环境中执行。浏览器页面应部署在安全上下文中,通常使用 HTTPS。代码不导出私钥,不打印任何密钥材料。

async function createNoteSignature() {
  if (!globalThis.isSecureContext || !globalThis.crypto?.subtle) {
    throw new Error("需要支持 Web Crypto 的安全上下文");
  }

  const subtle = globalThis.crypto.subtle;
  // false 限制生成的私钥导出;签名与验签用途分别归属两把密钥。
  const pair = await subtle.generateKey(
    "Ed25519", false, ["sign", "verify"]
  );
  const message = new TextEncoder().encode(
    "note-revision:v1|note=42|revision=7"
  );
  const signature = await subtle.sign(
    "Ed25519", pair.privateKey, message
  );
  const valid = await subtle.verify(
    "Ed25519", pair.publicKey, signature, message
  );

  // 原消息的签名不应用于另一个版本声明。
  const changed = new TextEncoder().encode(
    "note-revision:v1|note=42|revision=8"
  );
  const changedValid = await subtle.verify(
    "Ed25519", pair.publicKey, signature, changed
  );
  return { pair, message, signature, valid, changedValid };
}

// 示例调用者只显示判断结果,不把密钥写入日志。
createNoteSignature()
  .then(({ valid, changedValid }) => {
    console.log({ valid, changedValid });
  })
  .catch(error => {
    console.error("签名示例未完成:", error.name);
  });

按此示例语义,原消息应得到 true,修改版本号的消息应得到 false。后一个判断使用原签名与新字节,不能重新为修改后的消息签名,否则就失去了对照意义。生产接口应将失败明确传回业务层,不可在 catch 中默认放行。

调用关键参数返回内容
generateKeyEd25519、extractable、用途数组包含 publicKey 与 privateKey 的密钥对
sign算法、privateKey、消息字节包含签名的 ArrayBuffer
verify算法、publicKey、签名字节、消息字节签名是否有效的布尔值
私钥签名、公钥验签与消息字节的静态调用关系
图1:密钥用途静态说明图。sign 依赖私钥与消息字节,verify 依赖公钥、签名字节与消息字节;连线不代表执行步骤或运行证据。

原生接口省去哪些适配工作

对于已有浏览器签名需求的项目,CryptoKey 让算法、密钥类型与用途附着在对象上,调用方无需为每次签名手工传递一份私钥字符串。Ed25519 参数直接使用算法名称即可,不照搬 ECDSA 的 namedCurve、hash 或 RSA-PSS 的 saltLength。

它适合协议明确要求 Ed25519 的内容声明、应用消息或离线材料验签。签名提供完整性判断;消息依然可读,需要保密时还要另行设计加密。算法名相似的 X25519 用于密钥协商,不能把它生成的密钥交给 Ed25519 签名。

与使用 JavaScript 或 WebAssembly 密码库相比,原生入口减少一层库接口适配;与服务端签名相比,密钥所在位置和信任模型发生了变化。是否改为浏览器持有私钥,应由业务的授权规则决定,不能只因原生调用较短就迁移。

公钥分发与字节格式怎样对齐

同一函数内的验签只能说明这组密钥与数据匹配。接收方验签时,应使用通过可信渠道取得的公钥,并保持签名字节与消息字节不变。下面的函数接收前例返回的材料,演示 raw 公钥的导出与导入,不涉及网络分发。

async function verifyWithImportedKey({ pair, message, signature }) {
  const subtle = globalThis.crypto.subtle;
  // raw 这里表示公钥字节,不是私钥,也不是 PEM 文本。
  const publicBytes = await subtle.exportKey("raw", pair.publicKey);
  const verificationKey = await subtle.importKey(
    "raw", publicBytes, "Ed25519", false, ["verify"]
  );
  return subtle.verify(
    "Ed25519", verificationKey, signature, message
  );
}

生成密钥时的 false 限制私钥可导出性;该算法生成的公钥仍可导出,用于交付验签方。导入公钥时用途只设 verify。raw、SPKI、JWK 是不同表示方式,接收方 importKey 的格式必须与发送方 exportKey 的格式对应,不能直接把带 PEM 标记的文本当作 raw 字节。

如果通过 JSON 传递二进制,应先约定 Base64 或 Base64URL,并在接收端解码回字节;签名文本本身不应再经过 TextEncoder 当作签名字节。消息则应约定 UTF-8、换行和字段表示。JSON 属性顺序、空格或 Unicode 表示差异都可能改变签名输入,不能依靠“看起来一样”判断。

公钥表示、签名字节和消息字节的静态验签输入边界
图2:验签材料静态说明图。公钥需要匹配导入格式,签名和消息必须还原为约定字节;图片不展示真实密钥或运行结果。

验签返回 false 和调用失败怎样区分

false 表示这次验签没有认定签名有效,通常应检查消息字节、公钥选择与签名内容。Promise 拒绝则说明操作未正常完成,例如密钥类型或用途不匹配、数据导入错误、算法不受支持。两类情况都不能作为授权成功,但排查路径不同。

先确认传入 verify 的是 publicKey,其 usages 包含 verify;再确认算法为 Ed25519,签名来自对应私钥。随后核对字节编码和消息拼接规则。算法不受支持时应提示功能不可用,不能默默更换算法后继续接受旧签名。

这里只给排查顺序,没有声称某个浏览器版本已经通过测试。API 本身可用与特定算法可用属于两个层次;兼容判断应覆盖项目目标运行时,而不是从另一个算法成功推断 Ed25519 成功。

接入项目时保留哪些约束

密钥对不应像演示代码一样在每次业务签名时随意重建,否则历史公钥无法对应后续消息。实际设计要明确密钥生命周期、公钥标识、轮换和撤销策略。公钥不保密,却必须能与可信签名者绑定;攻击者自行提交的公钥配上自己生成的签名,也能在数学上通过验签。

不可导出私钥不等于硬件密钥或用户在场授权。拥有页面执行权限的代码仍可能调用签名接口,因此需要限制脚本来源与签名权限。若用途是用户身份认证,应采用经过设计的认证协议,不能把这个小例子直接当作登录系统。

Ed25519 签名之前要先调用 digest 吗?

本例直接对协议消息字节签名。不要自行预哈希后仍声称签的是原消息;如果业务协议定义了预处理规则,签名端与验签端必须遵守同一规则。

verify 返回 true 就能证明没有重放吗?

不能。业务需要把用途、版本或挑战值等纳入约定消息,并在接收侧落实有效期、一次性使用等规则。签名接口不会自动判断业务时效。

公钥可以放到页面中吗?

可以公开验签公钥,但必须确保它的来源可信,且更新受到控制。把攻击者可修改的公钥与材料放在一起,不能建立可信来源。

参考:W3C Web Cryptography Level 2 的 Ed25519 草案定义、MDN sign 参数、MDN verify 返回值与异常。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>