golang JWT加签算法及使用案例
JWT原理
查看官方lib库
官方lib库
选择go语言
数据结构定义
secret.go
package secret
const KEY_PATH = "keys"
type OutSecret struct {
Secret string // 哈希签名
PublicKeyFile string
PrivateKeyFile string
}
// 密钥生成
type Secret interface {
// 密钥信息可能是字符串,也可能是公钥+私钥
Generate() (*OutSecret, error)
}
x509封装
x509PemGen.go
package secret
import (
"encoding/pem"
"log"
"os"
)
func X509PemGenerate(privateKey []byte, publicKey[]byte, priName string, pubName string) error{
// 公钥 私钥写文件
privateBlock := &pem.Block{
Type: "PRIVATE KEY",
Bytes: privateKey,
}
publicBlock := &pem.Block{
Type: "PUBLIC KEY",
Bytes: publicKey,
}
privateKeyFileName := priName
publicKeyFileName := pubName
privateFile, err := os.OpenFile(privateKeyFileName, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
return err
}
defer privateFile.Close()
pem.Encode(privateFile, privateBlock)
publicFile, err := os.OpenFile(publicKeyFileName, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
return err
}
defer publicFile.Close()
pem.Encode(publicFile, publicBlock)
return nil
}
HS、RS、ES、ED签名密钥生成
HS签名密钥生成
HS(HMAC-SHA)是一种对称加密算法,它需要一个共享密钥来进行加解密操作。在JWT中,我们可以使用HS256、HS384和HS512三种不同长度的哈希值作为加密算法。其密钥可以直接用一个随机字符串即可
package secret
import (
"encoding/hex"
"math/rand"
"time"
)
// HS的密钥可以是一个随机的字符串
type HsGenerator struct {
Length int
}
func (hs *HsGenerator) Generate() (*OutSecret, error) {
out := OutSecret{}
length := 32
if hs.Length > 0 {
length = hs.Length
}
// 随机生成字符串
rand.Seed(time.Now().UnixNano())
b := make([]byte, length)
rand.Read(b)
out.Secret = hex.EncodeToString(b)[:length]
return &out, nil
}
RS签名密钥生成
RS(RSA-SHA)是一种非对称加密算法,它需要一个公钥和一个私钥来进行加解密操作。在JWT中,我们可以使用RS256、RS384和RS512三种不同长度的RSA密钥作为加密算法
RSA基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥
案例
package secret
import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
_ "encoding/hex"
"log"
_ "time"
)
//RSA,生成公钥私钥文件,可以通过openSSL生成也可以
type RsGenerator struct {
}
func (rs *RsGenerator) Generate() (*OutSecret, error) {
out := OutSecret{}
var err error
// 生成密钥对,包含公钥
privateKey, err := rsa.GenerateKey(rand.Reader, 1024)
if err != nil {
log.Println(err)
return nil, err
}
// x509格式封装
x509PrivateKey,err := x509.MarshalPKCS8PrivateKey(privateKey) // 传入的是指针类型
if err != nil {
log.Println(err)
return nil, err
}
//公钥封装
x509PublicKey, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) // 传入的是指针类型
if err != nil {
log.Println(err)
return nil, err
}
// 公钥 私钥写文件
privateKeyFileName := KEY_PATH + "/rs/private.pem"
publicKeyFileName := KEY_PATH + "/rs/public.pem"
err = X509PemGenerate(x509PrivateKey, x509PublicKey, privateKeyFileName, publicKeyFileName)
if err != nil {
return nil, err
}
out.PrivateKeyFile = privateKeyFileName
out.PublicKeyFile = publicKeyFileName
return &out, err
}
ES签名密钥生成
ES(Elliptic Curve Digital Signature Algorithm)是一种基于椭圆曲线密码学的非对称加密算法。在JWT中,我们可以使用ES256、ES384和ES512三种不同长度的ECDSA(Elliptic Curve Digital Signature Algorithm)曲线作为加密算法。不同长度对应的算法不同,而RSA对应的算法是一样的
案例
package secret
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"log"
)
const (
ES256 ESSignMethod = "ES256"
ES384 ESSignMethod = "ES384"
ES512 ESSignMethod = "ES512"
)
type ESSignMethod string
// 椭圆曲线加密算法,每个长度的密钥对都不一样(采用不通的椭圆曲线算法),RSA的则是一样的
type ESGenerator struct {
SignMethod ESSignMethod
}
func (es * ESGenerator) getCurve() elliptic.Curve{
switch es.SignMethod {
case ES256:
return elliptic.P256()
case ES384:
return elliptic.P384()
case ES512:
return elliptic.P521()
}
return elliptic.P256()
}
func (es *ESGenerator) Generate() (*OutSecret, error) {
out := OutSecret{}
privateKey, err := ecdsa.GenerateKey(es.getCurve(), rand.Reader)
if err != nil {
log.Println(err)
return nil, err
}
// x509格式封装
x509PrivateKey,err := x509.MarshalPKCS8PrivateKey(privateKey) // 传入的是指针类型
if err != nil {
log.Println(err)
return nil, err
}
//公钥封装
x509PublicKey, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey) // 传入的是指针类型
if err != nil {
log.Println(err)
return nil, err
}
// 公钥 私钥写文件
privateKeyFileName := KEY_PATH + "/es/private.pem"
publicKeyFileName := KEY_PATH + "/es/public.pem"
err = X509PemGenerate(x509PrivateKey, x509PublicKey, privateKeyFileName, publicKeyFileName)
if err != nil {
return nil, err
}
out.PrivateKeyFile = privateKeyFileName
out.PublicKeyFile = publicKeyFileName
return &out, err
}
ED签名密钥生成
ED(Edwards-curve Digital Signature Algorithm)是一种基于Edwards曲线密码学的非对称加密算法,在JWT中,我们可以使用ED25519和ED448两种不同长度的Edwards曲线作为加密算法。
案例
package secret
import (
"crypto/ed25519"
"crypto/rand"
"crypto/x509"
"log"
"os"
)
// 扭曲爱德华曲线
//RSA,生成公钥私钥文件,可以通过openSSL生成也可以
type EdGenerator struct {
}
func (ed *EdGenerator) Generate() (*OutSecret, error) {
out := OutSecret{}
var err error
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
log.Println(err)
return nil, err
}
// x509格式封装
x509PrivateKey,err := x509.MarshalPKCS8PrivateKey(privateKey)
if err != nil {
log.Println(err)
return nil, err
}
//公钥封装
x509PublicKey, err := x509.MarshalPKIXPublicKey(publicKey)
if err != nil {
log.Println(err)
return nil, err
}
privateKeyFileName := KEY_PATH + "/ed/private.pem"
publicKeyFileName := KEY_PATH + "/ed/public.pem"
privateFile, err := os.OpenFile(privateKeyFileName, os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
log.Println(err)
return nil, err
}
defer privateFile.Close()
err = X509PemGenerate(x509PrivateKey, x509PublicKey, privateKeyFileName, publicKeyFileName)
if err != nil {
return nil, err
}
out.PrivateKeyFile = privateKeyFileName
out.PublicKeyFile = publicKeyFileName
return &out, err
}
HS、RS、ES、ED加签与验签
有了密钥对,现在看他们在JWT中如何加签验签
测试数据结构定义
package jwtex
import (
"github.com/golang-jwt/jwt/v4"
)
type Data struct {
// 自定义字段
Name string
Age int
Gender int
// 规定字段
jwt.RegisteredClaims
}
func (d Data) Valid() error {
return nil
}
// 可以自定义实现里面的Valid接口
type Jwt interface {
// Sign 签名
Sign(data jwt.Claims) (string, error)
// Verify 验签
Verify(sign string, data jwt.Claims) error
}
HS加签验签
package jwtex
import (
"github.com/golang-jwt/jwt/v4"
"log"
)
type HS struct {
Key string
SignMethod HSSignMethod
}
type HSSignMethod string
const (
HS256 HSSignMethod = "HS256"
HS384 HSSignMethod = "HS384"
HS512 HSSignMethod = "HS512"
)
func (hs *HS)getMethod() *jwt.SigningMethodHMAC{
switch hs.SignMethod {
case HS256:
return jwt.SigningMethodHS256
case HS384:
return jwt.SigningMethodHS384
case HS512:
return jwt.SigningMethodHS512
}
return jwt.SigningMethodHS256
}
// 签名
func (hs *HS)Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(hs.getMethod(), data)
sign, err := token.SignedString([]byte(hs.Key))
if err != nil {
log.Println(err)
return "", err
}
return sign, nil
}
// 验签,获取数据
func (hs *HS)Verify(sign string, data jwt.Claims) error {
// keyFunc是提供密钥的函数
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return []byte(hs.Key), nil
})
return err
}
RS加签验签
RS 的密钥公钥可以使用不同的rsa长度算法进行加签验签
package jwtex
import (
"github.com/golang-jwt/jwt/v4"
"log"
)
type RS struct {
SignMethod RSSignMethod
// 密钥对
PrivateKey string
PublicKey string
}
type RSSignMethod string
const (
RS256 RSSignMethod = "RS256"
RS384 RSSignMethod = "RS384"
RS512 RSSignMethod = "RS512"
)
func (rs *RS)getMethod() *jwt.SigningMethodRSA{
switch rs.SignMethod {
case RS256:
return jwt.SigningMethodRS256
case RS384:
return jwt.SigningMethodRS384
case RS512:
return jwt.SigningMethodRS512
}
return jwt.SigningMethodRS256
}
func (rs *RS)Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(rs.getMethod(), data)
// 私钥
pKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(rs.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
}
func (rs *RS)Verify(sign string, data jwt.Claims) error {
// keyFunc是提供密钥的函数
// 公钥解密
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseRSAPublicKeyFromPEM([]byte(rs.PublicKey))
})
return err
}
ES加签验签
ES生成的密钥和公钥使用的什么算法,加签验签的时候就要用什么算法(ES256、ES384、ES512)
package jwtex
import (
"github.com/golang-jwt/jwt/v4"
"log"
)
type ES struct {
SignMethod ESSignMethod
// 密钥对
PrivateKey string
PublicKey string
}
type ESSignMethod string
const (
ES256 ESSignMethod = "ES256"
ES384 ESSignMethod = "ES384"
ES512 ESSignMethod = "ES512"
)
func (es *ES)getMethod() *jwt.SigningMethodECDSA{
switch es.SignMethod {
case ES256:
return jwt.SigningMethodES256
case ES384:
return jwt.SigningMethodES384
case ES512:
return jwt.SigningMethodES512
}
return jwt.SigningMethodES256
}
func (es *ES)Sign(data jwt.Claims) (string, error) {
token := jwt.NewWithClaims(es.getMethod(), 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
}
func (es *ES)Verify(sign string, data jwt.Claims) error {
// keyFunc是提供密钥的函数
// 公钥解密
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseECPublicKeyFromPEM([]byte(es.PublicKey))
})
return err
}
ED加签验签
package jwtex
import (
"github.com/golang-jwt/jwt/v4"
"log"
)
type ED struct {
// 密钥对
PrivateKey string
PublicKey string
}
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, nil
}
func (ed *ED)Verify(sign string, data jwt.Claims) error {
// keyFunc是提供密钥的函数
// 公钥解密
_, err := jwt.ParseWithClaims(sign, data, func(token *jwt.Token) (interface{}, error) {
return jwt.ParseEdPublicKeyFromPEM([]byte(ed.PublicKey))
})
return err
}
代码中体现的基本上只是加签验签算法的不同。
案例测试
使用前面的生成的密钥
package main
import (
"fmt"
"github.com/golang-jwt/jwt/v4"
"jwt-practice/jwtex"
"jwt-practice/secret"
"log"
"os"
"time"
)
func main() {
//GenerateKeys()
//HS加签验签
data := &jwtex.Data{
Name: "yuan",
Age: 11,
Gender: 2,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()), //签发时间
NotBefore: jwt.NewNumericDate(time.Now()), // 生效时间
},
}
hs := jwtex.HS{
Key: "123456",
SignMethod: jwtex.HS384,
}
sign, err := hs.Sign(data)
if err != nil {
return
}
fmt.Println("hs sign:", sign)
outData := &jwtex.Data{}
err = hs.Verify(sign, outData)
if err != nil {
return
}
fmt.Println("hs verify:", outData)
// jwt是不加密的,下面解一下data字段数据
/*
bytes, _:= base64.URLEncoding.DecodeString("eyJOYW1lIjoieXVhbiIsIkFnZSI6MTEsIkdlbmRlciI6MiwiRXhwaXJlc0F0IjoiMjAyMy0xMC0xMVQyMjoyMjoyOS4yOTMzODg0KzA4OjAwIn0")
fmt.Println(string(bytes))
*/
fmt.Println("------------------")
//RS加签验签
privateKey, _ := os.ReadFile("keys/rs/private.pem")
publicKey, _ := os.ReadFile("keys/rs/public.pem")
rs := jwtex.RS{
SignMethod: jwtex.RS512,
PrivateKey: string(privateKey),
PublicKey: string(publicKey),
}
sign, err = rs.Sign(data)
if err != nil {
return
}
fmt.Println("rs sign:", sign)
outData = &jwtex.Data{}
err = rs.Verify(sign, outData)
if err != nil {
log.Println(err)
return
}
fmt.Println("rs verify:", outData)
fmt.Println("------------------")
//ES加签验签
privateKey, _ = os.ReadFile("keys/es/private.pem")
publicKey, _ = os.ReadFile("keys/es/public.pem")
es := jwtex.ES{
SignMethod: jwtex.ES512,
PrivateKey: string(privateKey),
PublicKey: string(publicKey),
}
sign, err = es.Sign(data)
if err != nil {
return
}
fmt.Println("es sign:", sign)
outData = &jwtex.Data{}
err = es.Verify(sign, outData)
if err != nil {
log.Println(err)
return
}
fmt.Println("es verify:", outData)
fmt.Println("------------------")
//ED加签验签
privateKey, _ = os.ReadFile("keys/ed/private.pem")
publicKey, _ = os.ReadFile("keys/ed/public.pem")
ed := jwtex.ED{
PrivateKey: string(privateKey),
PublicKey: string(publicKey),
}
sign, err = ed.Sign(data)
if err != nil {
return
}
fmt.Println("ed sign:", sign)
outData = &jwtex.Data{}
err = ed.Verify(sign, outData)
if err != nil {
log.Println(err)
return
}
fmt.Println("ed verify:", outData)
fmt.Println("------------------")
}
func GenerateKeys() {
hs := secret.HsGenerator{
Length: 256,
}
res, err := hs.Generate()
fmt.Println(res, err)
rs := secret.RsGenerator{}
res, err = rs.Generate()
fmt.Println(res, err)
es := secret.ESGenerator{
SignMethod: secret.ES512,
}
res, err = es.Generate()
fmt.Println(res, err)
ed := secret.EdGenerator{}
res, err = ed.Generate()
fmt.Println(res, err)
}
输出
hs sign: eyJhbGciOiJIUzM4NCIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoieXVhbiIsIkFnZSI6MTEsIkdlbmRlciI6MiwiZXhwIjoxNjk3MDE4Nzk3LCJuYmYiOjE2OTcwMTUxOTcsImlhdCI6MTY5NzAxNTE5N30.dj2SMHiKmdvcyDRG6xvn_uYlQWsIzccE0AFgN863zwvJ4dWXZw4MgUTzoXh4DNVf
hs verify: &{yuan 11 2 { [] 2023-10-11 18:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST }}
------------------
rs sign: eyJhbGciOiJSUzUxMiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoieXVhbiIsIkFnZSI6MTEsIkdlbmRlciI6MiwiZXhwIjoxNjk3MDE4Nzk3LCJuYmYiOjE2OTcwMTUxOTcsImlhdCI6MTY5NzAxNTE5N30.j0ZBzRHQSAcDB7LlhuZMusqtwMNdYWXcz2tmI0W4Rs6VIBg3J3g04t5uag7MPjfWiNp85gcRKsjs3YVn5TKbFU29qTiLm3m8rsPW-rUI1b5W0aT5zJmHheNb0rmZ389vkaQHv1FlCoENKBIxjt62Vifg9YRqwbGWA1wFhgjGFJY
rs verify: &{yuan 11 2 { [] 2023-10-11 18:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST }}
------------------
es sign: eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoieXVhbiIsIkFnZSI6MTEsIkdlbmRlciI6MiwiZXhwIjoxNjk3MDE4Nzk3LCJuYmYiOjE2OTcwMTUxOTcsImlhdCI6MTY5NzAxNTE5N30.AIjdHCKqIZUoxS7z9ETqi-1kh0l9AD9ZBz_pA9Vmo_ofGQB-TRTTCbIcOARAazKXz063b6m92oHiYbIXzYxUACu0APPNwcaCd7kyq6gFF4KxxxZQsT7NRB3OMWh5rHdtr-2gfKXQOjI5_pgD3odNfwFLOicKE7OPPoj0_6yB5LnDq7-P
es verify: &{yuan 11 2 { [] 2023-10-11 18:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST }}
------------------
ed sign: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9.eyJOYW1lIjoieXVhbiIsIkFnZSI6MTEsIkdlbmRlciI6MiwiZXhwIjoxNjk3MDE4Nzk3LCJuYmYiOjE2OTcwMTUxOTcsImlhdCI6MTY5NzAxNTE5N30.9b6z6sce5uCkIyI_JVYO_Ncjj7TG0jKvHQFoWdMFTqhGWPPd0mp2Tzy_4ILzubkxTB-GR9KLH0pIeUVanJxECw
ed verify: &{yuan 11 2 { [] 2023-10-11 18:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST 2023-10-11 17:06:37 +0800 CST }}
------------------