news 2026/9/21 20:34:26

JWT 与 Ed25519:从三段式结构到 Node.js 密钥实践

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JWT 与 Ed25519:从三段式结构到 Node.js 密钥实践

目录

    • 背景与目标
    • 一、JWT 是什么:三段式结构
      • 三种常见签名算法怎么选
    • 二、Ed25519 密钥是什么
    • 三、公钥和私钥的对应关系(重点)
    • 四、本地用 Node.js 生成密钥对
      • 4.1 推荐:crypto.generateKeyPairSync
      • 4.2 备选:Web Crypto API
    • 五、用 jose 签发和验证 JWT(实测跑通)
      • 5.1 先避一个坑:jsonwebtoken 不支持 EdDSA
      • 5.2 jose 完整示例
      • 5.3 谁持有谁
    • 六、踩过的坑与注意事项
    • 总结
    • 参考链接

一篇团队内部分享文档,讲清楚三件事:JWT 是什么、Ed25519 密钥是什么、怎么在本地用 Node.js 生成密钥对并签发/验证 JWT。文中所有代码均已在本环境(Node.js v22、jsonwebtoken 9.0.3、jose 5.10.0)实测跑通。

背景与目标

后端做登录鉴权、微服务之间校验身份,几乎绕不开 JWT;而 JWT 用什么算法签名,直接决定密钥怎么生成、怎么管理。这篇文章围绕三个问题展开:

  1. JWT 是什么:三段式结构、签名原理,以及 HS256 / RS256 / EdDSA 怎么选。
  2. Ed25519 密钥是什么:它和 RSA 有什么区别,为什么越来越流行。
  3. 本地怎么用 Node.js 生成密钥:生成公钥/私钥的具体代码、PEM 文件长什么样、公钥和私钥是什么对应关系

读完你可以自己生成一对 Ed25519 密钥,签发一个 JWT,再用公钥验证它——全程不到 10 行代码。


一、JWT 是什么:三段式结构

JWT(JSON Web Token,RFC 7519)是一串用.分成三段的字符串:

<Header>.<Payload>.<Signature>
  • Header(头):声明签名算法和类型,例如{"alg":"EdDSA","typ":"JWT"}
  • Payload(载荷):放业务声明(claims),如用户 ID、角色、过期时间exp
  • Signature(签名):对「头 + 载荷」两段做签名,防止内容被篡改。

下面是一个用 jose 实测签发出来的真实 JWT(演示用,密钥已废弃):

eyJhbGciOiJFZERTQSJ9.eyJ1c2VySWQiOjEwMDEsInJvbGUiOiJhZG1pbiIsImlhdCI6MTc4OTkxNDQzNSwiZXhwIjoxNzg5OTE4MDM1fQ.OQBwM9PELHtYORGJ6hCsJo1TFM_GG5iahfGiXJVM8E2M03QWdkHhtu7GUl_e5g1vfF75fRp2tLKRXhCBlHhUDQ

把第一、二段做 base64url 解码,就是 Header 和 Payload:

{"alg":"EdDSA","typ":"JWT"}{"userId":1001,"role":"admin","iat":1789914435,"exp":1789918035}

签名的计算方式是(EdDSA 算法下):

signature = sign( base64url(header) + "." + base64url(payload), privateKey )

三种常见签名算法怎么选

算法类型密钥特点与适用
HS256对称(HMAC)签和验用同一个密钥实现简单,但密钥要双方共享;适合单服务内部
RS256非对称(RSA)私钥签、公钥验公钥可公开分发;RSA 密钥长、运算慢
EdDSA(Ed25519)非对称(EdDSA)私钥签、公钥验密钥和签名都很短、速度快;多服务/跨团队验证的首选

一句话结论:只要存在「签发方」和「验证方」分离(网关、其他服务、第三方),就用非对称算法;新项目优先 Ed25519。


二、Ed25519 密钥是什么

Ed25519是 EdDSA(Edwards-curve Digital Signature Algorithm,爱德华曲线数字签名算法)在 Edwards25519 曲线上的具体实现,标准见RFC 8032。它由一对密钥组成:

  • 私钥(Private Key / Seed):32 字节随机数。自己保管,绝不外传,用来给数据签名。
  • 公钥(Public Key):32 字节。可以公开给任何人,用来验证签名。
  • 签名结果:固定 64 字节。

为什么值得选它:

  1. :签名、验签性能明显优于 RSA 和 ECDSA,适合高并发场景。
  2. :32 字节公钥 + 64 字节签名。对比 RSA-2048:公钥 256 字节、签名 256 字节。
  3. 确定性:同一份消息 + 同一把私钥,签出的签名永远相同(ECDSA 因带随机数,每次签名不同)。
  4. 工程友好:实现不依赖随机数生成器的正确性(防随机数漏洞)、无专利争议、抗侧信道攻击。

一个容易混淆的点:Ed25519 是签名算法,不是加密算法。它的密钥对解决的是「验证是谁签的、内容有没有被改」,而不是「把数据加密成密文」。需要加密时用 X25519/AES 等算法。


