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

key-parsing.mddocs/

Key Parsing Utilities

This document covers utilities for parsing cryptographic keys from PEM-encoded formats for use with JWT signing and verification.

Overview

The jwt library provides functions to parse PEM-encoded keys for RSA, ECDSA, and EdDSA algorithms. These utilities handle common key formats including PKCS#1, PKCS#8, and PKIX.

RSA Key Parsing

ParseRSAPrivateKeyFromPEM

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

Parses a PEM-encoded RSA private key supporting both PKCS#1 and PKCS#8 formats.

Supported formats:

  • PKCS#1: -----BEGIN RSA PRIVATE KEY-----
  • PKCS#8: -----BEGIN PRIVATE KEY-----

Returns: *rsa.PrivateKey for use with RS* and PS* signing methods.

ParseRSAPublicKeyFromPEM

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

Parses a PEM-encoded RSA public key from certificates or PKCS#1/PKIX formats.

Supported formats:

  • X.509 Certificate: -----BEGIN CERTIFICATE-----
  • PKCS#1: -----BEGIN RSA PUBLIC KEY-----
  • PKIX: -----BEGIN PUBLIC KEY-----

Returns: *rsa.PublicKey for signature verification.

ParseRSAPrivateKeyFromPEMWithPassword (Deprecated)

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

Parses a password-protected PEM-encoded RSA private key.

DEPRECATED: This function uses the deprecated x509.DecryptPEMBlock which relies on RFC 1423, considered insecure by design. Avoid using password-protected PEM keys or use alternative modern formats.

ECDSA Key Parsing

ParseECPrivateKeyFromPEM

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

Parses a PEM-encoded Elliptic Curve private key.

Supported format:

  • EC Private Key: -----BEGIN EC PRIVATE KEY-----

Returns: *ecdsa.PrivateKey for use with ES256, ES384, or ES512 signing methods.

ParseECPublicKeyFromPEM

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

Parses a PEM-encoded EC public key supporting both PKCS#1 and PKCS#8 formats.

Supported formats:

  • PKIX: -----BEGIN PUBLIC KEY-----
  • Other standard EC public key formats

Returns: *ecdsa.PublicKey for ECDSA signature verification.

EdDSA Key Parsing

ParseEdPrivateKeyFromPEM

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

Parses a PEM-encoded Edwards curve private key (Ed25519).

Supported format:

  • PKCS#8: -----BEGIN PRIVATE KEY-----

Returns: crypto.PrivateKey (specifically ed25519.PrivateKey) for use with EdDSA signing.

ParseEdPublicKeyFromPEM

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

Parses a PEM-encoded Edwards curve public key (Ed25519).

Supported format:

  • PKIX: -----BEGIN PUBLIC KEY-----

Returns: crypto.PublicKey (specifically ed25519.PublicKey) for EdDSA signature verification.

Usage Examples

Loading RSA Keys from Files

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

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

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

// Use for signing
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
tokenString, err := token.SignedString(privateKey)

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

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

// Use for verification
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return publicKey, nil
})

Loading ECDSA Keys

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

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

ecPrivateKey, err := jwt.ParseECPrivateKeyFromPEM(ecPrivateKeyBytes)
if err != nil {
    panic(err)
}

// Use for signing with ES256
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
tokenString, err := token.SignedString(ecPrivateKey)

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

ecPublicKey, err := jwt.ParseECPublicKeyFromPEM(ecPublicKeyBytes)
if err != nil {
    panic(err)
}

// Use for verification
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return ecPublicKey, nil
})

Loading Ed25519 Keys

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

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

edPrivateKey, err := jwt.ParseEdPrivateKeyFromPEM(edPrivateKeyBytes)
if err != nil {
    panic(err)
}

// Use for signing
token := jwt.NewWithClaims(jwt.SigningMethodEdDSA, claims)
tokenString, err := token.SignedString(edPrivateKey)

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

edPublicKey, err := jwt.ParseEdPublicKeyFromPEM(edPublicKeyBytes)
if err != nil {
    panic(err)
}

// Use for verification
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return edPublicKey, nil
})

Parsing Keys from Embedded Strings

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

const rsaPrivateKeyPEM = `-----BEGIN RSA PRIVATE KEY-----
MIIEpAIBAAKCAQEA...
-----END RSA PRIVATE KEY-----`

