当前位置:首页 > 文章列表 > 文章 > 前端 > Web Crypto Ed25519怎样生成与验证签名

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

来源:17golang原创 2026-11-05 03:46:19 0浏览 收藏

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删除
JavaScript Iterator.prototype.drop怎样跳过前 N 个元素JavaScript Iterator.prototype.drop怎样跳过前 N 个元素
上一篇
JavaScript Iterator.prototype.drop怎样跳过前 N 个元素
CSS position-visibility怎样让锚定浮层随锚点离屏隐藏
下一篇
CSS position-visibility怎样让锚定浮层随锚点离屏隐藏
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    888次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    888次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    971次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    957次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    782次使用