or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

claims.mdindex.mdkey-parsing.mdrequest-extraction.mdsigning-methods.mdtoken-creation.mdtoken-parsing.md
tile.json

signing-methods.mddocs/

Signing Methods and Algorithms

This document provides comprehensive documentation of all signing methods and algorithms supported by the JWT library. Signing methods are responsible for creating and verifying JWT signatures.

SigningMethod Interface

type SigningMethod interface {
    Verify(signingString string, sig []byte, key any) error
    Sign(signingString string, key any) ([]byte, error)
    Alg() string
}

The core interface that all signing method implementations must satisfy.

Methods:

  • Verify - Verifies that the signature is valid for the given signing string and key
  • Sign - Creates a signature for the given signing string using the provided key
  • Alg - Returns the algorithm identifier string (e.g., "HS256", "RS256")

Signing Method Management

GetSigningMethod

func GetSigningMethod(alg string) SigningMethod

Retrieves a registered signing method by its algorithm name. Returns nil if the algorithm is not registered.

Parameters:

  • alg - Algorithm identifier (e.g., "HS256", "RS256", "ES256")

Returns:

  • The SigningMethod implementation, or nil if not found

Example:

method := jwt.GetSigningMethod("HS256")
if method == nil {
    panic("algorithm not found")
}

RegisterSigningMethod

func RegisterSigningMethod(alg string, f func() SigningMethod)

Registers a custom signing method. This allows you to add support for custom algorithms or override built-in ones. The function f is a factory that creates new instances of the signing method.

Parameters:

  • alg - Algorithm identifier to register
  • f - Factory function that returns a new SigningMethod instance

Example:

// Register a custom signing method
jwt.RegisterSigningMethod("CUSTOM", func() jwt.SigningMethod {
    return &CustomSigningMethod{}
})

GetAlgorithms

func GetAlgorithms() []string

Returns a list of all registered algorithm names. Useful for validation and introspection.

Returns:

  • Slice of algorithm identifier strings

Example:

algorithms := jwt.GetAlgorithms()
// Returns: ["HS256", "HS384", "HS512", "RS256", "RS384", "RS512", ...]

HMAC Algorithms (HS256, HS384, HS512)

HMAC (Hash-based Message Authentication Code) uses symmetric keys and hash functions to create signatures. These algorithms are fast, simple, and suitable for internal services where the same secret can be safely shared.

SigningMethodHMAC Type

type SigningMethodHMAC struct {
    Name string
    Hash crypto.Hash
}

Implements HMAC-based signing using SHA-2 hash functions.

Fields:

  • Name - Algorithm name (e.g., "HS256")
  • Hash - The hash function to use (e.g., crypto.SHA256)

Methods:

func (m *SigningMethodHMAC) Alg() string

Returns the algorithm name.

func (m *SigningMethodHMAC) Sign(signingString string, key any) ([]byte, error)

Signs the signing string using HMAC with the provided key.

Key Requirements:

  • Type: []byte
  • Minimum recommended size: 32 bytes (256 bits) for HS256
func (m *SigningMethodHMAC) Verify(signingString string, sig []byte, key any) error

Verifies the HMAC signature.

Pre-configured HMAC Instances

var SigningMethodHS256 *SigningMethodHMAC

HMAC using SHA-256 hash function. Produces 256-bit signatures.

var SigningMethodHS384 *SigningMethodHMAC

HMAC using SHA-384 hash function. Produces 384-bit signatures.

var SigningMethodHS512 *SigningMethodHMAC

HMAC using SHA-512 hash function. Produces 512-bit signatures.

HMAC Usage Examples

Basic HMAC Signing (HS256)

import (
    "fmt"
    "github.com/golang-jwt/jwt/v5"
)

// Create secret key (should be at least 32 bytes)
secretKey := []byte("your-256-bit-secret-key-here!!")

// Create claims
claims := jwt.MapClaims{
    "sub": "user123",
    "exp": jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
}

// Create token with HS256
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)

// Sign the token
tokenString, err := token.SignedString(secretKey)
if err != nil {
    panic(err)
}

fmt.Println("Token:", tokenString)

Using HS384

