·2 min read·← All posts
Go JWT Security Cryptography

The choice in one paragraph

HS256 uses a shared secret. Whoever holds the secret can both sign and verify. Same key both ways.

RS256 uses an asymmetric key pair. The private key signs; the public key verifies. Different keys; verifiers don’t need the secret.

When each is correct

HS256 is right when the same process (or trust boundary) both issues and verifies. A single Go service that mints tokens for its own consumption and consumes them on subsequent requests. The secret lives in one place; rotation is a one-knob operation.

RS256 is right when verifiers are separate from issuers. A central IdP signs tokens; many downstream services verify them. The downstreams hold only the public key; even a compromised downstream can’t mint forgeries.

The classic mistake

Some teams reach for RS256 by default “because asymmetric is more secure.” For a single-issuer-single-verifier system this buys nothing and costs:

If you’re not splitting issue from verify, HS256 is the simpler choice.

What changes if the key leaks

HS256 leak. Every token ever issued or issued during the leak window is forgeable. The mitigation is rotation: change the secret, invalidate all in-flight tokens, force re-authentication.

RS256 private-key leak. Same as HS256 — forgeable. The public-key-only leak doesn’t matter (it’s already public).

The blast radius is identical; the difference is who holds the key. Asymmetric reduces who might leak, not what happens when leak occurs.

What this looks like in Go

// HS256 (Genie default)
issuer := auth.NewIssuer(secret, "genie-api", []string{"genie-api"}, 60*time.Minute)
token, _, _ := issuer.Issue(userID, email, roles)
claims, _ := issuer.Verify(token)

// RS256 (federated deployment)
priv, _ := loadPrivateKey("genie.key")
pub, _ := loadPublicKey("genie.pub")
issuer := auth.NewRSAIssuer(priv, "genie-api", aud, ttl)
verifier := auth.NewRSAVerifier(pub, "genie-api", aud)

For Genie’s reference single-issuer topology, HS256 wins. When a federated deployment lands (bank issues SAML/OIDC token, Genie consumes), RS256 lives in the verifier slot — pkg/auth is designed to swap.

The boring rule: pick HS256 unless you actually have multiple verification boundaries. Asymmetric crypto for its own sake is the wrong default.

← Back to all posts