三、公钥和私钥的对应关系(重点)

一句话概括:

私钥是根,公钥是从私钥确定性推导出来的;私钥负责签名,公钥负责验签。

展开讲是四条:

  1. 单向推导:Ed25519 的公钥 = 私钥(32 字节种子)经 Edwards25519 曲线基点做标量乘法得到。给定私钥,必然能算出唯一的对应公钥。
  2. 不可逆:从公钥无法反推私钥——这是椭圆曲线离散对数问题的困难性保证的,也是整套机制的安全根基。
  3. 谁用哪个:签发方持有私钥做签名;任何拿到公钥的人(其他服务、网关、客户端)都可以验签,但只有持有私钥的人才能伪造签名
  4. JWK 里看得最清楚:把 Ed25519 私钥导出为 JWK(JSON Web Key)格式,会同时看到私钥字段d和公钥字段x——因为公钥就藏在私钥里(实测输出):
{"crv":"Ed25519","d":"o2IzaWtdUrLO1_TDx7E17YAq4kmq9oT9332V8quGR6Y","x":"VyfuOErNXXFKiQLhSprC3gFjNMZkKDiHmFrDxrNeLM4","kty":"OKP"}

实测还验证了两点:用crypto.createPublicKey(私钥对象)从私钥导出的公钥,与原始公钥逐字节一致;同一把私钥对同一消息签名后,crypto.verify用公钥验签通过。

对应关系可以用下面这张图理解(详细版见随文配套的生态图页面):

sign 签名

传递

verify 验签

签发方
持有私钥

JWT

验证方
持有公钥

签名有效?

信任 payload
userId=1001...

拒绝请求


四、本地用 Node.js 生成密钥对

4.1 推荐:crypto.generateKeyPairSync

Node.js 内置的node:crypto模块原生支持ed25519(无需安装任何依赖):