// Create token with stronger hash
token := jwt.NewWithClaims(jwt.SigningMethodHS384, claims)
tokenString, err := token.SignedString(secretKey)

Using HS512

// Create token with strongest HMAC hash
token := jwt.NewWithClaims(jwt.SigningMethodHS512, claims)
tokenString, err := token.SignedString(secretKey)

HMAC Key Requirements

  • Type: []byte (byte slice)
  • Minimum size:
    • HS256: 32 bytes (256 bits) recommended
    • HS384: 48 bytes (384 bits) recommended
    • HS512: 64 bytes (512 bits) recommended
  • Generation: Use cryptographically secure random bytes
  • Storage: Store securely, never commit to version control

RSA Algorithms (RS256, RS384, RS512)

RSA uses asymmetric cryptography with public/private key pairs. These algorithms are suitable when you need to distribute public keys for verification while keeping private keys secure.

SigningMethodRSA Type

type SigningMethodRSA struct {
    Name string
    Hash crypto.Hash
}

Implements RSA PKCS#1 v1.5 signature algorithm.

Fields:

  • Name - Algorithm name (e.g., "RS256")
  • Hash - The hash function to use (e.g., crypto.SHA256)

Methods:

func (m *SigningMethodRSA) Alg() string

Returns the algorithm name.

func (m *SigningMethodRSA) Sign(signingString string, key any) ([]byte, error)

Signs using RSA PKCS#1 v1.5 with the provided private key.

Key Requirements:

  • Signing: *rsa.PrivateKey
  • Verification: *rsa.PublicKey
  • Minimum size: 2048 bits (4096 bits recommended for high security)
func (m *SigningMethodRSA) Verify(signingString string, sig []byte, key any) error

Verifies the RSA signature using the public key.

Pre-configured RSA Instances

var SigningMethodRS256 *SigningMethodRSA

RSA signature with SHA-256 hash.

var SigningMethodRS384 *SigningMethodRSA

RSA signature with SHA-384 hash.

var SigningMethodRS512 *SigningMethodRSA

RSA signature with SHA-512 hash.

RSA Usage Examples

Signing with RSA (RS256)

import (
    "crypto/rsa"
    "github.com/golang-jwt/jwt/v5"
)

// Load RSA private key from PEM file
privateKeyData, err := os.ReadFile("private_key.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyData)
if err != nil {
    panic(err)
}

// Create claims
claims := jwt.RegisteredClaims{
    Issuer:    "my-service",
    Subject:   "user456",
    ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
}

// Create and sign token with RS256
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
tokenString, err := token.SignedString(privateKey)
if err != nil {
    panic(err)
}

Verifying RSA Signatures

// Load RSA public key from PEM file
publicKeyData, err := os.ReadFile("public_key.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyData)
if err != nil {
    panic(err)
}

// Parse and verify token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
    // Validate the algorithm
    if _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return publicKey, nil
})

if err != nil {
    panic(err)
}

Using RS384 and RS512

// RS384 - stronger hash
token := jwt.NewWithClaims(jwt.SigningMethodRS384, claims)
tokenString, err := token.SignedString(privateKey)

// RS512 - strongest RSA hash
token := jwt.NewWithClaims(jwt.SigningMethodRS512, claims)
tokenString, err := token.SignedString(privateKey)

RSA Key Requirements

  • Key size: Minimum 2048 bits, 4096 bits recommended
  • Format: PEM-encoded PKCS#1 or PKCS#8
  • Signing: Requires *rsa.PrivateKey
  • Verification: Requires *rsa.PublicKey
  • Generation: Use crypto/rsa package or OpenSSL

RSA-PSS Algorithms (PS256, PS384, PS512)

RSA-PSS (Probabilistic Signature Scheme) is a more secure RSA signature scheme with better security proofs than PKCS#1 v1.5. It uses randomized padding for enhanced security.

SigningMethodRSAPSS Type

type SigningMethodRSAPSS struct {
    *SigningMethodRSA
    Options       *rsa.PSSOptions
    VerifyOptions *rsa.PSSOptions
}

Implements RSA-PSS signature algorithm.

Fields:

  • *SigningMethodRSA - Embeds base RSA signing method
  • Options - PSS options for signing (salt length, hash function)
  • VerifyOptions - PSS options for verification

