目录
- 背景与目标
- 一、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 用什么算法签名,直接决定密钥怎么生成、怎么管理。这篇文章围绕三个问题展开:
- JWT 是什么:三段式结构、签名原理,以及 HS256 / RS256 / EdDSA 怎么选。
- Ed25519 密钥是什么:它和 RSA 有什么区别,为什么越来越流行。
- 本地怎么用 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 字节。
为什么值得选它:
- 快:签名、验签性能明显优于 RSA 和 ECDSA,适合高并发场景。
- 小:32 字节公钥 + 64 字节签名。对比 RSA-2048:公钥 256 字节、签名 256 字节。
- 确定性:同一份消息 + 同一把私钥,签出的签名永远相同(ECDSA 因带随机数,每次签名不同)。
- 工程友好:实现不依赖随机数生成器的正确性(防随机数漏洞)、无专利争议、抗侧信道攻击。
一个容易混淆的点:Ed25519 是签名算法,不是加密算法。它的密钥对解决的是「验证是谁签的、内容有没有被改」,而不是「把数据加密成密文」。需要加密时用 X25519/AES 等算法。
三、公钥和私钥的对应关系(重点)
一句话概括:
私钥是根,公钥是从私钥确定性推导出来的;私钥负责签名,公钥负责验签。
展开讲是四条:
- 单向推导:Ed25519 的公钥 = 私钥(32 字节种子)经 Edwards25519 曲线基点做标量乘法得到。给定私钥,必然能算出唯一的对应公钥。
- 不可逆:从公钥无法反推私钥——这是椭圆曲线离散对数问题的困难性保证的,也是整套机制的安全根基。
- 谁用哪个:签发方持有私钥做签名;任何拿到公钥的人(其他服务、网关、客户端)都可以验签,但只有持有私钥的人才能伪造签名。
- JWK 里看得最清楚:把 Ed25519 私钥导出为 JWK(JSON Web Key)格式,会同时看到私钥字段
d和公钥字段x——因为公钥就藏在私钥里(实测输出):
{"crv":"Ed25519","d":"o2IzaWtdUrLO1_TDx7E17YAq4kmq9oT9332V8quGR6Y","x":"VyfuOErNXXFKiQLhSprC3gFjNMZkKDiHmFrDxrNeLM4","kty":"OKP"}实测还验证了两点:用crypto.createPublicKey(私钥对象)从私钥导出的公钥,与原始公钥逐字节一致;同一把私钥对同一消息签名后,crypto.verify用公钥验签通过。
对应关系可以用下面这张图理解(详细版见随文配套的生态图页面):
四、本地用 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.pem和private_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,验证方自动拉取),无需保密。
六、踩过的坑与注意事项
- jsonwebtoken 不支持 EdDSA:9.0.3 实测报
"algorithm" must be a valid string enum value。用jose,或在 jsonwebtoken 的算法白名单里选 RS256/ES256。 - 验证必须显式约束算法:验签时若不限制算法,攻击者可能把
alg改成none或 HS256 做「算法混淆攻击」。jose.jwtVerify默认按密钥类型校验算法,安全性较好;自己实现时务必做算法白名单。 - 私钥泄露 = 全线失守:私钥能伪造任意用户的 token。不要提交到 Git 仓库,用环境变量或密钥管理服务(KMS/Vault)管理。
- 别把 Ed25519 当加密用:需要加密数据时用 X25519 + AES;Ed25519 只解决「签名与验签」。
- HS256 的对称密钥不能公开:HS256 只有一个共享密钥,一旦泄露,签和验都废了。多服务场景优先非对称。
- 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 在线调试