golang jwt(hs,es,rs,ed)密钥生成、加签验签案例

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基于一个十分简单的数论事实:将两个大素数相乘十分容易,但想要对其乘积进行因式分解却极其困难,因此可以将乘积公开作为加密密钥,即公钥,而两个大素数组合成私钥

golang jwt(hs,es,rs,ed)密钥生成、加签验签案例_第1张图片

案例

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 }}
------------------

你可能感兴趣的:(golang,开发语言,安全)