Methods:

func (m *SigningMethodRSAPSS) Sign(signingString string, key any) ([]byte, error)

Signs using RSA-PSS with the provided private key.

Key Requirements:

  • Signing: *rsa.PrivateKey
  • Verification: *rsa.PublicKey
  • Minimum size: 2048 bits
func (m *SigningMethodRSAPSS) Verify(signingString string, sig []byte, key any) error

Verifies the RSA-PSS signature using the public key.

Pre-configured RSA-PSS Instances

var SigningMethodPS256 *SigningMethodRSAPSS

RSA-PSS signature with SHA-256 hash. Uses default salt length (hash length).

var SigningMethodPS384 *SigningMethodRSAPSS

RSA-PSS signature with SHA-384 hash.

var SigningMethodPS512 *SigningMethodRSAPSS

RSA-PSS signature with SHA-512 hash.

RSA-PSS Usage Examples

Signing with RSA-PSS (PS256)

import (
    "crypto/rsa"
    "github.com/golang-jwt/jwt/v5"
)

// Load RSA private key
privateKeyData, err := os.ReadFile("private_key.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyData)
if err != nil {
    panic(err)
}

// Create claims
claims := jwt.MapClaims{
    "sub": "user789",
    "exp": time.Now().Add(2 * time.Hour).Unix(),
}

// Create and sign token with PS256
token := jwt.NewWithClaims(jwt.SigningMethodPS256, claims)
tokenString, err := token.SignedString(privateKey)
if err != nil {
    panic(err)
}

Using PS384 and PS512

// PS384
token := jwt.NewWithClaims(jwt.SigningMethodPS384, claims)
tokenString, err := token.SignedString(privateKey)

// PS512 - strongest PSS variant
token := jwt.NewWithClaims(jwt.SigningMethodPS512, claims)
tokenString, err := token.SignedString(privateKey)

Verifying RSA-PSS Signatures

// Load public key
publicKeyData, err := os.ReadFile("public_key.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyData)
if err != nil {
    panic(err)
}

// Parse and verify
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
    // Validate the algorithm
    if _, ok := token.Method.(*jwt.SigningMethodRSAPSS); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return publicKey, nil
})

RSA-PSS Key Requirements

  • Key size: Minimum 2048 bits, 4096 bits recommended
  • Format: PEM-encoded (same as regular RSA)
  • Signing: Requires *rsa.PrivateKey
  • Verification: Requires *rsa.PublicKey
  • Salt length: Defaults to hash output length

ECDSA Algorithms (ES256, ES384, ES512)

ECDSA (Elliptic Curve Digital Signature Algorithm) provides asymmetric signatures with smaller key sizes than RSA while maintaining comparable security. These algorithms are efficient and produce compact signatures.

SigningMethodECDSA Type

type SigningMethodECDSA struct {
    Name      string
    Hash      crypto.Hash
    KeySize   int
    CurveBits int
}

Implements ECDSA signature algorithm.

Fields:

  • Name - Algorithm name (e.g., "ES256")
  • Hash - The hash function to use (e.g., crypto.SHA256)
  • KeySize - Expected key size in bytes
  • CurveBits - Expected curve bit size

Methods:

func (m *SigningMethodECDSA) Alg() string

Returns the algorithm name.

func (m *SigningMethodECDSA) Sign(signingString string, key any) ([]byte, error)

Signs using ECDSA with the provided private key.

Key Requirements:

  • Signing: *ecdsa.PrivateKey
  • Verification: *ecdsa.PublicKey
  • Curve must match algorithm (P-256, P-384, or P-521)
func (m *SigningMethodECDSA) Verify(signingString string, sig []byte, key any) error

Verifies the ECDSA signature using the public key.

Pre-configured ECDSA Instances

var SigningMethodES256 *SigningMethodECDSA

ECDSA using P-256 curve and SHA-256 hash. Provides approximately 128-bit security.

var SigningMethodES384 *SigningMethodECDSA

ECDSA using P-384 curve and SHA-384 hash. Provides approximately 192-bit security.

var SigningMethodES512 *SigningMethodECDSA

