This document covers utilities for parsing cryptographic keys from PEM-encoded formats for use with JWT signing and verification.
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.
func ParseRSAPrivateKeyFromPEM(key []byte) (*rsa.PrivateKey, error)Parses a PEM-encoded RSA private key supporting both PKCS#1 and PKCS#8 formats.
Supported formats:
-----BEGIN RSA PRIVATE KEY----------BEGIN PRIVATE KEY-----Returns: *rsa.PrivateKey for use with RS* and PS* signing methods.
func ParseRSAPublicKeyFromPEM(key []byte) (*rsa.PublicKey, error)Parses a PEM-encoded RSA public key from certificates or PKCS#1/PKIX formats.
Supported formats:
-----BEGIN CERTIFICATE----------BEGIN RSA PUBLIC KEY----------BEGIN PUBLIC KEY-----Returns: *rsa.PublicKey for signature verification.
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.
func ParseECPrivateKeyFromPEM(key []byte) (*ecdsa.PrivateKey, error)Parses a PEM-encoded Elliptic Curve private key.
Supported format:
-----BEGIN EC PRIVATE KEY-----Returns: *ecdsa.PrivateKey for use with ES256, ES384, or ES512 signing methods.
func ParseECPublicKeyFromPEM(key []byte) (*ecdsa.PublicKey, error)Parses a PEM-encoded EC public key supporting both PKCS#1 and PKCS#8 formats.
Supported formats:
-----BEGIN PUBLIC KEY-----Returns: *ecdsa.PublicKey for ECDSA signature verification.
func ParseEdPrivateKeyFromPEM(key []byte) (crypto.PrivateKey, error)Parses a PEM-encoded Edwards curve private key (Ed25519).
Supported format:
-----BEGIN PRIVATE KEY-----Returns: crypto.PrivateKey (specifically ed25519.PrivateKey) for use with EdDSA signing.
func ParseEdPublicKeyFromPEM(key []byte) (crypto.PublicKey, error)Parses a PEM-encoded Edwards curve public key (Ed25519).
Supported format:
-----BEGIN PUBLIC KEY-----Returns: crypto.PublicKey (specifically ed25519.PublicKey) for EdDSA signature verification.
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
})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
})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
})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
}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
})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
}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
}While the jwt library doesn't provide key generation functions, here's how to generate keys using Go's standard library:
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
}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
}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
}var (
ErrKeyMustBePEMEncoded error
ErrNotRSAPrivateKey error
ErrNotRSAPublicKey error
)var (
ErrNotECPublicKey error
ErrNotECPrivateKey error
)var (
ErrNotEdPrivateKey error
ErrNotEdPublicKey error
)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")
}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")
}Ensure keys are properly PEM-encoded with correct headers and footers:
-----BEGIN RSA PRIVATE KEY-----
... base64-encoded key data ...
-----END RSA PRIVATE KEY-----Make sure the key type matches the signing method:
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)
}