constcrypto=require('node:crypto');const{publicKey,privateKey}=crypto.generateKeyPairSync('ed25519',{publicKeyEncoding:{type:'spki',format:'pem'},// 公钥用 SPKI 编码的 PEMprivateKeyEncoding:{type:'pkcs8',format:'pem'},// 私钥用 PKCS8 编码的 PEM});console.log(publicKey);console.log(privateKey);

本环境实测输出(演示用密钥):

-----BEGIN PUBLIC KEY----- MCowBQYDK2VwAyEAVyfuOErNXXFKiQLhSprC3gFjNMZkKDiHmFrDxrNeLM4= -----END PUBLIC KEY----- -----BEGIN PRIVATE KEY----- MC4CAQAwBQYDK2VwBCIEIKNiM2lrXVKyztf0w8exNe2AKuJJqvaE/d99lfKrhkem -----END PRIVATE KEY-----

把输出写入文件即可得到public_key.pemprivate_key.pem

constfs=require('node:fs');fs.writeFileSync('public_key.pem',publicKey);fs.writeFileSync('private_key.pem',privateKey);

PEM 是什么:PEM 是「DER 二进制 + base64 文本 + 首尾标记行」的封装格式。公钥用 SPKI(Subject Public Key Info)标准,私钥用 PKCS8 标准——注意私钥默认是明文存放的,若担心泄露,可以加口令加密(privateKeyEncoding里传cipher: 'aes-256-cbc'passphrase)。

4.2 备选:Web Crypto API

Node.js 22+ 也支持标准的 Web Crypto API 生成 Ed25519 密钥:

const{subtle}=globalThis.crypto;constkeyPair=awaitsubtle.generateKey({name:'Ed25519'},true,['sign','verify']);// keyPair.privateKey / keyPair.publicKey 是 CryptoKey 对象// 通过 subtle.exportKey('pkcs8' | 'spki' | 'jwk', key) 导出

两者生成的密钥对可以互相兼容(都遵循同一标准),选哪个取决于你的代码风格:同步场景用generateKeyPairSync更顺手,浏览器/跨端统一用 Web Crypto。


五、用 jose 签发和验证 JWT(实测跑通)

5.1 先避一个坑:jsonwebtoken 不支持 EdDSA

最流行的jsonwebtoken库,目前不支持 Ed25519/EdDSA。本环境实测(jsonwebtoken 9.0.3):

constjwt=require('jsonwebtoken');jwt.sign({userId:1001},privateKey,{algorithm:'EdDSA'});// 报错: "algorithm" must be a valid string enum value

它只支持 HS/RS/ES/PS 系列。所以要用 Ed25519 签 JWT,推荐jose(标准化、零依赖、Node 和浏览器通用)。

5.2 jose 完整示例

安装:

npminstalljose

签发(持有私钥的一方):

constjose=require('jose');const{publicKey,privateKey}=awaitjose.generateKeyPair('EdDSA');// 生产环境一般从 PEM 文件导入:// const privateKey = await jose.importPKCS8(pemString, 'EdDSA');consttoken=awaitnewjose.SignJWT({userId:1001,role:'admin'}).setProtectedHeader({alg:'EdDSA'})// 算法固定为 EdDSA.setIssuedAt().setExpirationTime('1h').sign(privateKey);console.log(token);

验证(持有公钥的一方,比如网关或另一个微服务):

const{payload}=awaitjose.jwtVerify(token,publicKey);console.log(payload);// { userId: 1001, role: 'admin', iat: ..., exp: ... }

实测输出:签发的 token 验签通过,payload 完整还原。上面的真实 token 示例就是这段代码跑出来的。

5.3 谁持有谁

认证服务(签发):私钥 private_key.pem —— 只能放在服务端,绝不进前端代码 网关 / 资源服务 / 第三方(验证):公钥 public_key.pem —— 可以随便分发

公钥可以写进配置、注册到 JWKS 端点(/jwks返回公钥 JSON,验证方自动拉取),无需保密。


六、踩过的坑与注意事项

  1. jsonwebtoken 不支持 EdDSA:9.0.3 实测报"algorithm" must be a valid string enum value。用jose,或在 jsonwebtoken 的算法白名单里选 RS256/ES256。
  2. 验证必须显式约束算法:验签时若不限制算法,攻击者可能把alg改成none或 HS256 做「算法混淆攻击」。jose.jwtVerify默认按密钥类型校验算法,安全性较好;自己实现时务必做算法白名单。
  3. 私钥泄露 = 全线失守:私钥能伪造任意用户的 token。不要提交到 Git 仓库,用环境变量或密钥管理服务(KMS/Vault)管理。
  4. 别把 Ed25519 当加密用:需要加密数据时用 X25519 + AES;Ed25519 只解决「签名与验签」。
  5. HS256 的对称密钥不能公开:HS256 只有一个共享密钥,一旦泄露,签和验都废了。多服务场景优先非对称。
  6. PEM 私钥默认明文:落盘前按需加passphrase加密;生产环境至少保证文件权限 600。

总结

  • JWT= 三段式(头 + 载荷 + 签名),签名保证内容不可篡改;多服务鉴权优先非对称算法。
  • Ed25519= 基于 Edwards25519 曲线的 EdDSA 签名方案,32 字节私钥 + 32 字节公钥 + 64 字节签名,快、小、确定性强,是当前签名算法里性价比很高的选择。
  • 密钥对应关系:私钥是根,公钥由私钥单向推导;私钥签名、公钥验签;公钥无法反推私钥。
  • Node.js 生成crypto.generateKeyPairSync('ed25519')一条命令拿到 PEM 文件;JWT 签发/验证用jose(jsonwebtoken 实测不支持 EdDSA)。

下一步建议:在本地跑一遍第四节和第五节的代码,生成自己的密钥对,试着用公钥去验签、把exp改成过去的时间看验证如何失败——跑通一遍,这套机制就真正是你的了。


参考链接

  • RFC 7519 - JSON Web Token (JWT)
  • RFC 8032 - Edwards-Curve Digital Signature Algorithm (EdDSA)
  • Node.js Crypto 文档
  • Node.js Web Crypto API 文档
  • jose(GitHub)
  • jsonwebtoken(GitHub)
  • JWT.io - JWT 在线调试
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/21 20:32:30

第006篇 面试必问·快手JavaScript 语言核心:var、let、const 的区别与暂时性死区

摘要:本篇复盘 快手 前端开发岗位在 JavaScript 语言核心 方向的真实问法,重点拆 8 道题:防抖与节流分别怎么实现,适用场景有何不同、进程与线程的区别,协程、如何设计一个支持高并发的秒杀系统。每题按「考察点 → 参考答案 → 代码/实操 → 易错点 → 面试官追问」五段…

作者头像 李华
网站建设 2026/9/21 20:28:40

二进制字符串转换与算法优化实战

1. 题目解析与算法思路1.1 Problem A&#xff1a;二进制字符串转换问题这道题目要求我们处理一个由0和1组成的二进制字符串。核心思路是通过识别字符串中的分界点&#xff08;即连续的0或开头/结尾的0&#xff09;&#xff0c;将字符串分割为若干个独立的"连续1序列"…

作者头像 李华
网站建设 2026/9/21 20:26:35

企业微信朋友圈自动化发布技术解析

1. 项目背景与核心价值去年服务某零售客户时&#xff0c;他们的运营团队每天需要手动在200多个企业微信账号的朋友圈发布相同内容。运营总监向我吐槽&#xff1a;光是切换账号就要花掉2小时&#xff0c;还经常漏发错发。这促使我开始研究企微朋友圈自动化发布的解决方案&#x…

作者头像 李华
网站建设 2026/9/21 20:22:52

Spring IoC容器核心原理与最佳实践

1. Spring IoC 容器核心解析Spring框架最核心的设计思想就是IoC&#xff08;控制反转&#xff09;&#xff0c;它彻底改变了Java应用程序中对象创建和依赖管理的方式。在传统编程模式中&#xff0c;对象通常直接通过new关键字实例化并管理自己的依赖关系&#xff0c;这种方式会…

作者头像 李华