ECDSA using P-521 curve and SHA-512 hash. Provides approximately 256-bit security.

ECDSA Usage Examples

Signing with ECDSA (ES256)

import (
    "crypto/ecdsa"
    "github.com/golang-jwt/jwt/v5"
)

// Load ECDSA private key from PEM file
privateKeyData, err := os.ReadFile("ec_private_key.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseECPrivateKeyFromPEM(privateKeyData)
if err != nil {
    panic(err)
}

// Create claims
claims := jwt.RegisteredClaims{
    Subject:   "user123",
    ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Minute)),
    IssuedAt:  jwt.NewNumericDate(time.Now()),
}

// Create and sign token with ES256
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
tokenString, err := token.SignedString(privateKey)
if err != nil {
    panic(err)
}

Verifying ECDSA Signatures

// Load ECDSA public key from PEM file
publicKeyData, err := os.ReadFile("ec_public_key.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseECPublicKeyFromPEM(publicKeyData)
if err != nil {
    panic(err)
}

// Parse and verify token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
    // Validate the algorithm
    if _, ok := token.Method.(*jwt.SigningMethodECDSA); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return publicKey, nil
})

if err != nil {
    panic(err)
}

Using ES384 and ES512

// ES384 - P-384 curve
token := jwt.NewWithClaims(jwt.SigningMethodES384, claims)
tokenString, err := token.SignedString(privateKey)  // Must use P-384 key

// ES512 - P-521 curve (note: curve is P-521, not P-512)
token := jwt.NewWithClaims(jwt.SigningMethodES512, claims)
tokenString, err := token.SignedString(privateKey)  // Must use P-521 key

Generating ECDSA Keys

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
)

// Generate P-256 key for ES256
privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
    panic(err)
}

// Generate P-384 key for ES384
privateKey384, err := ecdsa.GenerateKey(elliptic.P384(), rand.Reader)

// Generate P-521 key for ES512
privateKey521, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)

ECDSA Key Requirements

  • ES256: P-256 curve (secp256r1 / prime256v1)
  • ES384: P-384 curve (secp384r1)
  • ES512: P-521 curve (secp521r1) - note the curve is 521 bits, not 512
  • Signing: Requires *ecdsa.PrivateKey
  • Verification: Requires *ecdsa.PublicKey
  • Format: PEM-encoded SEC1 or PKCS#8

EdDSA Algorithm (Ed25519)

EdDSA (Edwards-curve Digital Signature Algorithm) using the Ed25519 curve is a modern signature scheme offering excellent performance, small key and signature sizes, and strong security guarantees.

SigningMethodEd25519 Type

type SigningMethodEd25519 struct{}

Implements EdDSA signature algorithm using Ed25519 curve.

Methods:

func (m *SigningMethodEd25519) Alg() string

Returns "EdDSA".

func (m *SigningMethodEd25519) Sign(signingString string, key any) ([]byte, error)

Signs using Ed25519 with the provided private key.

Key Requirements:

  • Signing: ed25519.PrivateKey (64 bytes)
  • Verification: ed25519.PublicKey (32 bytes)
func (m *SigningMethodEd25519) Verify(signingString string, sig []byte, key any) error

Verifies the Ed25519 signature using the public key.

Pre-configured EdDSA Instance

var SigningMethodEdDSA *SigningMethodEd25519

EdDSA using Ed25519 curve. This is the recommended algorithm for new projects due to its excellent security and performance characteristics.

EdDSA Usage Examples

Signing with EdDSA

import (
    "crypto/ed25519"
    "github.com/golang-jwt/jwt/v5"
)

// Load Ed25519 private key from PEM file
privateKeyData, err := os.ReadFile("ed25519_private_key.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseEdPrivateKeyFromPEM(privateKeyData)
if err != nil {
    panic(err)
}

// Create claims
claims := jwt.MapClaims{
    "sub":  "user456",
    "name": "Jane Doe",
    "exp":  time.Now().Add(1 * time.Hour).Unix(),
}

// Create and sign token with EdDSA
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
tokenString, err := token.SignedString(privateKey)
if err != nil {
    panic(err)
}

Verifying EdDSA Signatures

// Load Ed25519 public key from PEM file
publicKeyData, err := os.ReadFile("ed25519_public_key.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseEdPublicKeyFromPEM(publicKeyData)
if err != nil {
    panic(err)
}

// Parse and verify token
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
    // Validate the algorithm
    if _, ok := token.Method.(*jwt.SigningMethodEd25519); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    return publicKey, nil
})

