Repository: lestrrat-go/jwx
Pull request #932 (2023-06-12 06:26:30)

merge for v2.0.10
Fix history... boy, what a mess

codecov[bot]: ## [Codecov](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) Report
> Merging [#932](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) (796b2a9) into [v2](https://app.codecov.io/gh/lestrrat-go/jwx/commit/43ca1406d71955b098e833da5fc170ddef88e51a?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) (43ca140) will **decrease** coverage by `0.35%`.
> The diff coverage is `71.66%`.

```diff
@@            Coverage Diff             @@
##               v2     #932      +/-   ##
==========================================
- Coverage   71.78%   71.44%   -0.35%     
==========================================
  Files          90       93       +3     
  Lines        9662    13555    +3893     
==========================================
+ Hits         6936     9684    +2748     
- Misses       1928     3055    +1127     
- Partials      798      816      +18     
```


| [Impacted Files](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) | Coverage Δ | |
|---|---|---|
| [formatkind\_string\_gen.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-Zm9ybWF0a2luZF9zdHJpbmdfZ2VuLmdv) | `100.00% <ø> (ø)` | |
| [internal/json/goccy.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-aW50ZXJuYWwvanNvbi9nb2NjeS5nbw==) | `100.00% <ø> (ø)` | |
| [jwa/secp2561k.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andhL3NlY3AyNTYxay5nbw==) | `100.00% <ø> (ø)` | |
| [jwe/compress.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andlL2NvbXByZXNzLmdv) | `0.00% <0.00%> (ø)` | |
| [jwe/headers\_gen.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andlL2hlYWRlcnNfZ2VuLmdv) | `66.01% <ø> (-1.71%)` | :arrow_down: |
| [jwe/internal/cipher/cipher.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andlL2ludGVybmFsL2NpcGhlci9jaXBoZXIuZ28=) | `62.06% <0.00%> (+1.67%)` | :arrow_up: |
| [jwe/io.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andlL2lvLmdv) | `53.33% <ø> (+9.58%)` | :arrow_up: |
| [jwk/es256k.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL2VzMjU2ay5nbw==) | `100.00% <ø> (ø)` | |
| [jwk/io.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL2lvLmdv) | `42.10% <ø> (-1.65%)` | :arrow_down: |
| [jwk/options\_gen.go](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go#diff-andrL29wdGlvbnNfZ2VuLmdv) | `75.86% <ø> (+11.15%)` | :arrow_up: |
| ... and [63 more](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go) | |

... and [20 files with indirect coverage changes](https://app.codecov.io/gh/lestrrat-go/jwx/pull/932/indirect-changes?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=lestrrat-go)



Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
index 96bc6b46..32efae2c 100644
--- a/.github/dependabot.yml
+++ b/.github/dependabot.yml
@@ -10,13 +10,27 @@ updates:
     schedule:
       interval: "daily"
     target-branch: "develop/v2"
+    labels:
+      - "go"
+      - "dependencies"
+      - "dependabot"
   - package-ecosystem: "gomod" # See documentation for possible values
     directory: "/" # Location of package manifests
     schedule:
       interval: "daily"
     target-branch: "develop/v1"
+    labels:
+      - "go"
+      - "dependencies"
+      - "dependabot"
   - package-ecosystem: "github-actions"
     directory: "/"
     schedule:
       interval: "daily"
     target-branch: "develop/v2"
+  - package-ecosystem: "github-actions"
+    directory: "/"
+    schedule:
+      interval: "daily"
+    target-branch: "develop/v1"
+
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 1f59c032..3311e394 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,8 +10,8 @@ jobs:
     runs-on: ubuntu-latest
     strategy:
       matrix:
-        go_tags: [ 'stdlib', 'goccy', 'es256k', 'alltags']
-        go: [ '1.19', '1.18', '1.17' ]
+        go_tags: [ 'stdlib', 'goccy', 'es256k', 'asmbase64', 'alltags']
+        go: [ '1.20', '1.19', '1.18']
     name: "Test [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]"
     steps:
       - name: Checkout repository
@@ -44,7 +44,7 @@ jobs:
       - name: Install stringer
         run: go install golang.org/x/tools/cmd/stringer@latest
       - name: Install tparse
-        run: go install github.com/mfridman/tparse@latest
+        run: go install github.com/mfridman/tparse@v0.12.2
       - name: Install jose
         run: sudo apt-get install -y --no-install-recommends jose
       - run: make generate
@@ -57,7 +57,7 @@ jobs:
         uses: codecov/codecov-action@v3
         with:
           file: ./coverage.out
+      - uses: bazelbuild/setup-bazelisk@v2
+      - run: bazel run //:gazelle-update-repos
       - name: Check difference between generation code and commit code
         run: make check_diffs
-      - uses: bazelbuild/setup-bazelisk@v2
-      - run: bazel build //...
diff --git a/.github/workflows/smoke.yml b/.github/workflows/smoke.yml
index a7fb00b6..6e4fad8f 100644
--- a/.github/workflows/smoke.yml
+++ b/.github/workflows/smoke.yml
@@ -14,7 +14,7 @@ jobs:
     strategy:
       matrix:
         go_tags: [ 'stdlib', 'goccy', 'es256k', 'alltags' ]
-        go: [ '1.19', '1.18', '1.17' ]
+        go: [ '1.20', '1.19', '1.18' ]
     name: "Smoke [ Go ${{ matrix.go }} / Tags ${{ matrix.go_tags }} ]"
     steps:
       - name: Checkout repository
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
index d79f8e98..4277b638 100644
--- a/.github/workflows/stale.yml
+++ b/.github/workflows/stale.yml
@@ -7,7 +7,7 @@ jobs:
   stale:
     runs-on: ubuntu-latest
     steps:
-      - uses: actions/stale@v7
+      - uses: actions/stale@v8
         with:
           stale-issue-message: 'This issue is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 7 days.'
           stale-pr-message: 'This PR is stale because it has been open 14 days with no activity. Remove stale label or comment or this will be closed in 14 days.'
diff --git a/Changes b/Changes
index e20b7577..9f414a62 100644
--- a/Changes
+++ b/Changes
@@ -4,6 +4,42 @@ Changes
 v2 has many incompatibilities with v1. To see the full list of differences between
 v1 and v2, please read the Changes-v2.md file (https://github.com/lestrrat-go/jwx/blob/develop/v2/Changes-v2.md)
 
+v2.0.10 - 12 Jun 2023
+[New Features]
+  * [jwe] (EXPERIMENTAL) Added `jwe.KeyEncrypter` and `jwe.KeyDecrypter` interfaces
+    that works in similar ways as how `crypto.Signer` works for signature
+    generation and verification. It can act as the interface for your encryption/decryption
+    keys that are for example stored in an hardware device.
+
+    This feature is labeled experimental because the API for the above interfaces have not
+    been battle tested, and may need to changed yet. Please be aware that until the API
+    is deemed stable, you may have to adapat our code to these possible changes,
+    _even_ during minor version upgrades of this library.
+   
+[Bug fixes]
+  * Registering JWS signers/verifiers did not work since v2.0.0, because the
+    way we handle algorithm names changed in 2aa98ce6884187180a7145b73da78c859dd46c84.
+    (We previously thought that this would be checked by the example code, but it
+     apparently failed to flag us properly)
+
+    The logic behind managing the internal database has been fixed, and
+    `jws.RegisterSigner` and `jws.RegisterVerifier` now properly hooks into the new
+    `jwa.RegisterSignatureAlgorithm` to automatically register new algorithm names
+    (#910, #911)
+[Miscellaneous]
+  * Added limited support for github.com/segmentio/asm/base64. Compile your code
+    with the `jwx_asmbase64` build tag. This feature is EXPERIMENTAL.
+
+    Through limited testing, the use of a faster base64 library provide 1~5% increase
+    in throughput on average. It might make more difference if the input/output is large.
+    If you care about this performance improvement, you should probably enable
+    `goccy` JSON parser as well, by specifying `jwx_goccy,jwx_asmbase64` in your build call.
+  * Slightly changed the way global variables underneath `jwk.Fetch` are initialized and
+    configured. `jwk.Fetch` creates an object that spawns wokers to fetch JWKS when it's
+    first called.
+    You can now also use `jwk.SetGlobalFetcher()` to set a fetcher object which you can
+    control.
+
 v2.0.9 - 21 Mar 2023
 [Security Fixes]
   * Updated use of golang.org/x/crypto to v0.7.0
diff --git a/Makefile b/Makefile
index f3247b25..40add854 100644
--- a/Makefile
+++ b/Makefile
@@ -25,8 +25,11 @@ test-goccy:
 test-es256k:
 	$(MAKE) test-cmd TESTOPTS="-tags jwx_es256k"
 
+test-asmbase64:
+	$(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64"
+
 test-alltags:
-	$(MAKE) test-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k"
+	$(MAKE) test-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k"
 
 cover-cmd:
 	env MODE=cover ./tools/test.sh
@@ -43,8 +46,11 @@ cover-goccy:
 cover-es256k:
 	$(MAKE) cover-cmd TESTOPTS="-tags jwx_es256k"
 
+cover-asmbase64:
+	$(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64"
+
 cover-alltags:
-	$(MAKE) cover-cmd TESTOPTS="-tags jwx_goccy,jwx_es256k"
+	$(MAKE) cover-cmd TESTOPTS="-tags jwx_asmbase64,jwx_goccy,jwx_es256k"
 
 smoke-cmd:
 	env MODE=short ./tools/test.sh
diff --git a/README.md b/README.md
index c7114564..2bfb71fb 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ If you are using this module in your product or your company, please add  your p
   * Supports JWS with unencoded payload (RFC7797)
   * Supports JWE messages with multiple recipients, both compact and JSON serialization
   * Most operations work with either JWK or raw keys e.g. *rsa.PrivateKey, *ecdsa.PrivateKey, etc).
-* Opinionated, but very uniform API. Everything is symmetric, and follows a standard convetion
+* Opinionated, but very uniform API. Everything is symmetric, and follows a standard convention
   * jws.Parse/Verify/Sign
   * jwe.Parse/Encrypt/Decrypt
   * Arguments are organized as explicit required paramters and optional WithXXXX() style options.
diff --git a/bench/comparison/Makefile b/bench/comparison/Makefile
index daa851db..32b187de 100644
--- a/bench/comparison/Makefile
+++ b/bench/comparison/Makefile
@@ -4,3 +4,6 @@ stdlib:
 goccy:
 	go test -bench . -benchmem -tags jwx_goccy | tee goccy.txt
 
+asmbase64:
+	go test -bench . -benchmem -tags jwx_asmbase64 | tee asmbase64.txt
+
diff --git a/bench/performance/Makefile b/bench/performance/Makefile
index 7db68fe8..1e64e2a7 100644
--- a/bench/performance/Makefile
+++ b/bench/performance/Makefile
@@ -4,5 +4,11 @@ stdlib:
 goccy:
 	go test -bench . -benchmem -count 5 -timeout 60m -tags jwx_goccy | tee goccy.txt
 
+asmbase64:
+	go test -bench . -benchmem -count 5 -timeout 60m -tags jwx_asmbase64 | tee asmbase64.txt
+
+goccy-asmbase64:
+	go test -bench . -benchmem -count 5 -timeout 60m -tags jwx_goccy,jwx_asmbase64 | tee goccy-asmbase64.txt
+
 benchstat:
 	benchstat stdlib.txt goccy.txt
diff --git a/deps.bzl b/deps.bzl
index e9ca9716..4ecdfb6e 100644
--- a/deps.bzl
+++ b/deps.bzl
@@ -12,15 +12,15 @@ def go_dependencies():
         name = "com_github_decred_dcrd_crypto_blake256",
         build_file_proto_mode = "disable_global",
         importpath = "github.com/decred/dcrd/crypto/blake256",
-        sum = "h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0=",
-        version = "v1.0.0",
+        sum = "h1:7PltbUIQB7u/FfZ39+DGa/ShuMyJ5ilcvdfma9wOH6Y=",
+        version = "v1.0.1",
     )
     go_repository(
         name = "com_github_decred_dcrd_dcrec_secp256k1_v4",
         build_file_proto_mode = "disable_global",
         importpath = "github.com/decred/dcrd/dcrec/secp256k1/v4",
-        sum = "h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=",
-        version = "v4.1.0",
+        sum = "h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=",
+        version = "v4.2.0",
     )
     go_repository(
         name = "com_github_goccy_go_json",
@@ -73,6 +73,14 @@ def go_dependencies():
         sum = "h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=",
         version = "v1.0.0",
     )
+    go_repository(
+        name = "com_github_segmentio_asm",
+        build_file_proto_mode = "disable_global",
+        importpath = "github.com/segmentio/asm",
+        sum = "h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=",
+        version = "v1.2.0",
+    )
+
     go_repository(
         name = "com_github_stretchr_objx",
         build_file_proto_mode = "disable_global",
@@ -84,8 +92,8 @@ def go_dependencies():
         name = "com_github_stretchr_testify",
         build_file_proto_mode = "disable_global",
         importpath = "github.com/stretchr/testify",
-        sum = "h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=",
-        version = "v1.8.2",
+        sum = "h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=",
+        version = "v1.8.4",
     )
     go_repository(
         name = "com_github_yuin_goldmark",
@@ -114,8 +122,8 @@ def go_dependencies():
         name = "org_golang_x_crypto",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/crypto",
-        sum = "h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=",
-        version = "v0.7.0",
+        sum = "h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=",
+        version = "v0.9.0",
     )
     go_repository(
         name = "org_golang_x_mod",
@@ -129,8 +137,8 @@ def go_dependencies():
         name = "org_golang_x_net",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/net",
-        sum = "h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=",
-        version = "v0.8.0",
+        sum = "h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=",
+        version = "v0.10.0",
     )
     go_repository(
         name = "org_golang_x_sync",
@@ -144,23 +152,23 @@ def go_dependencies():
         name = "org_golang_x_sys",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/sys",
-        sum = "h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=",
-        version = "v0.6.0",
+        sum = "h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=",
+        version = "v0.8.0",
     )
     go_repository(
         name = "org_golang_x_term",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/term",
-        sum = "h1:clScbb1cHjoCkyRbWwBEUZ5H/tIFu5TAXIqaZD0Gcjw=",
-        version = "v0.6.0",
+        sum = "h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=",
+        version = "v0.8.0",
     )
 
     go_repository(
         name = "org_golang_x_text",
         build_file_proto_mode = "disable_global",
         importpath = "golang.org/x/text",
-        sum = "h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=",
-        version = "v0.8.0",
+        sum = "h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=",
+        version = "v0.9.0",
     )
     go_repository(
         name = "org_golang_x_tools",
diff --git a/docs/20-global-settings.md b/docs/20-global-settings.md
index 717d1bc3..b006222f 100644
--- a/docs/20-global-settings.md
+++ b/docs/20-global-settings.md
@@ -29,6 +29,18 @@ Disable the tag if you feel like it's not worth the hassle.
 
 And when you *do* enable [github.com/goccy/go-json](https://github.com/goccy/go-json) and you encounter some mysterious error, I also trust that you know to file an issue to [github.com/goccy/go-json](https://github.com/goccy/go-json) and **NOT** to this library.
 
+## Enabling experimental base64 encoder/decoder
+
+This feature is currently considered experimental.
+
+Currently you can enable [github.com/segmentio/asm/base64](https://github.com/segmentio/asm/tree/main/base64) by specifying the `jwx_asmbase64` build tag
+
+```shell
+% go build -tags jwx_goccy ...
+```
+
+In our limited testing, this does not seem to improve performance significantly: presumably the other bottlenecks are more dominant. If you care enough to use this option, you probably wantt o enable `jwx_goccy` build tag as well.
+
 ## Using json.Number
 
 If you want to parse numbers in the incoming JSON objects as json.Number
diff --git a/examples/go.mod b/examples/go.mod
index b0c4e492..1455d121 100644
--- a/examples/go.mod
+++ b/examples/go.mod
@@ -3,7 +3,7 @@ module github.com/lestrrat-go/jwx/v2/examples
 go 1.16
 
 require (
-	github.com/cloudflare/circl v1.1.0
+	github.com/cloudflare/circl v1.3.3
 	github.com/lestrrat-go/jwx/v2 v2.0.8
 	github.com/stretchr/testify v1.8.2 // indirect
 	golang.org/x/crypto v0.7.0 // indirect
diff --git a/examples/go.sum b/examples/go.sum
index 4e0f3e68..8265f593 100644
--- a/examples/go.sum
+++ b/examples/go.sum
@@ -1,6 +1,6 @@
-github.com/bwesterb/go-ristretto v1.2.0/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
-github.com/cloudflare/circl v1.1.0 h1:bZgT/A+cikZnKIwn7xL2OBj012Bmvho/o6RpRvv3GKY=
-github.com/cloudflare/circl v1.1.0/go.mod h1:prBCrKB9DV4poKZY1l9zBXg2QJY7mvgRvtMxxK7fi4I=
+github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
+github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs=
+github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -36,6 +36,7 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
 golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
 golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@@ -44,6 +45,7 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -53,20 +55,23 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
 golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
 golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
diff --git a/go.mod b/go.mod
index 6e8b82c0..6ebd4603 100644
--- a/go.mod
+++ b/go.mod
@@ -3,12 +3,13 @@ module github.com/lestrrat-go/jwx/v2
 go 1.16
 
 require (
-	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0
+	github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0
 	github.com/goccy/go-json v0.10.2
 	github.com/lestrrat-go/blackmagic v1.0.1
 	github.com/lestrrat-go/httprc v1.0.4
 	github.com/lestrrat-go/iter v1.0.2
 	github.com/lestrrat-go/option v1.0.1
-	github.com/stretchr/testify v1.8.2
-	golang.org/x/crypto v0.7.0
+	github.com/segmentio/asm v1.2.0
+	github.com/stretchr/testify v1.8.4
+	golang.org/x/crypto v0.9.0
 )
diff --git a/go.sum b/go.sum
index 55d4f1da..d61bcef1 100644
--- a/go.sum
+++ b/go.sum
@@ -1,9 +1,9 @@
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4=
-github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc=
+github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
+github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
 github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
 github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
 github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
@@ -19,45 +19,49 @@ github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNB
 github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys=
+github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
-github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
-github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
-golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A=
-golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
+golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
+golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
-golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
diff --git a/internal/base64/asmbase64.go b/internal/base64/asmbase64.go
new file mode 100644
index 00000000..b151b229
--- /dev/null
+++ b/internal/base64/asmbase64.go
@@ -0,0 +1,39 @@
+//go:build jwx_asmbase64
+
+package base64
+
+import (
+	"fmt"
+
+	asmbase64 "github.com/segmentio/asm/base64"
+)
+
+func init() {
+	SetEncoder(asmbase64.RawURLEncoding)
+	SetDecoder(asmDecoder{})
+}
+
+type asmDecoder struct{}
+
+func (d asmDecoder) Decode(src []byte) ([]byte, error) {
+	var enc *asmbase64.Encoding
+	switch Guess(src) {
+	case Std:
+		enc = asmbase64.StdEncoding
+	case RawStd:
+		enc = asmbase64.RawStdEncoding
+	case URL:
+		enc = asmbase64.URLEncoding
+	case RawURL:
+		enc = asmbase64.RawURLEncoding
+	default:
+		return nil, fmt.Errorf(`invalid encoding`)
+	}
+
+	dst := make([]byte, enc.DecodedLen(len(src)))
+	n, err := enc.Decode(dst, src)
+	if err != nil {
+		return nil, fmt.Errorf(`failed to decode source: %w`, err)
+	}
+	return dst[:n], nil
+}
diff --git a/internal/base64/base64.go b/internal/base64/base64.go
index bc494bcf..b227bc91 100644
--- a/internal/base64/base64.go
+++ b/internal/base64/base64.go
@@ -5,21 +5,57 @@ import (
 	"encoding/base64"
 	"encoding/binary"
 	"fmt"
+	"sync"
 )
 
-func Encode(src []byte) []byte {
-	enc := base64.RawURLEncoding
-	dst := make([]byte, enc.EncodedLen(len(src)))
-	enc.Encode(dst, src)
-	return dst
+type Decoder interface {
+	Decode([]byte) ([]byte, error)
+}
+
+type Encoder interface {
+	Encode([]byte, []byte)
+	EncodedLen(int) int
+	EncodeToString([]byte) string
+}
+
+var muEncoder sync.RWMutex
+var encoder Encoder = base64.RawURLEncoding
+var muDecoder sync.RWMutex
+var decoder Decoder = defaultDecoder{}
+
+func SetEncoder(enc Encoder) {
+	muEncoder.Lock()
+	defer muEncoder.Unlock()
+	encoder = enc
+}
+
+func getEncoder() Encoder {
+	muEncoder.RLock()
+	defer muEncoder.RUnlock()
+	return encoder
 }
 
-func EncodeToStringStd(src []byte) string {
-	return base64.StdEncoding.EncodeToString(src)
+func SetDecoder(dec Decoder) {
+	muDecoder.Lock()
+	defer muDecoder.Unlock()
+	decoder = dec
+}
+
+func getDecoder() Decoder {
+	muDecoder.RLock()
+	defer muDecoder.RUnlock()
+	return decoder
+}
+
+func Encode(src []byte) []byte {
+	encoder := getEncoder()
+	dst := make([]byte, encoder.EncodedLen(len(src)))
+	encoder.Encode(dst, src)
+	return dst
 }
 
 func EncodeToString(src []byte) string {
-	return base64.RawURLEncoding.EncodeToString(src)
+	return getEncoder().EncodeToString(src)
 }
 
 func EncodeUint64ToString(v uint64) string {
@@ -36,20 +72,49 @@ func EncodeUint64ToString(v uint64) string {
 	return EncodeToString(data[i:])
 }
 
-func Decode(src []byte) ([]byte, error) {
-	var enc *base64.Encoding
+const (
+	InvalidEncoding = iota
+	Std
+	URL
+	RawStd
+	RawURL
+)
 
+func Guess(src []byte) int {
 	var isRaw = !bytes.HasSuffix(src, []byte{'='})
 	var isURL = !bytes.ContainsAny(src, "+/")
 	switch {
 	case isRaw && isURL:
-		enc = base64.RawURLEncoding
+		return RawURL
 	case isURL:
-		enc = base64.URLEncoding
+		return URL
 	case isRaw:
-		enc = base64.RawStdEncoding
+		return RawStd
 	default:
+		return Std
+	}
+}
+
+// defaultDecoder is a Decoder that detects the encoding of the source and
+// decodes it accordingly. This shouldn't really be required per the spec, but
+// it exist because we have seen in the wild JWTs that are encoded using
+// various versions of the base64 encoding.
+type defaultDecoder struct{}
+
+func (defaultDecoder) Decode(src []byte) ([]byte, error) {
+	var enc *base64.Encoding
+
+	switch Guess(src) {
+	case RawURL:
+		enc = base64.RawURLEncoding
+	case URL:
+		enc = base64.URLEncoding
+	case RawStd:
+		enc = base64.RawStdEncoding
+	case Std:
 		enc = base64.StdEncoding
+	default:
+		return nil, fmt.Errorf(`invalid encoding`)
 	}
 
 	dst := make([]byte, enc.DecodedLen(len(src)))
@@ -60,6 +125,10 @@ func Decode(src []byte) ([]byte, error) {
 	return dst[:n], nil
 }
 
+func Decode(src []byte) ([]byte, error) {
+	return getDecoder().Decode(src)
+}
+
 func DecodeString(src string) ([]byte, error) {
-	return Decode([]byte(src))
+	return getDecoder().Decode([]byte(src))
 }
diff --git a/jwa/compression_gen.go b/jwa/compression_gen.go
index ad27ea3f..9fb65220 100644
--- a/jwa/compression_gen.go
+++ b/jwa/compression_gen.go
@@ -17,25 +17,55 @@ const (
 	NoCompress CompressionAlgorithm = ""    // No compression
 )
 
-var allCompressionAlgorithms = map[CompressionAlgorithm]struct{}{
-	Deflate:    {},
-	NoCompress: {},
+var muCompressionAlgorithms sync.RWMutex
+var allCompressionAlgorithms map[CompressionAlgorithm]struct{}
+var listCompressionAlgorithm []CompressionAlgorithm
+
+func init() {
+	muCompressionAlgorithms.Lock()
+	defer muCompressionAlgorithms.Unlock()
+	allCompressionAlgorithms = make(map[CompressionAlgorithm]struct{})
+	allCompressionAlgorithms[Deflate] = struct{}{}
+	allCompressionAlgorithms[NoCompress] = struct{}{}
+	rebuildCompressionAlgorithm()
 }
 
-var listCompressionAlgorithmOnce sync.Once
-var listCompressionAlgorithm []CompressionAlgorithm
+// RegisterCompressionAlgorithm registers a new CompressionAlgorithm so that the jwx can properly handle the new value.
+// Duplicates will silently be ignored
+func RegisterCompressionAlgorithm(v CompressionAlgorithm) {
+	muCompressionAlgorithms.Lock()
+	defer muCompressionAlgorithms.Unlock()
+	if _, ok := allCompressionAlgorithms[v]; !ok {
+		allCompressionAlgorithms[v] = struct{}{}
+		rebuildCompressionAlgorithm()
+	}
+}
+
+// UnregisterCompressionAlgorithm unregisters a CompressionAlgorithm from its known database.
+// Non-existentn entries will silently be ignored
+func UnregisterCompressionAlgorithm(v CompressionAlgorithm) {
+	muCompressionAlgorithms.Lock()
+	defer muCompressionAlgorithms.Unlock()
+	if _, ok := allCompressionAlgorithms[v]; ok {
+		delete(allCompressionAlgorithms, v)
+		rebuildCompressionAlgorithm()
+	}
+}
+
+func rebuildCompressionAlgorithm() {
+	listCompressionAlgorithm = make([]CompressionAlgorithm, 0, len(allCompressionAlgorithms))
+	for v := range allCompressionAlgorithms {
+		listCompressionAlgorithm = append(listCompressionAlgorithm, v)
+	}
+	sort.Slice(listCompressionAlgorithm, func(i, j int) bool {
+		return string(listCompressionAlgorithm[i]) < string(listCompressionAlgorithm[j])
+	})
+}
 
 // CompressionAlgorithms returns a list of all available values for CompressionAlgorithm
 func CompressionAlgorithms() []CompressionAlgorithm {
-	listCompressionAlgorithmOnce.Do(func() {
-		listCompressionAlgorithm = make([]CompressionAlgorithm, 0, len(allCompressionAlgorithms))
-		for v := range allCompressionAlgorithms {
-			listCompressionAlgorithm = append(listCompressionAlgorithm, v)
-		}
-		sort.Slice(listCompressionAlgorithm, func(i, j int) bool {
-			return string(listCompressionAlgorithm[i]) < string(listCompressionAlgorithm[j])
-		})
-	})
+	muCompressionAlgorithms.RLock()
+	defer muCompressionAlgorithms.RUnlock()
 	return listCompressionAlgorithm
 }
 
diff --git a/jwa/content_encryption_gen.go b/jwa/content_encryption_gen.go
index bc82d505..115fa18e 100644
--- a/jwa/content_encryption_gen.go
+++ b/jwa/content_encryption_gen.go
@@ -21,29 +21,59 @@ const (
 	A256GCM       ContentEncryptionAlgorithm = "A256GCM"       // AES-GCM (256)
 )
 
-var allContentEncryptionAlgorithms = map[ContentEncryptionAlgorithm]struct{}{
-	A128CBC_HS256: {},
-	A128GCM:       {},
-	A192CBC_HS384: {},
-	A192GCM:       {},
-	A256CBC_HS512: {},
-	A256GCM:       {},
+var muContentEncryptionAlgorithms sync.RWMutex
+var allContentEncryptionAlgorithms map[ContentEncryptionAlgorithm]struct{}
+var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm
+
+func init() {
+	muContentEncryptionAlgorithms.Lock()
+	defer muContentEncryptionAlgorithms.Unlock()
+	allContentEncryptionAlgorithms = make(map[ContentEncryptionAlgorithm]struct{})
+	allContentEncryptionAlgorithms[A128CBC_HS256] = struct{}{}
+	allContentEncryptionAlgorithms[A128GCM] = struct{}{}
+	allContentEncryptionAlgorithms[A192CBC_HS384] = struct{}{}
+	allContentEncryptionAlgorithms[A192GCM] = struct{}{}
+	allContentEncryptionAlgorithms[A256CBC_HS512] = struct{}{}
+	allContentEncryptionAlgorithms[A256GCM] = struct{}{}
+	rebuildContentEncryptionAlgorithm()
 }
 
-var listContentEncryptionAlgorithmOnce sync.Once
-var listContentEncryptionAlgorithm []ContentEncryptionAlgorithm
+// RegisterContentEncryptionAlgorithm registers a new ContentEncryptionAlgorithm so that the jwx can properly handle the new value.
+// Duplicates will silently be ignored
+func RegisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) {
+	muContentEncryptionAlgorithms.Lock()
+	defer muContentEncryptionAlgorithms.Unlock()
+	if _, ok := allContentEncryptionAlgorithms[v]; !ok {
+		allContentEncryptionAlgorithms[v] = struct{}{}
+		rebuildContentEncryptionAlgorithm()
+	}
+}
+
+// UnregisterContentEncryptionAlgorithm unregisters a ContentEncryptionAlgorithm from its known database.
+// Non-existentn entries will silently be ignored
+func UnregisterContentEncryptionAlgorithm(v ContentEncryptionAlgorithm) {
+	muContentEncryptionAlgorithms.Lock()
+	defer muContentEncryptionAlgorithms.Unlock()
+	if _, ok := allContentEncryptionAlgorithms[v]; ok {
+		delete(allContentEncryptionAlgorithms, v)
+		rebuildContentEncryptionAlgorithm()
+	}
+}
+
+func rebuildContentEncryptionAlgorithm() {
+	listContentEncryptionAlgorithm = make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithms))
+	for v := range allContentEncryptionAlgorithms {
+		listContentEncryptionAlgorithm = append(listContentEncryptionAlgorithm, v)
+	}
+	sort.Slice(listContentEncryptionAlgorithm, func(i, j int) bool {
+		return string(listContentEncryptionAlgorithm[i]) < string(listContentEncryptionAlgorithm[j])
+	})
+}
 
 // ContentEncryptionAlgorithms returns a list of all available values for ContentEncryptionAlgorithm
 func ContentEncryptionAlgorithms() []ContentEncryptionAlgorithm {
-	listContentEncryptionAlgorithmOnce.Do(func() {
-		listContentEncryptionAlgorithm = make([]ContentEncryptionAlgorithm, 0, len(allContentEncryptionAlgorithms))
-		for v := range allContentEncryptionAlgorithms {
-			listContentEncryptionAlgorithm = append(listContentEncryptionAlgorithm, v)
-		}
-		sort.Slice(listContentEncryptionAlgorithm, func(i, j int) bool {
-			return string(listContentEncryptionAlgorithm[i]) < string(listContentEncryptionAlgorithm[j])
-		})
-	})
+	muContentEncryptionAlgorithms.RLock()
+	defer muContentEncryptionAlgorithms.RUnlock()
 	return listContentEncryptionAlgorithm
 }
 
diff --git a/jwa/elliptic_gen.go b/jwa/elliptic_gen.go
index 6e813989..fbfe466a 100644
--- a/jwa/elliptic_gen.go
+++ b/jwa/elliptic_gen.go
@@ -23,30 +23,60 @@ const (
 	X448                 EllipticCurveAlgorithm = "X448"
 )
 
-var allEllipticCurveAlgorithms = map[EllipticCurveAlgorithm]struct{}{
-	Ed25519: {},
-	Ed448:   {},
-	P256:    {},
-	P384:    {},
-	P521:    {},
-	X25519:  {},
-	X448:    {},
+var muEllipticCurveAlgorithms sync.RWMutex
+var allEllipticCurveAlgorithms map[EllipticCurveAlgorithm]struct{}
+var listEllipticCurveAlgorithm []EllipticCurveAlgorithm
+
+func init() {
+	muEllipticCurveAlgorithms.Lock()
+	defer muEllipticCurveAlgorithms.Unlock()
+	allEllipticCurveAlgorithms = make(map[EllipticCurveAlgorithm]struct{})
+	allEllipticCurveAlgorithms[Ed25519] = struct{}{}
+	allEllipticCurveAlgorithms[Ed448] = struct{}{}
+	allEllipticCurveAlgorithms[P256] = struct{}{}
+	allEllipticCurveAlgorithms[P384] = struct{}{}
+	allEllipticCurveAlgorithms[P521] = struct{}{}
+	allEllipticCurveAlgorithms[X25519] = struct{}{}
+	allEllipticCurveAlgorithms[X448] = struct{}{}
+	rebuildEllipticCurveAlgorithm()
 }
 
-var listEllipticCurveAlgorithmOnce sync.Once
-var listEllipticCurveAlgorithm []EllipticCurveAlgorithm
+// RegisterEllipticCurveAlgorithm registers a new EllipticCurveAlgorithm so that the jwx can properly handle the new value.
+// Duplicates will silently be ignored
+func RegisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) {
+	muEllipticCurveAlgorithms.Lock()
+	defer muEllipticCurveAlgorithms.Unlock()
+	if _, ok := allEllipticCurveAlgorithms[v]; !ok {
+		allEllipticCurveAlgorithms[v] = struct{}{}
+		rebuildEllipticCurveAlgorithm()
+	}
+}
+
+// UnregisterEllipticCurveAlgorithm unregisters a EllipticCurveAlgorithm from its known database.
+// Non-existentn entries will silently be ignored
+func UnregisterEllipticCurveAlgorithm(v EllipticCurveAlgorithm) {
+	muEllipticCurveAlgorithms.Lock()
+	defer muEllipticCurveAlgorithms.Unlock()
+	if _, ok := allEllipticCurveAlgorithms[v]; ok {
+		delete(allEllipticCurveAlgorithms, v)
+		rebuildEllipticCurveAlgorithm()
+	}
+}
+
+func rebuildEllipticCurveAlgorithm() {
+	listEllipticCurveAlgorithm = make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithms))
+	for v := range allEllipticCurveAlgorithms {
+		listEllipticCurveAlgorithm = append(listEllipticCurveAlgorithm, v)
+	}
+	sort.Slice(listEllipticCurveAlgorithm, func(i, j int) bool {
+		return string(listEllipticCurveAlgorithm[i]) < string(listEllipticCurveAlgorithm[j])
+	})
+}
 
 // EllipticCurveAlgorithms returns a list of all available values for EllipticCurveAlgorithm
 func EllipticCurveAlgorithms() []EllipticCurveAlgorithm {
-	listEllipticCurveAlgorithmOnce.Do(func() {
-		listEllipticCurveAlgorithm = make([]EllipticCurveAlgorithm, 0, len(allEllipticCurveAlgorithms))
-		for v := range allEllipticCurveAlgorithms {
-			listEllipticCurveAlgorithm = append(listEllipticCurveAlgorithm, v)
-		}
-		sort.Slice(listEllipticCurveAlgorithm, func(i, j int) bool {
-			return string(listEllipticCurveAlgorithm[i]) < string(listEllipticCurveAlgorithm[j])
-		})
-	})
+	muEllipticCurveAlgorithms.RLock()
+	defer muEllipticCurveAlgorithms.RUnlock()
 	return listEllipticCurveAlgorithm
 }
 
diff --git a/jwa/key_encryption_gen.go b/jwa/key_encryption_gen.go
index f85574ff..49ed1f67 100644
--- a/jwa/key_encryption_gen.go
+++ b/jwa/key_encryption_gen.go
@@ -32,40 +32,70 @@ const (
 	RSA_OAEP_256       KeyEncryptionAlgorithm = "RSA-OAEP-256"       // RSA-OAEP-SHA256
 )
 
-var allKeyEncryptionAlgorithms = map[KeyEncryptionAlgorithm]struct{}{
-	A128GCMKW:          {},
-	A128KW:             {},
-	A192GCMKW:          {},
-	A192KW:             {},
-	A256GCMKW:          {},
-	A256KW:             {},
-	DIRECT:             {},
-	ECDH_ES:            {},
-	ECDH_ES_A128KW:     {},
-	ECDH_ES_A192KW:     {},
-	ECDH_ES_A256KW:     {},
-	PBES2_HS256_A128KW: {},
-	PBES2_HS384_A192KW: {},
-	PBES2_HS512_A256KW: {},
-	RSA1_5:             {},
-	RSA_OAEP:           {},
-	RSA_OAEP_256:       {},
+var muKeyEncryptionAlgorithms sync.RWMutex
+var allKeyEncryptionAlgorithms map[KeyEncryptionAlgorithm]struct{}
+var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm
+
+func init() {
+	muKeyEncryptionAlgorithms.Lock()
+	defer muKeyEncryptionAlgorithms.Unlock()
+	allKeyEncryptionAlgorithms = make(map[KeyEncryptionAlgorithm]struct{})
+	allKeyEncryptionAlgorithms[A128GCMKW] = struct{}{}
+	allKeyEncryptionAlgorithms[A128KW] = struct{}{}
+	allKeyEncryptionAlgorithms[A192GCMKW] = struct{}{}
+	allKeyEncryptionAlgorithms[A192KW] = struct{}{}
+	allKeyEncryptionAlgorithms[A256GCMKW] = struct{}{}
+	allKeyEncryptionAlgorithms[A256KW] = struct{}{}
+	allKeyEncryptionAlgorithms[DIRECT] = struct{}{}
+	allKeyEncryptionAlgorithms[ECDH_ES] = struct{}{}
+	allKeyEncryptionAlgorithms[ECDH_ES_A128KW] = struct{}{}
+	allKeyEncryptionAlgorithms[ECDH_ES_A192KW] = struct{}{}
+	allKeyEncryptionAlgorithms[ECDH_ES_A256KW] = struct{}{}
+	allKeyEncryptionAlgorithms[PBES2_HS256_A128KW] = struct{}{}
+	allKeyEncryptionAlgorithms[PBES2_HS384_A192KW] = struct{}{}
+	allKeyEncryptionAlgorithms[PBES2_HS512_A256KW] = struct{}{}
+	allKeyEncryptionAlgorithms[RSA1_5] = struct{}{}
+	allKeyEncryptionAlgorithms[RSA_OAEP] = struct{}{}
+	allKeyEncryptionAlgorithms[RSA_OAEP_256] = struct{}{}
+	rebuildKeyEncryptionAlgorithm()
 }
 
-var listKeyEncryptionAlgorithmOnce sync.Once
-var listKeyEncryptionAlgorithm []KeyEncryptionAlgorithm
+// RegisterKeyEncryptionAlgorithm registers a new KeyEncryptionAlgorithm so that the jwx can properly handle the new value.
+// Duplicates will silently be ignored
+func RegisterKeyEncryptionAlgorithm(v KeyEncryptionAlgorithm) {
+	muKeyEncryptionAlgorithms.Lock()
+	defer muKeyEncryptionAlgorithms.Unlock()
+	if _, ok := allKeyEncryptionAlgorithms[v]; !ok {
+		allKeyEncryptionAlgorithms[v] = struct{}{}
+		rebuildKeyEncryptionAlgorithm()
+	}
+}
+
+// UnregisterKeyEncryptionAlgorithm unregisters a KeyEncryptionAlgorithm from its known database.
+// Non-existentn entries will silently be ignored
+func UnregisterKeyEncryptionAlgorithm(v KeyEncryptionAlgorithm) {
+	muKeyEncryptionAlgorithms.Lock()
+	defer muKeyEncryptionAlgorithms.Unlock()
+	if _, ok := allKeyEncryptionAlgorithms[v]; ok {
+		delete(allKeyEncryptionAlgorithms, v)
+		rebuildKeyEncryptionAlgorithm()
+	}
+}
+
+func rebuildKeyEncryptionAlgorithm() {
+	listKeyEncryptionAlgorithm = make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithms))
+	for v := range allKeyEncryptionAlgorithms {
+		listKeyEncryptionAlgorithm = append(listKeyEncryptionAlgorithm, v)
+	}
+	sort.Slice(listKeyEncryptionAlgorithm, func(i, j int) bool {
+		return string(listKeyEncryptionAlgorithm[i]) < string(listKeyEncryptionAlgorithm[j])
+	})
+}
 
 // KeyEncryptionAlgorithms returns a list of all available values for KeyEncryptionAlgorithm
 func KeyEncryptionAlgorithms() []KeyEncryptionAlgorithm {
-	listKeyEncryptionAlgorithmOnce.Do(func() {
-		listKeyEncryptionAlgorithm = make([]KeyEncryptionAlgorithm, 0, len(allKeyEncryptionAlgorithms))
-		for v := range allKeyEncryptionAlgorithms {
-			listKeyEncryptionAlgorithm = append(listKeyEncryptionAlgorithm, v)
-		}
-		sort.Slice(listKeyEncryptionAlgorithm, func(i, j int) bool {
-			return string(listKeyEncryptionAlgorithm[i]) < string(listKeyEncryptionAlgorithm[j])
-		})
-	})
+	muKeyEncryptionAlgorithms.RLock()
+	defer muKeyEncryptionAlgorithms.RUnlock()
 	return listKeyEncryptionAlgorithm
 }
 
diff --git a/jwa/key_type_gen.go b/jwa/key_type_gen.go
index 2b602c67..e1f9e389 100644
--- a/jwa/key_type_gen.go
+++ b/jwa/key_type_gen.go
@@ -20,27 +20,57 @@ const (
 	RSA            KeyType = "RSA" // RSA
 )
 
-var allKeyTypes = map[KeyType]struct{}{
-	EC:       {},
-	OKP:      {},
-	OctetSeq: {},
-	RSA:      {},
+var muKeyTypes sync.RWMutex
+var allKeyTypes map[KeyType]struct{}
+var listKeyType []KeyType
+
+func init() {
+	muKeyTypes.Lock()
+	defer muKeyTypes.Unlock()
+	allKeyTypes = make(map[KeyType]struct{})
+	allKeyTypes[EC] = struct{}{}
+	allKeyTypes[OKP] = struct{}{}
+	allKeyTypes[OctetSeq] = struct{}{}
+	allKeyTypes[RSA] = struct{}{}
+	rebuildKeyType()
 }
 
-var listKeyTypeOnce sync.Once
-var listKeyType []KeyType
+// RegisterKeyType registers a new KeyType so that the jwx can properly handle the new value.
+// Duplicates will silently be ignored
+func RegisterKeyType(v KeyType) {
+	muKeyTypes.Lock()
+	defer muKeyTypes.Unlock()
+	if _, ok := allKeyTypes[v]; !ok {
+		allKeyTypes[v] = struct{}{}
+		rebuildKeyType()
+	}
+}
+
+// UnregisterKeyType unregisters a KeyType from its known database.
+// Non-existentn entries will silently be ignored
+func UnregisterKeyType(v KeyType) {
+	muKeyTypes.Lock()
+	defer muKeyTypes.Unlock()
+	if _, ok := allKeyTypes[v]; ok {
+		delete(allKeyTypes, v)
+		rebuildKeyType()
+	}
+}
+
+func rebuildKeyType() {
+	listKeyType = make([]KeyType, 0, len(allKeyTypes))
+	for v := range allKeyTypes {
+		listKeyType = append(listKeyType, v)
+	}
+	sort.Slice(listKeyType, func(i, j int) bool {
+		return string(listKeyType[i]) < string(listKeyType[j])
+	})
+}
 
 // KeyTypes returns a list of all available values for KeyType
 func KeyTypes() []KeyType {
-	listKeyTypeOnce.Do(func() {
-		listKeyType = make([]KeyType, 0, len(allKeyTypes))
-		for v := range allKeyTypes {
-			listKeyType = append(listKeyType, v)
-		}
-		sort.Slice(listKeyType, func(i, j int) bool {
-			return string(listKeyType[i]) < string(listKeyType[j])
-		})
-	})
+	muKeyTypes.RLock()
+	defer muKeyTypes.RUnlock()
 	return listKeyType
 }
 
diff --git a/jwa/signature_gen.go b/jwa/signature_gen.go
index bc2cbb91..eaa2f866 100644
--- a/jwa/signature_gen.go
+++ b/jwa/signature_gen.go
@@ -30,38 +30,68 @@ const (
 	RS512       SignatureAlgorithm = "RS512" // RSASSA-PKCS-v1.5 using SHA-512
 )
 
-var allSignatureAlgorithms = map[SignatureAlgorithm]struct{}{
-	ES256:       {},
-	ES256K:      {},
-	ES384:       {},
-	ES512:       {},
-	EdDSA:       {},
-	HS256:       {},
-	HS384:       {},
-	HS512:       {},
-	NoSignature: {},
-	PS256:       {},
-	PS384:       {},
-	PS512:       {},
-	RS256:       {},
-	RS384:       {},
-	RS512:       {},
+var muSignatureAlgorithms sync.RWMutex
+var allSignatureAlgorithms map[SignatureAlgorithm]struct{}
+var listSignatureAlgorithm []SignatureAlgorithm
+
+func init() {
+	muSignatureAlgorithms.Lock()
+	defer muSignatureAlgorithms.Unlock()
+	allSignatureAlgorithms = make(map[SignatureAlgorithm]struct{})
+	allSignatureAlgorithms[ES256] = struct{}{}
+	allSignatureAlgorithms[ES256K] = struct{}{}
+	allSignatureAlgorithms[ES384] = struct{}{}
+	allSignatureAlgorithms[ES512] = struct{}{}
+	allSignatureAlgorithms[EdDSA] = struct{}{}
+	allSignatureAlgorithms[HS256] = struct{}{}
+	allSignatureAlgorithms[HS384] = struct{}{}
+	allSignatureAlgorithms[HS512] = struct{}{}
+	allSignatureAlgorithms[NoSignature] = struct{}{}
+	allSignatureAlgorithms[PS256] = struct{}{}
+	allSignatureAlgorithms[PS384] = struct{}{}
+	allSignatureAlgorithms[PS512] = struct{}{}
+	allSignatureAlgorithms[RS256] = struct{}{}
+	allSignatureAlgorithms[RS384] = struct{}{}
+	allSignatureAlgorithms[RS512] = struct{}{}
+	rebuildSignatureAlgorithm()
 }
 
-var listSignatureAlgorithmOnce sync.Once
-var listSignatureAlgorithm []SignatureAlgorithm
+// RegisterSignatureAlgorithm registers a new SignatureAlgorithm so that the jwx can properly handle the new value.
+// Duplicates will silently be ignored
+func RegisterSignatureAlgorithm(v SignatureAlgorithm) {
+	muSignatureAlgorithms.Lock()
+	defer muSignatureAlgorithms.Unlock()
+	if _, ok := allSignatureAlgorithms[v]; !ok {
+		allSignatureAlgorithms[v] = struct{}{}
+		rebuildSignatureAlgorithm()
+	}
+}
+
+// UnregisterSignatureAlgorithm unregisters a SignatureAlgorithm from its known database.
+// Non-existentn entries will silently be ignored
+func UnregisterSignatureAlgorithm(v SignatureAlgorithm) {
+	muSignatureAlgorithms.Lock()
+	defer muSignatureAlgorithms.Unlock()
+	if _, ok := allSignatureAlgorithms[v]; ok {
+		delete(allSignatureAlgorithms, v)
+		rebuildSignatureAlgorithm()
+	}
+}
+
+func rebuildSignatureAlgorithm() {
+	listSignatureAlgorithm = make([]SignatureAlgorithm, 0, len(allSignatureAlgorithms))
+	for v := range allSignatureAlgorithms {
+		listSignatureAlgorithm = append(listSignatureAlgorithm, v)
+	}
+	sort.Slice(listSignatureAlgorithm, func(i, j int) bool {
+		return string(listSignatureAlgorithm[i]) < string(listSignatureAlgorithm[j])
+	})
+}
 
 // SignatureAlgorithms returns a list of all available values for SignatureAlgorithm
 func SignatureAlgorithms() []SignatureAlgorithm {
-	listSignatureAlgorithmOnce.Do(func() {
-		listSignatureAlgorithm = make([]SignatureAlgorithm, 0, len(allSignatureAlgorithms))
-		for v := range allSignatureAlgorithms {
-			listSignatureAlgorithm = append(listSignatureAlgorithm, v)
-		}
-		sort.Slice(listSignatureAlgorithm, func(i, j int) bool {
-			return string(listSignatureAlgorithm[i]) < string(listSignatureAlgorithm[j])
-		})
-	})
+	muSignatureAlgorithms.RLock()
+	defer muSignatureAlgorithms.RUnlock()
 	return listSignatureAlgorithm
 }
 
diff --git a/jwe/decrypt.go b/jwe/decrypt.go
index 1988f809..387d4a99 100644
--- a/jwe/decrypt.go
+++ b/jwe/decrypt.go
@@ -137,8 +137,8 @@ func (d *decrypter) ContentCipher() (content_crypt.Cipher, error) {
 	return d.cipher, nil
 }
 
-func (d *decrypter) Decrypt(recipientKey, ciphertext []byte) (plaintext []byte, err error) {
-	cek, keyerr := d.DecryptKey(recipientKey)
+func (d *decrypter) Decrypt(recipient Recipient, ciphertext []byte, msg *Message) (plaintext []byte, err error) {
+	cek, keyerr := d.DecryptKey(recipient, msg)
 	if keyerr != nil {
 		err = fmt.Errorf(`failed to decrypt key: %w`, keyerr)
 		return
@@ -226,7 +226,12 @@ func (d *decrypter) decryptSymmetricKey(recipientKey, cek []byte) ([]byte, error
 	}
 }
 
-func (d *decrypter) DecryptKey(recipientKey []byte) (cek []byte, err error) {
+func (d *decrypter) DecryptKey(recipient Recipient, msg *Message) (cek []byte, err error) {
+	recipientKey := recipient.EncryptedKey()
+	if kd, ok := d.privkey.(KeyDecrypter); ok {
+		return kd.DecryptKey(d.keyalg, recipientKey, recipient, msg)
+	}
+
 	if d.keyalg.IsSymmetric() {
 		var ok bool
 		cek, ok = d.privkey.([]byte)
diff --git a/jwe/interface.go b/jwe/interface.go
index d1044ce1..828412f6 100644
--- a/jwe/interface.go
+++ b/jwe/interface.go
@@ -3,9 +3,63 @@ package jwe
 import (
 	"github.com/lestrrat-go/iter/mapiter"
 	"github.com/lestrrat-go/jwx/v2/internal/iter"
+	"github.com/lestrrat-go/jwx/v2/jwa"
 	"github.com/lestrrat-go/jwx/v2/jwe/internal/keygen"
 )
 
+// KeyEncrypter is an interface for object that can encrypt a
+// content encryption key.
+//
+// You can use this in place of a regular key (i.e. in jwe.WithKey())
+// to encrypt the content encryption key in a JWE message without
+// having to expose the secret key in memory, for example, when you
+// want to use hardware security modules (HSMs) to encrypt the key.
+//
+// This API is experimental and may change without notice, even
+// in minor releases.
+type KeyEncrypter interface {
+	// Algorithm returns the algorithm used to encrypt the key.
+	Algorithm() jwa.KeyEncryptionAlgorithm
+
+	// EncryptKey encrypts the given content encryption key.
+	EncryptKey([]byte) ([]byte, error)
+}
+
+// KeyIDer is an interface for things that can return a key ID.
+//
+// As of this writing, this is solely used to identify KeyEncrypter
+// objects that also carry a key ID on its own.
+type KeyIDer interface {
+	KeyID() string
+}
+
+// KeyDecrypter is an interface for objects that can decrypt a content
+// encryption key.
+//
+// You can use this in place of a regular key (i.e. in jwe.WithKey())
+// to decrypt the encrypted key in a JWE message without having to
+// expose the secret key in memory, for example, when you want to use
+// hardware security modules (HSMs) to decrypt the key.
+//
+// This API is experimental and may change without notice, even
+// in minor releases.
+type KeyDecrypter interface {
+	// Decrypt decrypts the encrypted key of a JWE message.
+	//
+	// Make sure you understand how JWE messages are structured.
+	//
+	// For example, while in most circumstances a JWE message will only have one recipient,
+	// a JWE message may contain multiple recipients, each with their own
+	// encrypted key. This method will be called for each recipient, instead of
+	// just once for a message.
+	//
+	// Also, header values could be found in either protected/unprotected headers
+	// of a JWE message, as well as in protected/unprotected headers for each recipient.
+	// When checking a header value, you can decide to use either one, or both, but you
+	// must be aware that there are multiple places to look for.
+	DecryptKey(alg jwa.KeyEncryptionAlgorithm, encryptedKey []byte, recipient Recipient, message *Message) ([]byte, error)
+}
+
 // Recipient holds the encrypted key and hints to decrypt the key
 type Recipient interface {
 	Headers() Headers
diff --git a/jwe/internal/keyenc/interface.go b/jwe/internal/keyenc/interface.go
index 70fe7301..4457538e 100644
--- a/jwe/internal/keyenc/interface.go
+++ b/jwe/internal/keyenc/interface.go
@@ -11,13 +11,7 @@ import (
 // Encrypter is an interface for things that can encrypt keys
 type Encrypter interface {
 	Algorithm() jwa.KeyEncryptionAlgorithm
-	Encrypt([]byte) (keygen.ByteSource, error)
-	// KeyID returns the key id for this Encrypter. This exists so that
-	// you can pass in a Encrypter to MultiEncrypt, you can rest assured
-	// that the generated key will have the proper key ID.
-	KeyID() string
-
-	SetKeyID(string)
+	EncryptKey([]byte) (keygen.ByteSource, error)
 }
 
 // Decrypter is an interface for things that can decrypt keys
diff --git a/jwe/internal/keyenc/keyenc.go b/jwe/internal/keyenc/keyenc.go
index 3e19e62b..ce5e657c 100644
--- a/jwe/internal/keyenc/keyenc.go
+++ b/jwe/internal/keyenc/keyenc.go
@@ -46,7 +46,7 @@ func (kw *Noop) KeyID() string {
 	return kw.keyID
 }
 
-func (kw *Noop) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (kw *Noop) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	return keygen.ByteKey(kw.sharedkey), nil
 }
 
@@ -88,7 +88,7 @@ func (kw *AES) Decrypt(enckey []byte) ([]byte, error) {
 }
 
 // KeyEncrypt encrypts the given content encryption key
-func (kw *AES) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (kw *AES) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	block, err := aes.NewCipher(kw.sharedkey)
 	if err != nil {
 		return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
@@ -119,7 +119,7 @@ func (kw AESGCMEncrypt) KeyID() string {
 	return kw.keyID
 }
 
-func (kw AESGCMEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (kw AESGCMEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	block, err := aes.NewCipher(kw.sharedkey)
 	if err != nil {
 		return nil, fmt.Errorf(`failed to create cipher from shared key: %w`, err)
@@ -181,7 +181,7 @@ func (kw PBES2Encrypt) KeyID() string {
 	return kw.keyID
 }
 
-func (kw PBES2Encrypt) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (kw PBES2Encrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	count := 10000
 	salt := make([]byte, kw.keylen)
 	_, err := io.ReadFull(rand.Reader, salt)
@@ -245,7 +245,7 @@ func (kw ECDHESEncrypt) KeyID() string {
 }
 
 // KeyEncrypt encrypts the content encryption key using ECDH-ES
-func (kw ECDHESEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (kw ECDHESEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	kg, err := kw.generator.Generate()
 	if err != nil {
 		return nil, fmt.Errorf(`failed to create key generator: %w`, err)
@@ -443,7 +443,7 @@ func (e RSAOAEPEncrypt) KeyID() string {
 }
 
 // KeyEncrypt encrypts the content encryption key using RSA PKCS1v15
-func (e RSAPKCSEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (e RSAPKCSEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	if e.alg != jwa.RSA1_5 {
 		return nil, fmt.Errorf("invalid RSA PKCS encrypt algorithm (%s)", e.alg)
 	}
@@ -455,7 +455,7 @@ func (e RSAPKCSEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) {
 }
 
 // KeyEncrypt encrypts the content encryption key using RSA OAEP
-func (e RSAOAEPEncrypt) Encrypt(cek []byte) (keygen.ByteSource, error) {
+func (e RSAOAEPEncrypt) EncryptKey(cek []byte) (keygen.ByteSource, error) {
 	var hash hash.Hash
 	switch e.alg {
 	case jwa.RSA_OAEP:
diff --git a/jwe/internal/keygen/interface.go b/jwe/internal/keygen/interface.go
index 10543c05..ac019ddf 100644
--- a/jwe/internal/keygen/interface.go
+++ b/jwe/internal/keygen/interface.go
@@ -12,9 +12,6 @@ type Generator interface {
 	Generate() (ByteSource, error)
 }
 
-// StaticKeyGenerate uses a static byte buffer to provide keys.
-type Static []byte
-
 // RandomKeyGenerate generates random keys
 type Random struct {
 	keysize int
diff --git a/jwe/internal/keygen/keygen.go b/jwe/internal/keygen/keygen.go
index 0d9c7ece..150cbf71 100644
--- a/jwe/internal/keygen/keygen.go
+++ b/jwe/internal/keygen/keygen.go
@@ -22,18 +22,6 @@ func (k ByteKey) Bytes() []byte {
 	return []byte(k)
 }
 
-// Size returns the size of the key
-func (g Static) Size() int {
-	return len(g)
-}
-
-// Generate returns the key
-func (g Static) Generate() (ByteSource, error) {
-	buf := make([]byte, g.Size())
-	copy(buf, g)
-	return ByteKey(buf), nil
-}
-
 // NewRandom creates a new Generator that returns
 // random bytes
 func NewRandom(n int) Random {
diff --git a/jwe/jwe.go b/jwe/jwe.go
index dfd86132..67b8e97b 100644
--- a/jwe/jwe.go
+++ b/jwe/jwe.go
@@ -37,6 +37,22 @@ var _ = fmtMax
 
 var registry = json.NewRegistry()
 
+type keyEncrypterWrapper struct {
+	encrypter KeyEncrypter
+}
+
+func (w *keyEncrypterWrapper) Algorithm() jwa.KeyEncryptionAlgorithm {
+	return w.encrypter.Algorithm()
+}
+
+func (w *keyEncrypterWrapper) EncryptKey(cek []byte) (keygen.ByteSource, error) {
+	encrypted, err := w.encrypter.EncryptKey(cek)
+	if err != nil {
+		return nil, err
+	}
+	return keygen.ByteKey(encrypted), nil
+}
+
 type recipientBuilder struct {
 	alg     jwa.KeyEncryptionAlgorithm
 	key     interface{}
@@ -44,11 +60,18 @@ type recipientBuilder struct {
 }
 
 func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm, cc *content_crypt.Generic) (Recipient, []byte, error) {
-	// we need the raw key
+	var enc keyenc.Encrypter
+
+	// we need the raw key for later use
 	rawKey := b.key
 
 	var keyID string
-	if jwkKey, ok := b.key.(jwk.Key); ok {
+	if ke, ok := b.key.(KeyEncrypter); ok {
+		enc = &keyEncrypterWrapper{encrypter: ke}
+		if kider, ok := enc.(KeyIDer); ok {
+			keyID = kider.KeyID()
+		}
+	} else if jwkKey, ok := b.key.(jwk.Key); ok {
 		// Meanwhile, grab the kid as well
 		keyID = jwkKey.KeyID()
 
@@ -60,114 +83,110 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm
 		rawKey = raw
 	}
 
-	// First, create a key encryptor
-	var enc keyenc.Encrypter
-	switch b.alg {
-	case jwa.RSA1_5:
-		var pubkey rsa.PublicKey
-		if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil {
-			return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err)
-		}
-
-		v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err)
-		}
-		enc = v
-	case jwa.RSA_OAEP, jwa.RSA_OAEP_256:
-		var pubkey rsa.PublicKey
-		if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil {
-			return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err)
-		}
-
-		v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey)
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err)
-		}
-		enc = v
-	case jwa.A128KW, jwa.A192KW, jwa.A256KW,
-		jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW,
-		jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
-		sharedkey, ok := rawKey.([]byte)
-		if !ok {
-			return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey)
-		}
-
-		var err error
-		switch b.alg {
-		case jwa.A128KW, jwa.A192KW, jwa.A256KW:
-			enc, err = keyenc.NewAES(b.alg, sharedkey)
-		case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
-			enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey)
-		default:
-			enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey)
-		}
-		if err != nil {
-			return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err)
-		}
-		// NOTE: there was formerly a restriction, introduced
-		// in PR #26, which disallowed certain key/content
-		// algorithm combinations. This seemed bogus, and
-		// interop with the jose tool demonstrates it.
-	case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW:
-		var keysize int
+	if enc == nil {
 		switch b.alg {
-		case jwa.ECDH_ES:
-			// https://tools.ietf.org/html/rfc7518#page-15
-			// In Direct Key Agreement mode, the output of the Concat KDF MUST be a
-			// key of the same length as that used by the "enc" algorithm.
-			keysize = cc.KeySize()
-		case jwa.ECDH_ES_A128KW:
-			keysize = 16
-		case jwa.ECDH_ES_A192KW:
-			keysize = 24
-		case jwa.ECDH_ES_A256KW:
-			keysize = 32
-		}
-
-		switch key := rawKey.(type) {
-		case x25519.PublicKey:
-			var apu, apv []byte
-			if hdrs := b.headers; hdrs != nil {
-				apu = hdrs.AgreementPartyUInfo()
-				apv = hdrs.AgreementPartyVInfo()
+		case jwa.RSA1_5:
+			var pubkey rsa.PublicKey
+			if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil {
+				return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err)
 			}
 
-			v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv)
+			v, err := keyenc.NewRSAPKCSEncrypt(b.alg, &pubkey)
 			if err != nil {
-				return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err)
+				return nil, nil, fmt.Errorf(`failed to create RSA PKCS encrypter: %w`, err)
 			}
 			enc = v
-		default:
-			var pubkey ecdsa.PublicKey
-			if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil {
-				return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err)
+		case jwa.RSA_OAEP, jwa.RSA_OAEP_256:
+			var pubkey rsa.PublicKey
+			if err := keyconv.RSAPublicKey(&pubkey, rawKey); err != nil {
+				return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, rawKey, err)
 			}
 
-			var apu, apv []byte
-			if hdrs := b.headers; hdrs != nil {
-				apu = hdrs.AgreementPartyUInfo()
-				apv = hdrs.AgreementPartyVInfo()
+			v, err := keyenc.NewRSAOAEPEncrypt(b.alg, &pubkey)
+			if err != nil {
+				return nil, nil, fmt.Errorf(`failed to create RSA OAEP encrypter: %w`, err)
+			}
+			enc = v
+		case jwa.A128KW, jwa.A192KW, jwa.A256KW,
+			jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW,
+			jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
+			sharedkey, ok := rawKey.([]byte)
+			if !ok {
+				return nil, nil, fmt.Errorf(`invalid key: []byte required (%T)`, rawKey)
 			}
 
-			v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv)
+			var err error
+			switch b.alg {
+			case jwa.A128KW, jwa.A192KW, jwa.A256KW:
+				enc, err = keyenc.NewAES(b.alg, sharedkey)
+			case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
+				enc, err = keyenc.NewPBES2Encrypt(b.alg, sharedkey)
+			default:
+				enc, err = keyenc.NewAESGCMEncrypt(b.alg, sharedkey)
+			}
 			if err != nil {
-				return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err)
+				return nil, nil, fmt.Errorf(`failed to create key wrap encrypter: %w`, err)
+			}
+			// NOTE: there was formerly a restriction, introduced
+			// in PR #26, which disallowed certain key/content
+			// algorithm combinations. This seemed bogus, and
+			// interop with the jose tool demonstrates it.
+		case jwa.ECDH_ES, jwa.ECDH_ES_A128KW, jwa.ECDH_ES_A192KW, jwa.ECDH_ES_A256KW:
+			var keysize int
+			switch b.alg {
+			case jwa.ECDH_ES:
+				// https://tools.ietf.org/html/rfc7518#page-15
+				// In Direct Key Agreement mode, the output of the Concat KDF MUST be a
+				// key of the same length as that used by the "enc" algorithm.
+				keysize = cc.KeySize()
+			case jwa.ECDH_ES_A128KW:
+				keysize = 16
+			case jwa.ECDH_ES_A192KW:
+				keysize = 24
+			case jwa.ECDH_ES_A256KW:
+				keysize = 32
 			}
-			enc = v
-		}
-	case jwa.DIRECT:
-		sharedkey, ok := rawKey.([]byte)
-		if !ok {
-			return nil, nil, fmt.Errorf("invalid key: []byte required")
-		}
-		enc, _ = keyenc.NewNoop(b.alg, sharedkey)
-	default:
-		return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg)
-	}
 
-	if keyID != "" {
-		enc.SetKeyID(keyID)
+			switch key := rawKey.(type) {
+			case x25519.PublicKey:
+				var apu, apv []byte
+				if hdrs := b.headers; hdrs != nil {
+					apu = hdrs.AgreementPartyUInfo()
+					apv = hdrs.AgreementPartyVInfo()
+				}
+
+				v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, rawKey, apu, apv)
+				if err != nil {
+					return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err)
+				}
+				enc = v
+			default:
+				var pubkey ecdsa.PublicKey
+				if err := keyconv.ECDSAPublicKey(&pubkey, rawKey); err != nil {
+					return nil, nil, fmt.Errorf(`failed to generate public key from key (%T): %w`, key, err)
+				}
+
+				var apu, apv []byte
+				if hdrs := b.headers; hdrs != nil {
+					apu = hdrs.AgreementPartyUInfo()
+					apv = hdrs.AgreementPartyVInfo()
+				}
+
+				v, err := keyenc.NewECDHESEncrypt(b.alg, calg, keysize, &pubkey, apu, apv)
+				if err != nil {
+					return nil, nil, fmt.Errorf(`failed to create ECDHS key wrap encrypter: %w`, err)
+				}
+				enc = v
+			}
+		case jwa.DIRECT:
+			sharedkey, ok := rawKey.([]byte)
+			if !ok {
+				return nil, nil, fmt.Errorf("invalid key: []byte required")
+			}
+			enc, _ = keyenc.NewNoop(b.alg, sharedkey)
+		default:
+			return nil, nil, fmt.Errorf(`invalid key encryption algorithm (%s)`, b.alg)
+		}
 	}
 
 	r := NewRecipient()
@@ -178,14 +197,15 @@ func (b *recipientBuilder) Build(cek []byte, calg jwa.ContentEncryptionAlgorithm
 	if err := r.Headers().Set(AlgorithmKey, b.alg); err != nil {
 		return nil, nil, fmt.Errorf(`failed to set header: %w`, err)
 	}
-	if v := enc.KeyID(); v != "" {
-		if err := r.Headers().Set(KeyIDKey, v); err != nil {
+
+	if keyID != "" {
+		if err := r.Headers().Set(KeyIDKey, keyID); err != nil {
 			return nil, nil, fmt.Errorf(`failed to set header: %w`, err)
 		}
 	}
 
 	var rawCEK []byte
-	enckey, err := enc.Encrypt(cek)
+	enckey, err := enc.EncryptKey(cek)
 	if err != nil {
 		return nil, nil, fmt.Errorf(`failed to encrypt key: %w`, err)
 	}
@@ -233,6 +253,7 @@ func Encrypt(payload []byte, options ...EncryptOption) ([]byte, error) {
 	// default compression is "none"
 	compression := jwa.NoCompress
 
+	// default format is compact serialization
 	format := fmtCompact
 
 	// builds each "recipient" with encrypted_key and headers
@@ -532,7 +553,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in
 			alg := pair.alg.(jwa.KeyEncryptionAlgorithm)
 			key := pair.key
 
-			decrypted, err := dctx.decryptKey(ctx, alg, key, recipient)
+			decrypted, err := dctx.decryptContent(ctx, alg, key, recipient)
 			if err != nil {
 				lastError = err
 				continue
@@ -549,7 +570,7 @@ func (dctx *decryptCtx) try(ctx context.Context, recipient Recipient, keyUsed in
 	return nil, fmt.Errorf(`jwe.Decrypt: tried %d keys, but failed to match any of the keys with recipient (last error = %s)`, tried, lastError)
 }
 
-func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) {
+func (dctx *decryptCtx) decryptContent(ctx context.Context, alg jwa.KeyEncryptionAlgorithm, key interface{}, recipient Recipient) ([]byte, error) {
 	if jwkKey, ok := key.(jwk.Key); ok {
 		var raw interface{}
 		if err := jwkKey.Raw(&raw); err != nil {
@@ -610,31 +631,29 @@ func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlg
 		}
 	case jwa.A128GCMKW, jwa.A192GCMKW, jwa.A256GCMKW:
 		ivB64, ok := h2.Get(InitializationVectorKey)
-		if !ok {
-			return nil, fmt.Errorf(`failed to get 'iv' field`)
-		}
-		ivB64Str, ok := ivB64.(string)
-		if !ok {
-			return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64)
+		if ok {
+			ivB64Str, ok := ivB64.(string)
+			if !ok {
+				return nil, fmt.Errorf("unexpected type for 'iv': %T", ivB64)
+			}
+			iv, err := base64.DecodeString(ivB64Str)
+			if err != nil {
+				return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err)
+			}
+			dec.KeyInitializationVector(iv)
 		}
 		tagB64, ok := h2.Get(TagKey)
-		if !ok {
-			return nil, fmt.Errorf(`failed to get 'tag' field`)
-		}
-		tagB64Str, ok := tagB64.(string)
-		if !ok {
-			return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64)
-		}
-		iv, err := base64.DecodeString(ivB64Str)
-		if err != nil {
-			return nil, fmt.Errorf(`failed to b64-decode 'iv': %w`, err)
-		}
-		tag, err := base64.DecodeString(tagB64Str)
-		if err != nil {
-			return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err)
+		if ok {
+			tagB64Str, ok := tagB64.(string)
+			if !ok {
+				return nil, fmt.Errorf("unexpected type for 'tag': %T", tagB64)
+			}
+			tag, err := base64.DecodeString(tagB64Str)
+			if err != nil {
+				return nil, fmt.Errorf(`failed to b64-decode 'tag': %w`, err)
+			}
+			dec.KeyTag(tag)
 		}
-		dec.KeyInitializationVector(iv)
-		dec.KeyTag(tag)
 	case jwa.PBES2_HS256_A128KW, jwa.PBES2_HS384_A192KW, jwa.PBES2_HS512_A256KW:
 		saltB64, ok := h2.Get(SaltKey)
 		if !ok {
@@ -661,7 +680,7 @@ func (dctx *decryptCtx) decryptKey(ctx context.Context, alg jwa.KeyEncryptionAlg
 		dec.KeyCount(int(countFlt))
 	}
 
-	plaintext, err := dec.Decrypt(recipient.EncryptedKey(), dctx.msg.cipherText)
+	plaintext, err := dec.Decrypt(recipient, dctx.msg.cipherText, dctx.msg)
 	if err != nil {
 		return nil, fmt.Errorf(`jwe.Decrypt: decryption failed: %w`, err)
 	}
diff --git a/jwk/README.md b/jwk/README.md
index ff6b4a4f..85fb0a4b 100644
--- a/jwk/README.md
+++ b/jwk/README.md
@@ -111,10 +111,7 @@ Parse and use a JWK key:
 package examples_test
 
 import (
-  "bytes"
   "context"
-  "crypto/ecdsa"
-  "crypto/elliptic"
   "fmt"
   "log"
 
@@ -179,22 +176,23 @@ func ExampleJWK_Usage() {
 
 //nolint:govet
 func ExampleJWK_MarshalJSON() {
-  // to get the same values every time, we need to create a static source
-  // of "randomness"
-  rdr := bytes.NewReader([]byte("01234567890123456789012345678901234567890123456789ABCDEF"))
-  raw, err := ecdsa.GenerateKey(elliptic.P384(), rdr)
-  if err != nil {
-    fmt.Printf("failed to generate new ECDSA private key: %s\n", err)
-    return
-  }
+  // JWKs that inherently involve randomness such as RSA and EC keys are
+  // not used in this example, because they may produce different results
+  // depending on the environment.
+  //
+  // (In fact, even if you use a static source of randomness, tests may fail
+  // because of internal changes in the Go runtime).
+
+  raw := []byte("01234567890123456789012345678901234567890123456789ABCDEF")
 
+  // This would create a symmetric key
   key, err := jwk.FromRaw(raw)
   if err != nil {
-    fmt.Printf("failed to create ECDSA key: %s\n", err)
+    fmt.Printf("failed to create symmetric key: %s\n", err)
     return
   }
-  if _, ok := key.(jwk.ECDSAPrivateKey); !ok {
-    fmt.Printf("expected jwk.ECDSAPrivateKey, got %T\n", key)
+  if _, ok := key.(jwk.SymmetricKey); !ok {
+    fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
     return
   }
 
@@ -209,12 +207,9 @@ func ExampleJWK_MarshalJSON() {
 
   // OUTPUT:
   // {
-  //   "crv": "P-384",
-  //   "d": "ODkwMTIzNDU2Nzg5MDEyMz7deMbyLt8g4cjcxozuIoygLLlAeoQ1AfM9TSvxkFHJ",
+  //   "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY",
   //   "kid": "mykey",
-  //   "kty": "EC",
-  //   "x": "gvvRMqm1w5aHn7sVNA2QUJeOVcedUnmiug6VhU834gzS9k87crVwu9dz7uLOdoQl",
-  //   "y": "7fVF7b6J_6_g6Wu9RuJw8geWxEi5ja9Gp2TSdELm5u2E-M7IF-bsxqcdOj3n1n7N"
+  //   "kty": "oct"
   // }
 }
 ```
diff --git a/jwk/cache.go b/jwk/cache.go
index 2230505d..5d5b6b90 100644
--- a/jwk/cache.go
+++ b/jwk/cache.go
@@ -113,6 +113,9 @@ type jwksTransform struct {
 var defaultTransform = &jwksTransform{}
 
 func (t *jwksTransform) Transform(u string, res *http.Response) (interface{}, error) {
+	if res.StatusCode != http.StatusOK {
+		return nil, fmt.Errorf(`failed to process response: non-200 response code %q`, res.Status)
+	}
 	buf, err := io.ReadAll(res.Body)
 	if err != nil {
 		return nil, fmt.Errorf(`failed to read response body status: %w`, err)
diff --git a/jwk/fetch.go b/jwk/fetch.go
index daca1773..ddc75cd8 100644
--- a/jwk/fetch.go
+++ b/jwk/fetch.go
@@ -4,8 +4,11 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"math"
 	"os"
 	"strconv"
+	"sync"
+	"sync/atomic"
 
 	"github.com/lestrrat-go/httprc"
 )
@@ -21,18 +24,65 @@ func (f FetchFunc) Fetch(ctx context.Context, u string, options ...FetchOption)
 }
 
 var globalFetcher httprc.Fetcher
+var muGlobalFetcher sync.Mutex
+var fetcherChanged uint32
 
 func init() {
-	var nworkers int
-	v := os.Getenv(`JWK_FETCHER_WORKER_COUNT`)
-	if c, err := strconv.ParseInt(v, 10, 64); err == nil {
-		nworkers = int(c)
+	atomic.StoreUint32(&fetcherChanged, 1)
+}
+
+func getGlobalFetcher() httprc.Fetcher {
+	if v := atomic.LoadUint32(&fetcherChanged); v == 0 {
+		return globalFetcher
 	}
-	if nworkers < 1 {
-		nworkers = 3
+
+	muGlobalFetcher.Lock()
+	defer muGlobalFetcher.Unlock()
+	if globalFetcher == nil {
+		var nworkers int
+		v := os.Getenv(`JWK_FETCHER_WORKER_COUNT`)
+		if c, err := strconv.ParseInt(v, 10, 64); err == nil {
+			if c > math.MaxInt {
+				nworkers = math.MaxInt
+			} else {
+				nworkers = int(c)
+			}
+		}
+		if nworkers < 1 {
+			nworkers = 3
+		}
+
+		globalFetcher = httprc.NewFetcher(context.Background(), httprc.WithFetcherWorkerCount(nworkers))
 	}
 
-	globalFetcher = httprc.NewFetcher(context.Background(), httprc.WithFetcherWorkerCount(nworkers))
+	atomic.StoreUint32(&fetcherChanged, 0)
+	return globalFetcher
+}
+
+// SetGlobalFetcher allows users to specify a custom global fetcher,
+// which is used by the `Fetch` function. Assigning `nil` forces the
+// the default fetcher to be (re)created when the next call to
+// `jwk.Fetch` occurs
+//
+// You only need to call this function when you want to
+// either change the fetching behavior (for example, you want to change
+// how the default whitelist is handled), or when you want to control
+// the lifetime of the global fetcher, for example for tests
+// that require a clean shutdown.
+//
+// If you do use this function to set a custom fetcher and you
+// control its termination, make sure that you call `jwk.SetGlobalFetcher()`
+// one more time (possibly with `nil`) to assign a valid fetcher.
+// Otherwise, once the fetcher is invalidated, subsequent calls to `jwk.Fetch`
+// may hang, causing very hard to debug problems.
+//
+// If you are sure you no longer need `jwk.Fetch` after terminating the
+// fetcher, then you the above caution is not necessary.
+func SetGlobalFetcher(f httprc.Fetcher) {
+	muGlobalFetcher.Lock()
+	globalFetcher = f
+	muGlobalFetcher.Unlock()
+	atomic.StoreUint32(&fetcherChanged, 1)
 }
 
 // Fetch fetches a JWK resource specified by a URL. The url must be
@@ -43,6 +93,14 @@ func init() {
 // contents of the object with the data at the remote resource,
 // consider using `jwk.Cache`, which automatically refreshes
 // jwk.Set objects asynchronously.
+//
+// Please note that underneath the `jwk.Fetch` function, it uses a global
+// object that spawns goroutines that are present until the go runtime
+// exits. Initially this global variable is uninitialized, but upon
+// calling `jwk.Fetch` once, it is initialized and goroutines are spawned.
+// If you want to control the lifetime of these goroutines, you can
+// call `jwk.SetGlobalFetcher` with a custom fetcher which is tied to
+// a `context.Context` object that you can control.
 func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) {
 	var hrfopts []httprc.FetchOption
 	var parseOptions []ParseOption
@@ -61,7 +119,7 @@ func Fetch(ctx context.Context, u string, options ...FetchOption) (Set, error) {
 		}
 	}
 
-	res, err := globalFetcher.Fetch(ctx, u, hrfopts...)
+	res, err := getGlobalFetcher().Fetch(ctx, u, hrfopts...)
 	if err != nil {
 		return nil, fmt.Errorf(`failed to fetch %q: %w`, u, err)
 	}
diff --git a/jwk/jwk.go b/jwk/jwk.go
index 7aced492..8521ba6e 100644
--- a/jwk/jwk.go
+++ b/jwk/jwk.go
@@ -43,7 +43,7 @@ func bigIntToBytes(n *big.Int) ([]byte, error) {
 //   - []byte creates a symmetric key
 func FromRaw(key interface{}) (Key, error) {
 	if key == nil {
-		return nil, fmt.Errorf(`jwk.New requires a non-nil key`)
+		return nil, fmt.Errorf(`jwk.FromRaw requires a non-nil key`)
 	}
 
 	var ptr interface{}
diff --git a/jws/signer.go b/jws/signer.go
index 279386c5..44c8bfb7 100644
--- a/jws/signer.go
+++ b/jws/signer.go
@@ -2,6 +2,7 @@ package jws
 
 import (
 	"fmt"
+	"sync"
 
 	"github.com/lestrrat-go/jwx/v2/jwa"
 )
@@ -15,6 +16,7 @@ func (fn SignerFactoryFn) Create() (Signer, error) {
 	return fn()
 }
 
+var muSignerDB sync.RWMutex
 var signerDB map[jwa.SignatureAlgorithm]SignerFactory
 
 // RegisterSigner is used to register a factory object that creates
@@ -23,8 +25,30 @@ var signerDB map[jwa.SignatureAlgorithm]SignerFactory
 // For example, if you would like to provide a custom signer for
 // jwa.EdDSA, use this function to register a `SignerFactory`
 // (probably in your `init()`)
+//
+// Unlike the `UnregisterSigner` function, this function automatically
+// calls `jwa.RegisterSignatureAlgorithm` to register the algorithm
+// in the known algorithms database.
 func RegisterSigner(alg jwa.SignatureAlgorithm, f SignerFactory) {
+	jwa.RegisterSignatureAlgorithm(alg)
+	muSignerDB.Lock()
 	signerDB[alg] = f
+	muSignerDB.Unlock()
+}
+
+// UnregisterSigner removes the signer factory associated with
+// the given algorithm.
+//
+// Note that when you call this function, the algorithm itself is
+// not automatically unregistered from the known algorithms database.
+// This is because the algorithm may still be required for verification or
+// some other operation (however unlikely, it is still possible).
+// Therefore, in order to completely remove the algorithm, you must
+// call `jwa.UnregisterSignatureAlgorithm` yourself.
+func UnregisterSigner(alg jwa.SignatureAlgorithm) {
+	muSignerDB.Lock()
+	delete(signerDB, alg)
+	muSignerDB.Unlock()
 }
 
 func init() {
@@ -61,7 +85,10 @@ func init() {
 
 // NewSigner creates a signer that signs payloads using the given signature algorithm.
 func NewSigner(alg jwa.SignatureAlgorithm) (Signer, error) {
+	muSignerDB.RLock()
 	f, ok := signerDB[alg]
+	muSignerDB.RUnlock()
+
 	if ok {
 		return f.Create()
 	}
diff --git a/jws/verifier.go b/jws/verifier.go
index 8093f879..2dd29c84 100644
--- a/jws/verifier.go
+++ b/jws/verifier.go
@@ -2,6 +2,7 @@ package jws
 
 import (
 	"fmt"
+	"sync"
 
 	"github.com/lestrrat-go/jwx/v2/jwa"
 )
@@ -15,6 +16,7 @@ func (fn VerifierFactoryFn) Create() (Verifier, error) {
 	return fn()
 }
 
+var muVerifierDB sync.RWMutex
 var verifierDB map[jwa.SignatureAlgorithm]VerifierFactory
 
 // RegisterVerifier is used to register a factory object that creates
@@ -23,8 +25,30 @@ var verifierDB map[jwa.SignatureAlgorithm]VerifierFactory
 // For example, if you would like to provide a custom verifier for
 // jwa.EdDSA, use this function to register a `VerifierFactory`
 // (probably in your `init()`)
+//
+// Unlike the `UnregisterVerifier` function, this function automatically
+// calls `jwa.RegisterSignatureAlgorithm` to register the algorithm
+// in the known algorithms database.
 func RegisterVerifier(alg jwa.SignatureAlgorithm, f VerifierFactory) {
+	jwa.RegisterSignatureAlgorithm(alg)
+	muVerifierDB.Lock()
 	verifierDB[alg] = f
+	muVerifierDB.Unlock()
+}
+
+// UnregisterVerifier removes the signer factory associated with
+// the given algorithm.
+//
+// Note that when you call this function, the algorithm itself is
+// not automatically unregistered from the known algorithms database.
+// This is because the algorithm may still be required for signing or
+// some other operation (however unlikely, it is still possible).
+// Therefore, in order to completely remove the algorithm, you must
+// call `jwa.UnregisterSignatureAlgorithm` yourself.
+func UnregisterVerifier(alg jwa.SignatureAlgorithm) {
+	muVerifierDB.Lock()
+	delete(verifierDB, alg)
+	muVerifierDB.Unlock()
 }
 
 func init() {
@@ -61,7 +85,10 @@ func init() {
 
 // NewVerifier creates a verifier that signs payloads using the given signature algorithm.
 func NewVerifier(alg jwa.SignatureAlgorithm) (Verifier, error) {
+	muVerifierDB.RLock()
 	f, ok := verifierDB[alg]
+	muVerifierDB.RUnlock()
+
 	if ok {
 		return f.Create()
 	}
diff --git a/jwt/options.go b/jwt/options.go
index b7f78fc8..19e30fc4 100644
--- a/jwt/options.go
+++ b/jwt/options.go
@@ -187,7 +187,7 @@ func WithJwtID(s string) ValidateOption {
 
 // WithAudience specifies that expected audience value.
 // `Validate()` will return true if one of the values in the `aud` element
-// matches this value.  If not specified, the value of issuer is not
+// matches this value. If not specified, the value of `aud` is not
 // verified at all.
 func WithAudience(s string) ValidateOption {
 	return WithValidator(audienceClaimContainsString(s))
diff --git a/tools/cmd/genjwa/main.go b/tools/cmd/genjwa/main.go
index fd123c3e..b7f8a7f6 100644
--- a/tools/cmd/genjwa/main.go
+++ b/tools/cmd/genjwa/main.go
@@ -400,19 +400,48 @@ func (t typ) Generate() error {
 	}
 	o.L(")") // end const
 
-	o.L("var all%[1]ss = map[%[1]s]struct{} {", t.name)
+	// Register%s and related tools are provided so users can register their own types.
+	// This triggers some re-building of data structures that are otherwise
+	// reused for efficiency
+	o.LL("var mu%[1]ss sync.RWMutex", t.name)
+	o.L("var all%[1]ss map[%[1]s]struct{}", t.name)
+	o.L("var list%[1]s []%[1]s", t.name)
+
+	o.LL("func init() {")
+	o.L("mu%[1]ss.Lock()", t.name)
+	o.L("defer mu%[1]ss.Unlock()", t.name)
+	o.L("all%[1]ss = make(map[%[1]s]struct{})", t.name)
 	for _, e := range t.elements {
 		if !e.invalid {
-			o.L("%s: {},", e.name)
+			o.L("all%[1]ss[%[2]s] = struct{}{}", t.name, e.name)
 		}
 	}
+	o.L("rebuild%[1]s()", t.name)
 	o.L("}")
 
-	o.LL("var list%sOnce sync.Once", t.name)
-	o.L("var list%[1]s []%[1]s", t.name)
-	o.LL("// %[1]ss returns a list of all available values for %[1]s", t.name)
-	o.L("func %[1]ss() []%[1]s {", t.name)
-	o.L("list%sOnce.Do(func() {", t.name)
+	o.LL("// Register%[1]s registers a new %[1]s so that the jwx can properly handle the new value.", t.name)
+	o.L("// Duplicates will silently be ignored")
+	o.L("func Register%[1]s(v %[1]s) {", t.name)
+	o.L("mu%[1]ss.Lock()", t.name)
+	o.L("defer mu%[1]ss.Unlock()", t.name)
+	o.L("if _, ok := all%[1]ss[v]; !ok {", t.name)
+	o.L("all%[1]ss[v] = struct{}{}", t.name)
+	o.L("rebuild%[1]s()", t.name)
+	o.L("}")
+	o.L("}")
+
+	o.LL("// Unregister%[1]s unregisters a %[1]s from its known database.", t.name)
+	o.L("// Non-existentn entries will silently be ignored")
+	o.L("func Unregister%[1]s(v %[1]s) {", t.name)
+	o.L("mu%[1]ss.Lock()", t.name)
+	o.L("defer mu%[1]ss.Unlock()", t.name)
+	o.L("if _, ok := all%[1]ss[v]; ok {", t.name)
+	o.L("delete(all%[1]ss, v)", t.name)
+	o.L("rebuild%[1]s()", t.name)
+	o.L("}")
+	o.L("}")
+
+	o.LL("func rebuild%[1]s() {", t.name)
 	o.L("list%[1]s = make([]%[1]s, 0, len(all%[1]ss))", t.name)
 	o.L("for v := range all%ss {", t.name)
 	o.L("list%[1]s = append(list%[1]s, v)", t.name)
@@ -420,7 +449,12 @@ func (t typ) Generate() error {
 	o.L("sort.Slice(list%s, func(i, j int) bool {", t.name)
 	o.L("return string(list%[1]s[i]) < string(list%[1]s[j])", t.name)
 	o.L("})")
-	o.L("})")
+	o.L("}")
+
+	o.LL("// %[1]ss returns a list of all available values for %[1]s", t.name)
+	o.L("func %[1]ss() []%[1]s {", t.name)
+	o.L("mu%[1]ss.RLock()", t.name)
+	o.L("defer mu%[1]ss.RUnlock()", t.name)
 	o.L("return list%s", t.name)
 	o.L("}")
 


Test patch:
diff --git a/bench/performance/jwt_benchmark_test.go b/bench/performance/jwt_benchmark_test.go
index 6635aeff..0f9fded3 100644
--- a/bench/performance/jwt_benchmark_test.go
+++ b/bench/performance/jwt_benchmark_test.go
@@ -111,14 +111,14 @@ func BenchmarkJWT(b *testing.B) {
 					Name:      "jwt.ParseString",
 					SkipShort: true,
 					Test: func(b *testing.B) error {
-						_, err := jwt.ParseString(signedString)
+						_, err := jwt.ParseString(signedString, jwt.WithVerify(false), jwt.WithValidate(false))
 						return err
 					},
 				},
 				{
 					Name: "jwt.Parse",
 					Test: func(b *testing.B) error {
-						_, err := jwt.Parse(signedBuf)
+						_, err := jwt.Parse(signedBuf, jwt.WithVerify(false), jwt.WithValidate(false))
 						return err
 					},
 				},
@@ -130,7 +130,7 @@ func BenchmarkJWT(b *testing.B) {
 						return err
 					},
 					Test: func(b *testing.B) error {
-						_, err := jwt.ParseReader(signedReader)
+						_, err := jwt.ParseReader(signedReader, jwt.WithVerify(false), jwt.WithValidate(false))
 						return err
 					},
 				},
diff --git a/examples/jwk_example_test.go b/examples/jwk_example_test.go
index 0d0307d9..2eb294c2 100644
--- a/examples/jwk_example_test.go
+++ b/examples/jwk_example_test.go
@@ -1,10 +1,7 @@
 package examples_test
 
 import (
-	"bytes"
 	"context"
-	"crypto/ecdsa"
-	"crypto/elliptic"
 	"fmt"
 	"log"
 
@@ -69,22 +66,23 @@ func ExampleJWK_Usage() {
 
 //nolint:govet
 func ExampleJWK_MarshalJSON() {
-	// to get the same values every time, we need to create a static source
-	// of "randomness"
-	rdr := bytes.NewReader([]byte("01234567890123456789012345678901234567890123456789ABCDEF"))
-	raw, err := ecdsa.GenerateKey(elliptic.P384(), rdr)
-	if err != nil {
-		fmt.Printf("failed to generate new ECDSA private key: %s\n", err)
-		return
-	}
+	// JWKs that inherently involve randomness such as RSA and EC keys are
+	// not used in this example, because they may produce different results
+	// depending on the environment.
+	//
+	// (In fact, even if you use a static source of randomness, tests may fail
+	// because of internal changes in the Go runtime).
+
+	raw := []byte("01234567890123456789012345678901234567890123456789ABCDEF")
 
+	// This would create a symmetric key
 	key, err := jwk.FromRaw(raw)
 	if err != nil {
-		fmt.Printf("failed to create ECDSA key: %s\n", err)
+		fmt.Printf("failed to create symmetric key: %s\n", err)
 		return
 	}
-	if _, ok := key.(jwk.ECDSAPrivateKey); !ok {
-		fmt.Printf("expected jwk.ECDSAPrivateKey, got %T\n", key)
+	if _, ok := key.(jwk.SymmetricKey); !ok {
+		fmt.Printf("expected jwk.SymmetricKey, got %T\n", key)
 		return
 	}
 
@@ -99,11 +97,8 @@ func ExampleJWK_MarshalJSON() {
 
 	// OUTPUT:
 	// {
-	//   "crv": "P-384",
-	//   "d": "ODkwMTIzNDU2Nzg5MDEyMz7deMbyLt8g4cjcxozuIoygLLlAeoQ1AfM9TSvxkFHJ",
+	//   "k": "MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODlBQkNERUY",
 	//   "kid": "mykey",
-	//   "kty": "EC",
-	//   "x": "gvvRMqm1w5aHn7sVNA2QUJeOVcedUnmiug6VhU834gzS9k87crVwu9dz7uLOdoQl",
-	//   "y": "7fVF7b6J_6_g6Wu9RuJw8geWxEi5ja9Gp2TSdELm5u2E-M7IF-bsxqcdOj3n1n7N"
+	//   "kty": "oct"
 	// }
 }
diff --git a/internal/base64/base64_test.go b/internal/base64/base64_test.go
index e0e09488..b4214e74 100644
--- a/internal/base64/base64_test.go
+++ b/internal/base64/base64_test.go
@@ -47,12 +47,3 @@ func TestDecode(t *testing.T) {
 		})
 	}
 }
-func TestEncodeToStringStd(t *testing.T) {
-	t.Parallel()
-	t.Run("Encodes to StdEncoding with padding", func(t *testing.T) {
-		t.Parallel()
-		out, err := base64.StdEncoding.DecodeString(EncodeToStringStd([]byte("Hello, World!")))
-		assert.NoError(t, err)
-		assert.NotNil(t, out)
-	})
-}
diff --git a/internal/keyconv/keyconv_test.go b/internal/keyconv/keyconv_test.go
index 5e908ca4..abb418fd 100644
--- a/internal/keyconv/keyconv_test.go
+++ b/internal/keyconv/keyconv_test.go
@@ -44,7 +44,11 @@ func TestKeyconv(t *testing.T) {
 					if !checker(t, keyconv.RSAPrivateKey(&dst, tc.Src), `keyconv.RSAPrivateKey should succeed`) {
 						return
 					}
+
 					if !tc.Error {
+						// From Go 1.20 on, for purposes of our test, we need the
+						// precomputed values as well
+						dst.Precompute()
 						if !assert.Equal(t, key, &dst, `keyconv.RSAPrivateKey should produce same value`) {
 							return
 						}
@@ -63,6 +67,9 @@ func TestKeyconv(t *testing.T) {
 						return
 					}
 					if !tc.Error {
+						// From Go 1.20 on, for purposes of our test, we need the
+						// precomputed values as well
+						dst.Precompute()
 						if !assert.Equal(t, key, dst, `keyconv.RSAPrivateKey should produce same value`) {
 							return
 						}
diff --git a/jwe/jwe_test.go b/jwe/jwe_test.go
index d3732ac8..64800de9 100644
--- a/jwe/jwe_test.go
+++ b/jwe/jwe_test.go
@@ -1,6 +1,7 @@
 package jwe_test
 
 import (
+	"bytes"
 	"context"
 	"crypto"
 	"crypto/ecdsa"
@@ -674,7 +675,6 @@ func TestGHIssue230(t *testing.T) {
 
 func TestReadFile(t *testing.T) {
 	const s = `eyJhbGciOiJSU0EtT0FFUCIsImVuYyI6IkEyNTZHQ00ifQ.OKOawDo13gRp2ojaHV7LFpZcgV7T6DVZKTyKOMTYUmKoTCVJRgckCL9kiMT03JGeipsEdY3mx_etLbbWSrFr05kLzcSr4qKAq7YN7e9jwQRb23nfa6c9d-StnImGyFDbSv04uVuxIp5Zms1gNxKKK2Da14B8S4rzVRltdYwam_lDp5XnZAYpQdb76FdIKLaVmqgfwX7XWRxv2322i-vDxRfqNzo_tETKzpVLzfiwQyeyPGLBIO56YJ7eObdv0je81860ppamavo35UgoRdbYaBcoh9QcfylQr66oc6vFWXRcZ_ZT2LawVCWTIy3brGPi6UklfCpIMfIjf7iGdXKHzg.48V1_ALb6US04U3b.5eym8TW_c8SuK0ltJ3rpYIzOeDQz7TALvtu6UG9oMo4vpzs9tX_EFShS8iB7j6jiSdiwkIr3ajwQzaBtQD_A.XFBoMYUZodetZdvTiFvSkQ`
-	t.Parallel()
 
 	f, err := os.CreateTemp("", "test-read-file-*.jwe")
 	if !assert.NoError(t, err, `os.CreateTemp should succeed`) {
@@ -841,3 +841,45 @@ func TestGH840(t *testing.T) {
 	_, err = jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.ECDH_ES_A128KW, pubkey))
 	require.Error(t, err, `jwe.Encrypt should fail (instead of panic)`)
 }
+
+type dummyKeyEncrypterDecrypter struct {
+	key []byte
+}
+
+func (kd *dummyKeyEncrypterDecrypter) DecryptKey(alg jwa.KeyEncryptionAlgorithm, cek []byte, _ jwe.Recipient, _ *jwe.Message) ([]byte, error) {
+	return bytes.TrimSuffix(cek, kd.key), nil
+}
+
+func (kd *dummyKeyEncrypterDecrypter) Algorithm() jwa.KeyEncryptionAlgorithm {
+	return jwa.A128GCMKW
+}
+
+func (kd *dummyKeyEncrypterDecrypter) EncryptKey(key []byte) ([]byte, error) {
+	return append(key, kd.key...), nil
+}
+
+var _ jwe.KeyEncrypter = (*dummyKeyEncrypterDecrypter)(nil)
+
+func TestGH924(t *testing.T) {
+	sharedKey := []byte("abra-kadabra")
+
+	ked := &dummyKeyEncrypterDecrypter{key: sharedKey}
+
+	payload := []byte("Lorem Ipsum")
+	encrypted, err := jwe.Encrypt(
+		payload,
+		jwe.WithJSON(),
+		jwe.WithKey(jwa.A128GCMKW, ked),
+		jwe.WithContentEncryption(jwa.A128GCM),
+	)
+	require.NoError(t, err, `jwe.Encrypt should succeed`)
+
+	var msg jwe.Message
+	decrypted, err := jwe.Decrypt(
+		encrypted,
+		jwe.WithKey(jwa.A128GCMKW, ked),
+		jwe.WithMessage(&msg),
+	)
+	require.NoError(t, err, `jwe.Decrypt should succeed`)
+	require.Equal(t, payload, decrypted, `decrypt messages match`)
+}
diff --git a/jwk/jwk_test.go b/jwk/jwk_test.go
index f5c084c9..19d28dcf 100644
--- a/jwk/jwk_test.go
+++ b/jwk/jwk_test.go
@@ -2130,3 +2130,49 @@ func TestECDSAPEM(t *testing.T) {
 		t.Fatal(err)
 	}
 }
+
+// This test is commented out because we did not want to include `go.uber.org/goleak`
+// in our dependency. We agree that it's important to check for goroutine leaks,
+// but 1) this is sort of expected in this library, and 2) we don't believe that
+// forcing all of our users to use `go.uber.org/goleak` is prudent.
+//
+// However, we are leaving this test here so that users can learn how this function
+// can be used. This is meant to show you the boilerplate code for when you/ want to make
+// absolutely sure that no goroutine is left when you finish your program.
+//
+// For example, if you are writing an app, you can follow the pattern in the
+// test below and stop the goroutines that are started by httprc.NewFetcher
+// when your app terminates:
+//
+//	func AppMain() {
+//	  ctx, cancel := context.WithCancel(context.Background())
+//	  defer cancel()
+//
+//	  jwk.SetGlobalFetcher(http.NewFetcher(ctx))
+//	  // your app code goes here
+//	}
+//
+// Then, you can be sure that no goroutines are left when `AppMain()` is done.
+// You can also verify this in your test:
+//
+//	  func TestApp(t *testing.T) {
+//	    AppMain()
+//		goleak.VerifyNone(t)
+//	  }
+/*
+func TestGH928(t *testing.T) {
+	ctx, cancel := context.WithCancel(context.Background())
+	jwk.SetGlobalFetcher(httprc.NewFetcher(ctx))
+
+	// If you are using a custom fetcher like this in your test and you
+	// still have more tests to run, you probably want to include the
+	// following line to restore the default behavior after this test.
+	// Otherwise, calls to `jwk.Fetch` may hang.
+	defer jwk.SetGlobalFetcher(nil) // This must be included to restore default behavior
+
+	cancel() // stop fetcher goroutines
+
+	// At this point, not goroutines from httprc.Fetcher should be running
+	goleak.VerifyNone(t)
+}
+*/
diff --git a/jws/jws_test.go b/jws/jws_test.go
index 06e9eb3b..53d1803b 100644
--- a/jws/jws_test.go
+++ b/jws/jws_test.go
@@ -8,8 +8,10 @@ import (
 	"crypto/ecdsa"
 	"crypto/ed25519"
 	"crypto/rsa"
+	"crypto/sha256"
 	"crypto/sha512"
 	"encoding/asn1"
+	"errors"
 	"fmt"
 	"io"
 	"math/big"
@@ -1989,3 +1991,56 @@ func TestGH888(t *testing.T) {
 	require.NoError(t, err, `jws.Verify should succeed`)
 	require.Equal(t, []byte(`foo`), verified)
 }
+
+// Some stuff required for testing #910
+// The original code used an external library to sign/verify, but here
+// we just use a simple SHA256 digest here so that we don't force
+// users to download an optional dependency
+type s256SignerVerifier struct{}
+
+const sha256Algo jwa.SignatureAlgorithm = "SillyTest256"
+
+func (s256SignerVerifier) Algorithm() jwa.SignatureAlgorithm {
+	return sha256Algo
+}
+
+func (s256SignerVerifier) Sign(payload []byte, _ interface{}) ([]byte, error) {
+	h := sha256.Sum256(payload)
+	return h[:], nil
+}
+
+func (s256SignerVerifier) Verify(payload, signature []byte, _ interface{}) error {
+	h := sha256.Sum256(payload)
+	if !bytes.Equal(h[:], signature) {
+		return errors.New("invalid signature")
+	}
+	return nil
+}
+
+func TestGH910(t *testing.T) {
+	// Note: This has global effect. You can't run this in parallel with other tests
+	jws.RegisterSigner(sha256Algo, jws.SignerFactoryFn(func() (jws.Signer, error) {
+		return s256SignerVerifier{}, nil
+	}))
+	defer jws.UnregisterSigner(sha256Algo)
+
+	jws.RegisterVerifier(sha256Algo, jws.VerifierFactoryFn(func() (jws.Verifier, error) {
+		return s256SignerVerifier{}, nil
+	}))
+	defer jws.UnregisterVerifier(sha256Algo)
+	defer jwa.UnregisterSignatureAlgorithm(sha256Algo)
+
+	var sa jwa.SignatureAlgorithm
+	require.NoError(t, sa.Accept(sha256Algo.String()), `jwa.SignatureAlgorithm.Accept should succeed`)
+
+	// Now that we have established that the signature algorithm works,
+	// we can proceed with the test
+	const src = `Lorem Ipsum`
+	signed, err := jws.Sign([]byte(src), jws.WithKey(sha256Algo, nil))
+	require.NoError(t, err, `jws.Sign should succeed`)
+
+	verified, err := jws.Verify(signed, jws.WithKey(sha256Algo, nil))
+	require.NoError(t, err, `jws.Verify should succeed`)
+
+	require.Equal(t, src, string(verified), `verified payload should match`)
+}