const rsaPublicKeyPEM = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----`

func loadEmbeddedKeys() (*rsa.PrivateKey, *rsa.PublicKey, error) {
    privateKey, err := jwt.ParseRSAPrivateKeyFromPEM([]byte(rsaPrivateKeyPEM))
    if err != nil {
        return nil, nil, err
    }

    publicKey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(rsaPublicKeyPEM))
    if err != nil {
        return nil, nil, err
    }

    return privateKey, publicKey, nil
}

Parsing Public Key from Certificate

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

// ParseRSAPublicKeyFromPEM can parse public keys from X.509 certificates
certBytes, err := os.ReadFile("certificate.pem")
if err != nil {
    panic(err)
}

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

// Use the public key from certificate for verification
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
    return publicKey, nil
})

Error Handling

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

func parseKeyWithErrorHandling(keyBytes []byte) (*rsa.PrivateKey, error) {
    key, err := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
    if err != nil {
        // Check specific errors
        if errors.Is(err, jwt.ErrKeyMustBePEMEncoded) {
            return nil, fmt.Errorf("key must be PEM encoded: %w", err)
        }
        if errors.Is(err, jwt.ErrNotRSAPrivateKey) {
            return nil, fmt.Errorf("not a valid RSA private key: %w", err)
        }
        return nil, fmt.Errorf("failed to parse key: %w", err)
    }
    return key, nil
}

Key Management Helper

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

type KeyPair struct {
    PrivateKey interface{}
    PublicKey  interface{}
}

func LoadRSAKeyPair(privateKeyPath, publicKeyPath string) (*KeyPair, error) {
    // Load private key
    privBytes, err := os.ReadFile(privateKeyPath)
    if err != nil {
        return nil, err
    }
    privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privBytes)
    if err != nil {
        return nil, err
    }

    // Load public key
    pubBytes, err := os.ReadFile(publicKeyPath)
    if err != nil {
        return nil, err
    }
    publicKey, err := jwt.ParseRSAPublicKeyFromPEM(pubBytes)
    if err != nil {
        return nil, err
    }

    return &KeyPair{
        PrivateKey: privateKey,
        PublicKey:  publicKey,
    }, nil
}

func LoadECKeyPair(privateKeyPath, publicKeyPath string) (*KeyPair, error) {
    // Load EC private key
    privBytes, err := os.ReadFile(privateKeyPath)
    if err != nil {
        return nil, err
    }
    privateKey, err := jwt.ParseECPrivateKeyFromPEM(privBytes)
    if err != nil {
        return nil, err
    }

    // Load EC public key
    pubBytes, err := os.ReadFile(publicKeyPath)
    if err != nil {
        return nil, err
    }
    publicKey, err := jwt.ParseECPublicKeyFromPEM(pubBytes)
    if err != nil {
        return nil, err
    }

    return &KeyPair{
        PrivateKey: privateKey,
        PublicKey:  publicKey,
    }, nil
}

Key Generation

While the jwt library doesn't provide key generation functions, here's how to generate keys using Go's standard library:

Generating RSA Keys

import (
    "crypto/rand"
    "crypto/rsa"
    "crypto/x509"
    "encoding/pem"
    "os"
)

func generateRSAKeyPair() error {
    // Generate 2048-bit RSA key
    privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
    if err != nil {
        return err
    }

    // Encode private key to PKCS#1 PEM
    privateKeyPEM := &pem.Block{
        Type:  "RSA PRIVATE KEY",
        Bytes: x509.MarshalPKCS1PrivateKey(privateKey),
    }

    // Write private key
    privateFile, err := os.Create("private_key.pem")
    if err != nil {
        return err
    }
    defer privateFile.Close()
    pem.Encode(privateFile, privateKeyPEM)

    // Encode public key to PKIX PEM
    publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
    if err != nil {
        return err
    }
    publicKeyPEM := &pem.Block{
        Type:  "PUBLIC KEY",
        Bytes: publicKeyBytes,
    }

    // Write public key
    publicFile, err := os.Create("public_key.pem")
    if err != nil {
        return err
    }
    defer publicFile.Close()
    pem.Encode(publicFile, publicKeyPEM)

    return nil
}

Generating ECDSA Keys

import (
    "crypto/ecdsa"
    "crypto/elliptic"
    "crypto/rand"
    "crypto/x509"
    "encoding/pem"
    "os"
)

func generateECDSAKeyPair() error {
    // Generate ECDSA key with P-256 curve (for ES256)
    privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
    if err != nil {
        return err
    }

    // Encode private key to PEM
    privateKeyBytes, err := x509.MarshalECPrivateKey(privateKey)
    if err != nil {
        return err
    }
    privateKeyPEM := &pem.Block{
        Type:  "EC PRIVATE KEY",
        Bytes: privateKeyBytes,
    }

    privateFile, err := os.Create("ec_private.pem")
    if err != nil {
        return err
    }
    defer privateFile.Close()
    pem.Encode(privateFile, privateKeyPEM)

    // Encode public key to PEM
    publicKeyBytes, err := x509.MarshalPKIXPublicKey(&privateKey.PublicKey)
    if err != nil {
        return err
    }
    publicKeyPEM := &pem.Block{
        Type:  "PUBLIC KEY",
        Bytes: publicKeyBytes,
    }

    publicFile, err := os.Create("ec_public.pem")
    if err != nil {
        return err
    }
    defer publicFile.Close()
    pem.Encode(publicFile, publicKeyPEM)

    return nil
}

Generating Ed25519 Keys

import (
    "crypto/ed25519"
    "crypto/rand"
    "crypto/x509"
    "encoding/pem"
    "os"
)

func generateEd25519KeyPair() error {
    // Generate Ed25519 key
    publicKey, privateKey, err := ed25519.GenerateKey(rand.Reader)
    if err != nil {
        return err
    }

    // Encode private key to PKCS#8 PEM
    privateKeyBytes, err := x509.MarshalPKCS8PrivateKey(privateKey)
    if err != nil {
        return err
    }
    privateKeyPEM := &pem.Block{
        Type:  "PRIVATE KEY",
        Bytes: privateKeyBytes,
    }

    privateFile, err := os.Create("ed25519_private.pem")
    if err != nil {
        return err
    }
    defer privateFile.Close()
    pem.Encode(privateFile, privateKeyPEM)

    // Encode public key to PKIX PEM
    publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey)
    if err != nil {
        return err
    }
    publicKeyPEM := &pem.Block{
        Type:  "PUBLIC KEY",
        Bytes: publicKeyBytes,
    }

    publicFile, err := os.Create("ed25519_public.pem")
    if err != nil {
        return err
    }
    defer publicFile.Close()
    pem.Encode(publicFile, publicKeyPEM)

    return nil
}

Error Types

RSA Key Errors

var (
    ErrKeyMustBePEMEncoded error
    ErrNotRSAPrivateKey    error
    ErrNotRSAPublicKey     error
)

ECDSA Key Errors

var (
    ErrNotECPublicKey  error
    ErrNotECPrivateKey error
)

EdDSA Key Errors

var (
    ErrNotEdPrivateKey error
    ErrNotEdPublicKey  error
)

Best Practices

Key Storage

  • Never commit keys to version control
  • Store keys in secure locations with appropriate file permissions
  • Use environment variables or configuration management for key paths
  • Consider using Hardware Security Modules (HSMs) or Key Management Services (KMS)

Key Formats

  • Prefer PKCS#8 format for private keys (more standardized)
  • Use PKIX format for public keys
  • Avoid password-protected PEM keys (RFC 1423 is insecure)

Key Sizes

  • RSA: Minimum 2048 bits, 4096 bits for high security
  • ECDSA: P-256 (ES256), P-384 (ES384), or P-521 (ES512)
  • Ed25519: Fixed 256-bit keys

Error Handling

Always handle parsing errors appropriately:

key, err := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
if err != nil {
    // Log error securely (don't expose key contents)
    log.Printf("Failed to parse RSA private key: %v", err)
    return fmt.Errorf("key parsing failed")
}

Key Validation

After parsing, validate key properties:

privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
if err != nil {
    return nil, err
}

// Validate key size
if privateKey.N.BitLen() < 2048 {
    return nil, errors.New("RSA key size must be at least 2048 bits")
}

Common Issues

PEM Format Issues

Ensure keys are properly PEM-encoded with correct headers and footers:

-----BEGIN RSA PRIVATE KEY-----
... base64-encoded key data ...
-----END RSA PRIVATE KEY-----

Key Type Mismatches

Make sure the key type matches the signing method:

  • RS*/PS* algorithms require RSA keys
  • ES* algorithms require ECDSA keys
  • EdDSA requires Ed25519 keys

File Reading Errors

Handle file reading errors separately from parsing errors for better debugging:

keyBytes, err := os.ReadFile("key.pem")
if err != nil {
    return fmt.Errorf("failed to read key file: %w", err)
}

key, err := jwt.ParseRSAPrivateKeyFromPEM(keyBytes)
if err != nil {
    return fmt.Errorf("failed to parse key: %w", err)
}