if err != nil {
    panic(err)
}

Generating Ed25519 Keys

import (
    "crypto/ed25519"
    "crypto/rand"
)

// Generate Ed25519 key pair
publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
    panic(err)
}

// Use the keys
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
tokenString, err := token.SignedString(privateKey)

EdDSA Key Requirements

  • Curve: Ed25519 (fixed, not configurable)
  • Private key: 64 bytes (ed25519.PrivateKey)
  • Public key: 32 bytes (ed25519.PublicKey)
  • Signature size: 64 bytes (fixed)
  • Format: PEM-encoded PKCS#8 or raw bytes

None Algorithm (Unsecured JWTs)

The "none" algorithm creates unsecured JWTs with no signature. This should only be used in very specific scenarios where the token is transmitted over a secure channel and signature verification is not needed.

SigningMethodNone Type

type signingMethodNone struct{}

Implements the "none" signature method (unsecured JWTs).

Pre-configured None Instance

var SigningMethodNone *signingMethodNone

The "none" signing method. Creates JWTs without signatures.

Allowing None Signature Type

const UnsafeAllowNoneSignatureType unsafeNoneMagicConstant = "none signing method allowed"

A constant that must be used explicitly to allow parsing tokens with "none" algorithm. This is a safety mechanism to prevent accidental acceptance of unsecured tokens.

None Algorithm Usage Examples

Creating Unsecured Tokens

import (
    "github.com/golang-jwt/jwt/v5"
)

// Create claims
claims := jwt.MapClaims{
    "sub":  "user123",
    "data": "public information",
}

// Create token with none algorithm
token := jwt.NewWithClaims(jwt.SigningMethodNone, claims)

// Sign with UnsafeAllowNoneSignatureType constant
tokenString, err := token.SignedString(jwt.UnsafeAllowNoneSignatureType)
if err != nil {
    panic(err)
}

// The resulting token has no signature: "header.payload."

Parsing Unsecured Tokens

// Parse token allowing none algorithm
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
    // Explicitly check for none algorithm
    if _, ok := token.Method.(*jwt.signingMethodNone); !ok {
        return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
    }
    // Return the magic constant to allow none
    return jwt.UnsafeAllowNoneSignatureType, nil
})

if err != nil {
    panic(err)
}

None Algorithm Warnings

Security Considerations:

  • Never use "none" for sensitive data
  • Only use when transmitted over authenticated/encrypted channels
  • Most applications should reject "none" tokens by default
  • Attackers may try to change "alg" header to "none" to bypass verification

When to Use:

  • Testing and development
  • Internal debugging
  • Tokens transmitted over mutually authenticated TLS
  • When signature is provided by external mechanism (e.g., envelope encryption)

When NOT to Use:

  • Public APIs
  • Cross-origin requests
  • Untrusted environments
  • Any scenario requiring authentication or integrity

Algorithm Selection Best Practices

Symmetric vs Asymmetric

Use Symmetric (HMAC) when:

  • Both signing and verification happen in the same service
  • Key can be securely shared between services
  • Maximum performance is needed
  • Simplicity is preferred

Use Asymmetric (RSA/ECDSA/EdDSA) when:

  • Multiple services need to verify tokens
  • Public key distribution is acceptable
  • Private key must remain isolated
  • Supporting external verification

Choosing Hash Strength

Algorithm Variants:

  • 256-bit (HS256, RS256, ES256, PS256): Standard security, sufficient for most applications
  • 384-bit (HS384, RS384, ES384, PS384): Higher security margin
  • 512-bit (HS512, RS512, ES512, PS512): Maximum security, larger signatures

Recommendations:

  • Use 256-bit variants unless you have specific requirements
  • Stronger hashes provide minimal additional security for most use cases
  • 512-bit variants produce larger tokens

Algorithm Comparison

