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 jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type HS struct {Key stringSignMethod HSSignMethod
}type HSSignMethod stringconst (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.SigningMethodHS256case HS384:return jwt.SigningMethodHS384case HS512:return jwt.SigningMethodHS512default: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 jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type RS struct {SignMethod RSSignMethodPublicKey stringPrivateKey string
}type RSSignMethod stringconst (RS256 RSSignMethod = "RS256"RS384 RSSignMethod = "RS384"RS512 RSSignMethod = "RS512"
)func (rs *RS) getSignMethod() *jwt.SigningMethodRSA {switch rs.SignMethod {case RS512:return jwt.SigningMethodRS512case RS384:return jwt.SigningMethodRS384case RS256:return jwt.SigningMethodRS256default: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 jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type ES struct {SignMethod ESSignMethodPublicKey stringPrivateKey string
}type ESSignMethod stringconst (ES256 ESSignMethod = "ES256"ES384 ESSignMethod = "ES384"ES512 ESSignMethod = "ES512"
)func (es *ES) getSignMethod() *jwt.SigningMethodECDSA {switch es.SignMethod {case ES512:return jwt.SigningMethodES512case ES384:return jwt.SigningMethodES384case ES256:return jwt.SigningMethodES256default: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 jwteximport ("github.com/golang-jwt/jwt/v5""log"
)type ED struct {PrivateKey stringPublicKey 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 jwteximport ("github.com/golang-jwt/jwt/v5"
)type Data struct {Name stringAge intGender intjwt.RegisteredClaims
}type Jwt interface {Sing(data jwt.Claims) (string, error)Verify(sign string, data jwt.Claims) error
}
https://github.com/0voice