JWT 实现 HS、RS、ES 和 ED 签名与验签
签名方式 | 算法 | 密钥类型 | 签名要点 | 验签要点 |
---|---|---|---|---|
HS | HMAC-SHA256 | 对称密钥 | - 使用 crypto/hmac 和对称密钥生成 HMAC 签名- 将 header.payload 作为数据输入 | - 使用同一密钥重新计算 HMAC 签名 - 比较计算结果与接收到的签名是否一致 |
RS | RSA-SHA256 | 公钥 + 私钥 | - 使用 crypto/rsa 生成 RSA 签名- 私钥签名,输入为 header.payload 的哈希值 | - 使用 crypto/rsa 验证 RSA 签名- 公钥解密签名后,验证是否与输入哈希值匹配 |
ES | ECDSA-P256 | 公钥 + 私钥 | - 使用 crypto/ecdsa 生成 ECDSA 签名- 签名结果为 (r, s) ,序列化并编码为 Base64URL | - 使用 crypto/ecdsa 验证签名- 解析签名为 (r, s) ,验证其与 header.payload 的哈希匹配 |
ED | Ed25519 | 公钥 + 私钥 | - 使用 crypto/ed25519 私钥直接签名完整的 header.payload 数据- 签名结果无需额外哈希处理 | - 使用 crypto/ed25519 公钥直接验证签名是否匹配完整数据 |
签名与验签实现重点
-
es算法签名和验签时算法位数必须相同:ES算法在验签时必须严格使用与签名时相同位数的算法进行验证,这一点与其他算法有所不同。(其他算法不必相同) 说明如下:
-
加密
-
验签
-
Base64URL 编码:JWT 的
Header
和Payload
都需编码。 -
数据输入:签名计算与验证的输入数据始终是
Base64URL(Header) + "." + Base64URL(Payload)
。 -
密钥管理:对称密钥 (HS) 要妥善分发,公私钥对 (RS/ES/ED) 要安全存储。
go案例
hs.go
package jwtex
import (
"github.com/golang-jwt/jwt/v5"
"log"
)
type HS struct {
Key string
SignMethod HSSignMethod
}
type HSSignMethod string
const (
HS256 HSSignMethod = "HS256"
HS384 HSSignMethod = "HS384"
HS512 HSSignMethod = "HS512"
)
// hs HMAC(Hash-based Message Authentication Code)用的hash-based
func (hs *HS) getSignMethod() *jwt.SigningMethodHMAC {
// *jwt.SigningMethodHMAC 是 jwt.SigningMethod 接口的具体实现之一。通过返回具体的实现类型,
// 可以确保你使用的是 HMAC 签名方法,而不是其他类型的签名方法(如 RSA 或 ECDSA)。
switch hs.SignMethod {
case HS256:
return jwt.SigningMethodHS256
case HS384:
return jwt.SigningMethodHS384
case HS512:
return jwt.SigningMethodHS512
default:
return jwt.SigningMethodHS256
}
}
// Sign 签名
func (hs *HS) Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(hs.getSignMethod(), data)
sign, err := token.SignedString([]byte(hs.Key))
if err != nil {
log.Println(err)
return "", err
}
return sign, nil
}
// Verify 验签,获取数据
func (hs *HS) Verify(sign string, data jwt.Claims) error {
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return []byte(hs.Key), nil
})
return err
}
rsa.go 私钥签名、公钥验证
package jwtex
import (
"github.com/golang-jwt/jwt/v5"
"log"
)
type RS struct {
SignMethod RSSignMethod
PublicKey string
PrivateKey string
}
type RSSignMethod string
const (
RS256 RSSignMethod = "RS256"
RS384 RSSignMethod = "RS384"
RS512 RSSignMethod = "RS512"
)
func (rs *RS) getSignMethod() *jwt.SigningMethodRSA {
switch rs.SignMethod {
case RS512:
return jwt.SigningMethodRS512
case RS384:
return jwt.SigningMethodRS384
case RS256:
return jwt.SigningMethodRS256
default:
return jwt.SigningMethodRS256
}
}
// Sign 签名 私钥签名、公钥验证
func (rs *RS) Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(rs.getSignMethod(), data)
pKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(rs.PrivateKey))
sign, err := token.SignedString(pKey)
if err != nil {
log.Println(err)
return "", err
}
return sign, nil
}
// Verify 验签,获取数据
func (rs *RS) Verify(sign string, data jwt.Claims) error {
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseRSAPublicKeyFromPEM([]byte(rs.PublicKey))
})
return err
}
es.go 私钥签名、公钥验证
package jwtex
import (
"github.com/golang-jwt/jwt/v5"
"log"
)
type ES struct {
SignMethod ESSignMethod
PublicKey string
PrivateKey string
}
type ESSignMethod string
const (
ES256 ESSignMethod = "ES256"
ES384 ESSignMethod = "ES384"
ES512 ESSignMethod = "ES512"
)
func (es *ES) getSignMethod() *jwt.SigningMethodECDSA {
switch es.SignMethod {
case ES512:
return jwt.SigningMethodES512
case ES384:
return jwt.SigningMethodES384
case ES256:
return jwt.SigningMethodES256
default:
return jwt.SigningMethodES256
}
}
// Sign 签名 私钥签名、公钥验证
func (es *ES) Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(es.getSignMethod(), data)
pKey, err := jwt.ParseECPrivateKeyFromPEM([]byte(es.PrivateKey))
if err != nil {
log.Println(err)
return "", err
}
sign, err := token.SignedString(pKey)
if err != nil {
log.Println(err)
return "", err
}
return sign, nil
}
// Verify 验签,获取数据
func (es *ES) Verify(sign string, data jwt.Claims) error {
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseECPublicKeyFromPEM([]byte(es.PublicKey))
})
return err
}
ed.go 私钥签名、公钥验证
package jwtex
import (
"github.com/golang-jwt/jwt/v5"
"log"
)
type ED struct {
PrivateKey string
PublicKey string
}
// Sign 签名
func (ed *ED) Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, data)
pKey, err := jwt.ParseEdPrivateKeyFromPEM([]byte(ed.PrivateKey))
if err != nil {
log.Println(err)
return "", err
}
sign, err := token.SignedString(pKey)
if err != nil {
log.Println(err)
return "", err
}
return sign, err
}
// Verify 验签,并获取数据
func (ed *ED) Verify(sign string, data jwt.Claims) error {
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseEdPublicKeyFromPEM([]byte(ed.PublicKey))
})
return err
}
jwt.go
package jwtex
import (
"github.com/golang-jwt/jwt/v5"
)
type Data struct {
Name string
Age int
Gender int
jwt.RegisteredClaims
}
type Jwt interface {
Sing(data jwt.Claims) (string, error)
Verify(sign string, data jwt.Claims) error
}
https://github.com/0voice