AlgorithmTypeKey SizeSignature SizePerformanceSecurity Level
HS256Symmetric32+ bytes32 bytesFastestHigh
HS384Symmetric48+ bytes48 bytesFastestHigher
HS512Symmetric64+ bytes64 bytesFastestHighest
RS256Asymmetric2048+ bits256 bytesMediumHigh
RS384Asymmetric2048+ bits256 bytesMediumHigher
RS512Asymmetric2048+ bits256 bytesMediumHighest
PS256Asymmetric2048+ bits256 bytesMediumHigh (Better than RS)
PS384Asymmetric2048+ bits256 bytesMediumHigher
PS512Asymmetric2048+ bits256 bytesMediumHighest
ES256Asymmetric256 bits64 bytesFastHigh
ES384Asymmetric384 bits96 bytesFastHigher
ES512Asymmetric521 bits132 bytesFastHighest
EdDSAAsymmetric256 bits64 bytesVery FastVery High

Modern Recommendations

For New Projects:

  1. EdDSA - Best overall choice for modern applications
  2. ES256 - Excellent alternative if EdDSA is not supported
  3. HS256 - For internal services where symmetric keys are acceptable

For Legacy Compatibility:

  1. RS256 - Widest support across platforms
  2. HS256 - Simple and widely supported

For High Security:

  1. ES512 - Elliptic curve with strongest security margin
  2. PS512 - RSA-PSS with strongest hash
  3. EdDSA - Modern design with strong security guarantees

Migration Considerations

When migrating algorithms:

// Support multiple algorithms during transition
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
    switch token.Method.(type) {
    case *jwt.SigningMethodHMAC:
        // Legacy HMAC tokens
        return hmacSecret, nil
    case *jwt.SigningMethodEd25519:
        // New EdDSA tokens
        return eddsaPublicKey, nil
    default:
        return nil, fmt.Errorf("unsupported algorithm: %v", token.Header["alg"])
    }
})

Key Parsing Functions

The library provides utility functions for parsing keys from PEM format.

ECDSA Key Parsing

func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error)

Parses a PEM-encoded Elliptic Curve private key.

func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error)

Parses a PEM-encoded Elliptic Curve public key.

Ed25519 Key Parsing

func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error)

Parses a PEM-encoded Ed25519 private key.

func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error)

Parses a PEM-encoded Ed25519 public key.

RSA Key Parsing

func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)

Parses a PEM-encoded RSA private key (PKCS#1 or PKCS#8).

func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)

Parses a PEM-encoded RSA public key.

func ParseRSAPrivateKeyFromPEMWithPassword(key []byte, password string) (*rsa.PrivateKey, error)

Parses a password-protected PEM-encoded RSA private key. DEPRECATED: Use standard library's encrypted PEM parsing instead.

Key Parsing Examples

Loading ECDSA Keys

import (
    "os"
    "github.com/golang-jwt/jwt/v5"
)

