go-jose: JOSE, JWE, JWS and JWT for Go without the compatibility surprises
An implementation of JOSE standards (JWE, JWS, JWT) in Go
At a glance
- What is it?
- go-jose implements the JOSE standards in Go, covering JSON Web Encryption, JSON Web Signature and JSON Web Token, with both compact and JSON serialisations and optional multi-recipient support. It is the library Go services tend to reach for when the standard library has no JOSE support, and it ships a jose-util command line tool.
- Who is it for?
- go-jose suits a Go service that has to produce or consume JOSE messages, particularly one where interoperability matters more than API convenience, since its case-sensitive JSON parsing avoids exactly the class of bug where two libraries disagree about what a token member name means.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
What the package covers
The jose package aims to provide an implementation of the Javascript Object Signing and Encryption set of standards, with support for JSON Web Encryption, JSON Web Signature and JSON Web Token.
The three specifications it follows are RFC 7516 for JSON Web Encryption, RFC 7515 for JSON Web Signature and RFC 7519 for JSON Web Token. The library supports both the compact and JWS/JWE JSON Serialization formats, and has optional support for multiple recipients.
The multiple recipients support is the feature that distinguishes it from a minimal JWT library. In JWE, a message can be encrypted once under a content encryption key and that key wrapped for several recipients, each of whom unwraps it with their own key. That is what you need for a message addressed to a set of parties, and it is not something a simple sign-and-verify helper offers.
The algorithm coverage is broad. For key encryption there is RSA-PKCS#1v1.5 as RSA1_5, RSA-OAEP and RSA-OAEP-256, AES key wrap as A128KW, A192KW and A256KW, AES-GCM key wrap as A128GCMKW, A192GCMKW and A256GCMKW, ECDH-ES with AES key wrap as ECDH-ES+A128KW and its 192 and 256 variants, plus ECDH-ES direct and direct encryption, with a footnote that the last two are not supported in multi-recipient mode.
For signing and MAC there is RSASSA-PKCS#1v1.5 as RS256, RS384 and RS512, RSASSA-PSS as PS256, PS384 and PS512, HMAC as HS256, HS384 and HS512, ECDSA as ES256, ES384 and ES512, and Ed25519 as EdDSA, which the footnote marks as only available in version 2 of the package.
Content encryption covers AES-CBC with HMAC as A128CBC-HS256 and its 192 and 512 variants, and AES-GCM as A128GCM, A192GCM and A256GCM. Compression is DEFLATE under RFC 1951, exposed as the DEF algorithm identifier.
The case-sensitive JSON choice, and why it matters
There is a note in the README that looks like a footnote and is actually the design decision that matters most. The library uses a forked version of the encoding/json package from the Go standard library which uses case-sensitive matching for member names, instead of the standard library's case-insensitive matching.
The stated reason is to avoid differences in interpretation of messages between go-jose and libraries in other languages.
That is a real interoperability problem and it is worth understanding why. Go's encoding/json matches JSON member names to struct fields case-insensitively, so a token containing a header field named Algorithm would populate a struct field tagged algorithm, and so would one named ALGORITHM. Other JOSE implementations, in other languages, will typically treat the member name as exact. A token that one library accepts and another rejects on a case difference is a genuine production failure, and the disagreement usually surfaces as a signature verification failing in one place and passing in another, which is a miserable bug to chase.
By parsing strictly, go-jose refuses the ambiguous tokens rather than accepting them with a different interpretation than the peer. That is the safer behaviour for a security library: rejecting a non-canonical message is better than signing or verifying something whose meaning is not agreed upon.
The cost is that the library carries a fork of encoding/json, which is a maintenance consideration in its own right. The fork lives in the json/ directory at the repository root, and it is visible in the repository layout rather than hidden in a dependency.
Version strategy and the Go version floor
The version story in the README is unusually well documented, which is itself a good sign.
Version 4 is the current stable version, imported as github.com/go-jose/go-jose/v4, and it requires Golang 1.24. The go.mod confirms this with a go directive of 1.24.0. The module path includes the major version suffix, so the import line is:
import "github.com/go-jose/go-jose/v4"Version 4 supports at least the current and previous Golang release.
Version 3 is only receiving critical security updates, and migration to version 4 is recommended. Versions 1 and 2 are obsolete and live in an old repository, square/go-jose.
Then there is version 5, and this is the part to pay attention to. The forthcoming version 5 will be released with several breaking API changes, and will require Golang's encoding/json/v2, which currently requires Go 1.25 built with GOEXPERIMENT=jsonv2.
That last detail is a genuine constraint rather than a formality. GOEXPERIMENT is a build-time opt-in flag, so a version 5 consumer would need a Go toolchain built with an experiment enabled, or a future release where the experiment graduated. For teams on a standard release channel today, version 4 is the only realistic choice, and the README says as much.
For a security library, the fact that version 3 still receives critical security updates and that a migration path is named rather than a break is the important part. A library that handles signature verification cannot simply be abandoned, and this one is not being abandoned.
Key types and the JWK wrapper
The supported key types table maps algorithms to the Go types you pass to functions such as NewEncrypter or NewSigner.
RSA maps to *rsa.PublicKey and *rsa.PrivateKey from crypto/rsa. ECDH and ECDSA map to *ecdsa.PublicKey and *ecdsa.PrivateKey from crypto/ecdsa. EdDSA maps to ed25519.PublicKey and ed25519.PrivateKey from crypto/ed25519, with a footnote that these are only available in version 2 or later of the package. AES and HMAC map to a plain []byte.
Everything there is from the Go standard library's crypto packages. That is a deliberate and important property: a JOSE library that defined its own key types would force conversions at every boundary, and the risk of a conversion mistake in key handling is exactly the kind of bug a security library should not have. Here the RSA key you already have is the RSA key the library wants.
The last piece is the JWK wrapper. Each of these keys can also be wrapped in a JWK if desired, which allows attaching a key id. So the same key can be passed as a bare crypto/rsa type where you have it in memory, or as a JSON Web Key with a kid when you need to serialise it or select it from a set.
The repository layout shows where each responsibility lives. asymmetric.go, symmetric.go, shared.go, signing.go and crypter.go hold the core types, jwk.go the key wrapping, jwe.go and jws.go the two message formats, encoding.go the serialisation, and opaque.go the encrypted key structures. The jwt/ directory holds the token-level API, cipher/ the block cipher modes, json/ the forked encoder, cryptosigner/ an adapter to the crypto.Signer interface, fuzz/ the fuzzing targets, and testutils/ the shared test helpers.
jose-util and the security posture
The package ships a small command line utility, jose-util, for dealing with JOSE messages in a shell. The README points at it both as a practical tool and as a piece of example code, since the jose-util subdirectory is described as containing a small command line utility that might be useful as an example as well.
The repository also carries a threat_model.md at the root, which is a detail worth noticing in a cryptography library. Alongside it are a SECURITY.md for reporting issues, a CONTRIBUTING.md, and a .golangci.yml for linting. The presence of a written threat model means the library's designers have stated what they consider in scope and out of scope, which is the sort of document that lets a user evaluate a crypto library rather than take it on trust.
The fuzz/ directory is the other one that signals care. Fuzz targets exist for the parsers, which is where a JOSE library's real risk lies: not in the arithmetic, since that is the standard library's job, but in the parsing of attacker-controlled JSON, header fields and compact serialisations.
The test layout follows the same logic, with a dedicated test file beside each source file, asymmetric_test.go, crypter_test.go, jwk_test.go, jws_test.go, and a utils_test.go, plus a tests/ directory with a Containerfile so the suite can run in a container.
One caveat about the utility itself: a shell tool that decodes and re-encodes JOSE messages is useful, but it should be read as an example of the API rather than as a security boundary. Anything it prints is now in your terminal history and your scrollback.
Where go-jose is the wrong choice
Three limits are structural. The first is that it is not a JWT validation service. go-jose gives you the primitives, and the jwt package gives you parsing and signing helpers, but deciding whether a token is acceptable to your application is your code. There is no built-in notion of issuer allowlists, audience checking, clock skew tolerance or a key set fetched from a discovery document. Every project using it writes that policy itself, which means every project gets it slightly differently, and a policy bug in one of those layers is a security bug.
The second is that version 5 will break your code. The README says so explicitly, with several breaking API changes and a requirement for encoding/json/v2 under a GOEXPERIMENT build. A library you depend on for signature verification is exactly the kind of dependency you cannot easily swap, so a planned major version break is a scheduled piece of work rather than a surprise, and the migration is on your calendar either way.
The third is the language and ecosystem boundary. This is a Go library, and its idioms are Go's, with crypto.Signer adapters and Go key types. A polyglot organisation that needs the same JOSE behaviour in its TypeScript service and its Go service has to reconcile two implementations, and the case-sensitive parsing decision that makes go-jose predictable against its peers is only half the story, because the other half is that the peer library has to be equally strict.
The alternative worth naming is the standard library plus a thin layer. If you only need to parse a JWT and check a signature with a known key, encoding/base64 for the segments and encoding/json for the payload, then crypto/ecdsa or crypto/ed25519 for the verification, is a few dozen lines with no dependency at all. The difference in approach is that go-jose gives you the full set of JOSE formats, multi-recipient encryption, JWK handling, key management for symmetric wrapping algorithms and a tested parser for adversarial input, none of which you should write yourself. The judgement is about how much of JOSE you actually need.
Editorial conclusion
go-jose suits a Go service that has to produce or consume JOSE messages, particularly one where interoperability matters more than API convenience, since its case-sensitive JSON parsing avoids exactly the class of bug where two libraries disagree about what a token member name means. It is the wrong tool if you only need to parse a JWT without verifying it, since the standard library's encoding/base64 plus encoding/json will do, and it is the wrong tool if you are starting a new project today, because the README states that the forthcoming version 5 will require encoding/json/v2 and Go 1.25 built with GOEXPERIMENT=jsonv2. Verify first that version 4 satisfies your Go toolchain, and note that v4.1.6 was released on 2026-10-14 with the last push on the same day.
Frequently asked questions
Which JOSE standards does go-jose implement?
JSON Web Encryption per RFC 7516, JSON Web Signature per RFC 7515 and JSON Web Token per RFC 7519. It supports both the compact and the JWS and JWE JSON Serialization formats, and has optional support for multiple recipients.
How do I import go-jose version 4?
The module path carries the major version suffix, so the import is github.com/go-jose/go-jose/v4. Version 4 requires Golang 1.24, and the go.mod in the repository declares go 1.24.0.
Why does go-jose use its own fork of encoding/json?
The fork uses case-sensitive matching for member names instead of the standard library's case-insensitive matching, to avoid differences in interpretation of messages between go-jose and libraries in other languages. The forked package lives in the json/ directory of the repository.
Which signing and encryption algorithms does go-jose support?
Signing covers RS256, RS384, RS512, PS256, PS384, PS512, HS256, HS384, HS512, ES256, ES384, ES512 and EdDSA. Content encryption covers AES-CBC with HMAC as A128CBC-HS256 and its 192 and 512 variants and AES-GCM as A128GCM, A192GCM and A256GCM, with DEFLATE available under the DEF compression identifier.
What is the difference between version 3 and version 4 of go-jose?
Version 4 is the current stable version and supports at least the current and previous Golang release, while version 3 is only receiving critical security updates and the README recommends migrating to version 4. Versions 1 and 2 are obsolete and live in the old square/go-jose repository.
What will change in go-jose version 5?
The forthcoming version 5 will have several breaking API changes and will require Golang's encoding/json/v2, which currently requires Go 1.25 built with GOEXPERIMENT=jsonv2. Version 4 remains the stable choice for a standard toolchain today.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/go-jose-go-jose)Was this useful?