// Load private key
privateKeyPEM, err := os.ReadFile("ec_private.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseECPrivateKeyFromPEM(privateKeyPEM)
if err != nil {
    panic(err)
}

// Load public key
publicKeyPEM, err := os.ReadFile("ec_public.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseECPublicKeyFromPEM(publicKeyPEM)
if err != nil {
    panic(err)
}

Loading Ed25519 Keys

// Load Ed25519 private key
privateKeyPEM, err := os.ReadFile("ed25519_private.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseEdPrivateKeyFromPEM(privateKeyPEM)
if err != nil {
    panic(err)
}

// Load Ed25519 public key
publicKeyPEM, err := os.ReadFile("ed25519_public.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseEdPublicKeyFromPEM(publicKeyPEM)
if err != nil {
    panic(err)
}

Loading RSA Keys

// Load RSA private key
privateKeyPEM, err := os.ReadFile("rsa_private.pem")
if err != nil {
    panic(err)
}

privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privateKeyPEM)
if err != nil {
    panic(err)
}

// Load RSA public key
publicKeyPEM, err := os.ReadFile("rsa_public.pem")
if err != nil {
    panic(err)
}

publicKey, err := jwt.ParseRSAPublicKeyFromPEM(publicKeyPEM)
if err != nil {
    panic(err)
}

Common Errors

Key-Related Errors

  • ErrInvalidKey: Key is nil or invalid
  • ErrInvalidKeyType: Key type doesn't match the signing method
  • ErrKeyMustBePEMEncoded: Provided key is not PEM-encoded
  • ErrNotRSAPrivateKey: PEM does not contain RSA private key
  • ErrNotRSAPublicKey: PEM does not contain RSA public key
  • ErrNotECPrivateKey: PEM does not contain EC private key
  • ErrNotECPublicKey: PEM does not contain EC public key
  • ErrNotEdPrivateKey: PEM does not contain Ed25519 private key
  • ErrNotEdPublicKey: PEM does not contain Ed25519 public key

Verification Errors

  • ErrECDSAVerification: ECDSA signature verification failed
  • ErrEd25519Verification: Ed25519 signature verification failed
  • ErrHashUnavailable: Required hash function is not linked
  • ErrSignatureInvalid: Signature verification failed

None Algorithm Error

  • NoneSignatureTypeDisallowedError: Returned when attempting to parse a token with "none" algorithm without explicitly allowing it

Complete Example: Multi-Algorithm Support

Here's a complete example showing how to support multiple signing algorithms in a single application:

package main

import (
    "crypto/ecdsa"
    "crypto/ed25519"
    "crypto/rsa"
    "fmt"
    "os"
    "time"

    "github.com/golang-jwt/jwt/v5"
)

type KeyManager struct {
    hmacSecret    []byte
    rsaPrivate    *rsa.PrivateKey
    rsaPublic     *rsa.PublicKey
    ecdsaPrivate  *ecdsa.PrivateKey
    ecdsaPublic   *ecdsa.PublicKey
    eddsaPrivate  ed25519.PrivateKey
    eddsaPublic   ed25519.PublicKey
}

func NewKeyManager() (*KeyManager, error) {
    km := &KeyManager{}

    // Load HMAC secret
    km.hmacSecret = []byte("your-256-bit-secret")

    // Load RSA keys
    rsaPrivPEM, _ := os.ReadFile("rsa_private.pem")
    km.rsaPrivate, _ = jwt.ParseRSAPrivateKeyFromPEM(rsaPrivPEM)

    rsaPubPEM, _ := os.ReadFile("rsa_public.pem")
    km.rsaPublic, _ = jwt.ParseRSAPublicKeyFromPEM(rsaPubPEM)

    // Load ECDSA keys
    ecPrivPEM, _ := os.ReadFile("ec_private.pem")
    km.ecdsaPrivate, _ = jwt.ParseECPrivateKeyFromPEM(ecPrivPEM)

    ecPubPEM, _ := os.ReadFile("ec_public.pem")
    km.ecdsaPublic, _ = jwt.ParseECPublicKeyFromPEM(ecPubPEM)

    // Load EdDSA keys
    edPrivPEM, _ := os.ReadFile("ed25519_private.pem")
    km.eddsaPrivate, _ = jwt.ParseEdPrivateKeyFromPEM(edPrivPEM)

    edPubPEM, _ := os.ReadFile("ed25519_public.pem")
    km.eddsaPublic, _ = jwt.ParseEdPublicKeyFromPEM(edPubPEM)

    return km, nil
}

func (km *KeyManager) CreateToken(alg string, claims jwt.Claims) (string, error) {
    var method jwt.SigningMethod
    var key any

    switch alg {
    case "HS256":
        method = jwt.SigningMethodHS256
        key = km.hmacSecret
    case "HS384":
        method = jwt.SigningMethodHS384
        key = km.hmacSecret
    case "HS512":
        method = jwt.SigningMethodHS512
        key = km.hmacSecret
    case "RS256":
        method = jwt.SigningMethodRS256
        key = km.rsaPrivate
    case "RS384":
        method = jwt.SigningMethodRS384
        key = km.rsaPrivate
    case "RS512":
        method = jwt.SigningMethodRS512
        key = km.rsaPrivate
    case "PS256":
        method = jwt.SigningMethodPS256
        key = km.rsaPrivate
    case "PS384":
        method = jwt.SigningMethodPS384
        key = km.rsaPrivate
    case "PS512":
        method = jwt.SigningMethodPS512
        key = km.rsaPrivate
    case "ES256":
        method = jwt.SigningMethodES256
        key = km.ecdsaPrivate
    case "ES384":
        method = jwt.SigningMethodES384
        key = km.ecdsaPrivate
    case "ES512":
        method = jwt.SigningMethodES512
        key = km.ecdsaPrivate
    case "EdDSA":
        method = jwt.SigningMethodEdDSA
        key = km.eddsaPrivate
    default:
        return "", fmt.Errorf("unsupported algorithm: %s", alg)
    }

    token := jwt.NewWithClaims(method, claims)
    return token.SignedString(key)
}

func (km *KeyManager) VerifyToken(tokenString string) (*jwt.Token, error) {
    return jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
        switch method := token.Method.(type) {
        case *jwt.SigningMethodHMAC:
            return km.hmacSecret, nil
        case *jwt.SigningMethodRSA:
            return km.rsaPublic, nil
        case *jwt.SigningMethodRSAPSS:
            return km.rsaPublic, nil
        case *jwt.SigningMethodECDSA:
            return km.ecdsaPublic, nil
        case *jwt.SigningMethodEd25519:
            return km.eddsaPublic, nil
        default:
            return nil, fmt.Errorf("unexpected signing method: %v", method.Alg())
        }
    })
}

func main() {
    km, err := NewKeyManager()
    if err != nil {
        panic(err)
    }

    // Create claims
    claims := jwt.RegisteredClaims{
        Issuer:    "my-service",
        Subject:   "user123",
        ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
        IssuedAt:  jwt.NewNumericDate(time.Now()),
    }

    // Test each algorithm
    algorithms := []string{"HS256", "RS256", "PS256", "ES256", "EdDSA"}

    for _, alg := range algorithms {
        // Create token
        tokenString, err := km.CreateToken(alg, claims)
        if err != nil {
            fmt.Printf("Error creating %s token: %v\n", alg, err)
            continue
        }

        // Verify token
        token, err := km.VerifyToken(tokenString)
        if err != nil {
            fmt.Printf("Error verifying %s token: %v\n", alg, err)
            continue
        }

        fmt.Printf("%s: Token valid=%v\n", alg, token.Valid)
    }
}

Security Best Practices

Algorithm Selection

  1. Prefer EdDSA or ES256 for new projects
  2. Use RS256 only for compatibility with older systems
  3. Avoid HS256 for public APIs where key distribution is difficult
  4. Never accept "none" algorithm in production
  5. Use PS256 over RS256 when both are supported

Key Management

  1. Generate keys securely:

    • Use crypto/rand for random bytes
    • Use appropriate key sizes (2048+ for RSA, P-256+ for ECDSA)
  2. Store keys securely:

    • Use key management services (AWS KMS, Azure Key Vault, etc.)
    • Never commit keys to version control
    • Encrypt private keys at rest
  3. Rotate keys regularly:

    • Implement key rotation mechanisms
    • Support multiple keys during rotation
    • Track key usage with "kid" header
  4. Separate signing and verification:

    • Keep private keys isolated
    • Distribute public keys widely
    • Use different keys for different purposes

Validation

  1. Always validate algorithm:

    token, err := jwt.Parse(tokenString, func(token *jwt.Token) (any, error) {
        // Check algorithm is expected
        if _, ok := token.Method.(*jwt.SigningMethodEd25519); !ok {
            return nil, fmt.Errorf("unexpected alg: %v", token.Header["alg"])
        }
        return publicKey, nil
    })
  2. Use parser options for additional validation:

    parser := jwt.NewParser(
        jwt.WithValidMethods([]string{"EdDSA", "ES256"}),
        jwt.WithIssuer("trusted-issuer"),
    )
  3. Check token validity after parsing:

    if !token.Valid {
        return fmt.Errorf("invalid token")
    }

Common Vulnerabilities

  1. Algorithm confusion attack: Always validate the algorithm in your key function
  2. None algorithm acceptance: Explicitly reject "none" unless absolutely necessary
  3. Weak keys: Use minimum recommended key sizes
  4. Key reuse: Don't reuse keys across different services or purposes
  5. Missing validation: Always validate claims (exp, iss, aud, etc.)