In this document we describe how to work with JWK using github.com/lestrrat-go/jwx/v2/jwe
Parsing a JWE message means taking either a JWE message serialized in JSON or Compact form and loading it into a jwe.Message
object. No decryption is performed, and therefore you cannot access the raw payload like when you use jwe.Decrypt()
to decrypt the message.
Also, be aware that a jwe.Message
is not meant to be used for either decryption nor encryption. It is only provided so that it can be inspected -- there is no way to decrypt or sign using an already parsed jwe.Message
.
You can parse a JWE message in memory stored as []byte
into a jwe.Message
object. In this mode, there is no decryption performed.
package examples_test
import (
"encoding/json"
"fmt"
"os"
"github.com/lestrrat-go/jwx/v2/jwe"
)
func ExampleJWE_Parse() {
const src = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA.bK7z7Z3gEzFDgDQvNen0Ww.2hngnAVrmucUpJKLgIzYcg.CHs3ZP7JtG430Dl9YAKLMAl`
msg, err := jwe.Parse([]byte(src))
if err != nil {
fmt.Printf("failed to parse JWE message: %s\n", err)
return
}
json.NewEncoder(os.Stdout).Encode(msg)
// OUTPUT:
// {"ciphertext":"2hngnAVrmucUpJKLgIzYcg","encrypted_key":"KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA","header":{"alg":"RSA1_5"},"iv":"bK7z7Z3gEzFDgDQvNen0Ww","protected":"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"CHs3ZP7JtG430Dl9YAKLMAk"}
}
source: examples/jwe_parse_example_test.go
To parse a JWE stored in a file, use jwe.ReadFile()
. jwe.ReadFile()
accepts the same options as jwe.Parse()
.
package examples_test
import (
"encoding/json"
"fmt"
"os"
"github.com/lestrrat-go/jwx/v2/jwe"
)
func ExampleJWE_ReadFile() {
const src = `eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0.KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA.bK7z7Z3gEzFDgDQvNen0Ww.2hngnAVrmucUpJKLgIzYcg.CHs3ZP7JtG430Dl9YAKLMAl`
f, err := os.CreateTemp(``, `jwe_readfile_example-*.jwe`)
if err != nil {
fmt.Printf("failed to create temporary file: %s\n", err)
return
}
defer os.Remove(f.Name())
f.Write([]byte(src))
f.Close()
msg, err := jwe.ReadFile(f.Name())
if err != nil {
fmt.Printf("failed to parse JWE message from file %q: %s\n", f.Name(), err)
return
}
json.NewEncoder(os.Stdout).Encode(msg)
// OUTPUT:
// {"ciphertext":"2hngnAVrmucUpJKLgIzYcg","encrypted_key":"KrFTaMKVY_iUKYYk905QjbUf_fpBXvXCzIAfbPoPMGViDzxtgz5qnch8waV7wraVDfzpW7JfPOw6Nz_-XRwN3Vbud48bRYFw92GkC0M6kpKFpl_xgZxGN47ggNk9hzgqd7mFCuyufeYdn5c2fPoRZAV4UxvakLozEYcQo-eZaFmoYS4pyoC-IKKRikobW8n__LksMzXc_Vps1axn5kdpxsKQ4k1oayvUrgWX2PMxKn_TcLEKHtCN7qRlJ5hkKbZAXAdd34zGWcFV5gc1tcLs6HFhnebo8GUgItTYWBKSKzF6MyLJNRSUPFVq9q-Jxi1juXIlDrv_7rHVsdokQmBfvA","header":{"alg":"RSA1_5"},"iv":"bK7z7Z3gEzFDgDQvNen0Ww","protected":"eyJhbGciOiJSU0ExXzUiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2In0","tag":"CHs3ZP7JtG430Dl9YAKLMAk"}
}
source: examples/jwe_readfile_example_test.go
To encrypt an arbitrary payload as a JWE message in compact serialization format, use jwt.Encrypt()
.
Note that this would be slightly different if you are encrypting JWTs, as you would be
using functions from the jwt
package instead of jws
.
package examples_test
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
"github.com/lestrrat-go/jwx/v2/jwk"
)
func ExampleJWE_Encrypt() {
rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to create raw private key: %s\n", err)
return
}
privkey, err := jwk.FromRaw(rawprivkey)
if err != nil {
fmt.Printf("failed to create private key: %s\n", err)
return
}
pubkey, err := privkey.PublicKey()
if err != nil {
fmt.Printf("failed to create public key:%s\n", err)
return
}
const payload = `Lorem ipsum`
encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP, pubkey))
if err != nil {
fmt.Printf("failed to encrypt payload: %s\n", err)
return
}
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, privkey))
if err != nil {
fmt.Printf("failed to decrypt payload: %s\n", err)
return
}
fmt.Printf("%s\n", decrypted)
// OUTPUT:
// Lorem ipsum
}
source: examples/jwe_encrypt_example_test.go
Generally the only time you need to use a JSON serialization format is when you have to generate multiple recipients (encrypted keys) for a given payload using multiple encryption algorithms and keys.
When this need arises, use the jwe.Encrypt()
function with the jwe.WithJSON()
option and multiple jwe.WithKey()
options:
package examples_test
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
"github.com/lestrrat-go/jwx/v2/jwk"
)
func ExampleJWE_EncryptJSON() {
rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to create raw private key: %s\n", err)
return
}
privkey, err := jwk.FromRaw(rawprivkey)
if err != nil {
fmt.Printf("failed to create private key: %s\n", err)
return
}
pubkey, err := privkey.PublicKey()
if err != nil {
fmt.Printf("failed to create public key:%s\n", err)
return
}
const payload = `Lorem ipsum`
encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithJSON(), jwe.WithKey(jwa.RSA_OAEP, pubkey))
if err != nil {
fmt.Printf("failed to encrypt payload: %s\n", err)
return
}
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, privkey))
if err != nil {
fmt.Printf("failed to decrypt payload: %s\n", err)
return
}
fmt.Printf("%s\n", decrypted)
// OUTPUT:
// Lorem ipsum
}
func ExampleJWE_EncryptJSONMulti() {
var privkeys []jwk.Key
var pubkeys []jwk.Key
for i := 0; i < 3; i++ {
rawprivkey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to create raw private key: %s\n", err)
return
}
privkey, err := jwk.FromRaw(rawprivkey)
if err != nil {
fmt.Printf("failed to create private key: %s\n", err)
return
}
privkeys = append(privkeys, privkey)
pubkey, err := privkey.PublicKey()
if err != nil {
fmt.Printf("failed to create public key:%s\n", err)
return
}
pubkeys = append(pubkeys, pubkey)
}
options := []jwe.EncryptOption{jwe.WithJSON()}
for _, key := range pubkeys {
options = append(options, jwe.WithKey(jwa.RSA_OAEP, key))
}
const payload = `Lorem ipsum`
encrypted, err := jwe.Encrypt([]byte(payload), options...)
if err != nil {
fmt.Printf("failed to encrypt payload: %s\n", err)
return
}
for _, key := range privkeys {
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, key))
if err != nil {
fmt.Printf("failed to decrypt payload: %s\n", err)
return
}
fmt.Printf("%s\n", decrypted)
}
// OUTPUT:
// Lorem ipsum
// Lorem ipsum
// Lorem ipsum
}
source: examples/jwe_encrypt_json_example_test.go
By default, only some header fields are included in the result from jwe.Encrypt()
.
For global protected headers, you can use the jwe.WithProtectedHeaders()
option.
In order to provide extra headers to the encrypted message such as apu
and apv
, you will need to use
jwe.WithKey()
option with the jwe.WithPerRecipientHeaders()
suboption.
package examples_test
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"os"
"github.com/lestrrat-go/jwx/v2/internal/json"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
)
func ExampleJWE_SignWithHeaders() {
privkey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to create private key: %s\n", err)
return
}
const payload = "Lorem ipsum"
hdrs := jwe.NewHeaders()
hdrs.Set(`x-example`, true)
encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP, privkey.PublicKey, jwe.WithPerRecipientHeaders(hdrs)))
if err != nil {
fmt.Printf("failed to encrypt payload: %s\n", err)
return
}
msg, err := jwe.Parse(encrypted)
if err != nil {
fmt.Printf("failed to parse message: %s\n", err)
return
}
// NOTE: This is a bit tricky. Even though we specified a per-recipient
// header when executing jwe.Encrypt, the headers end up being in the
// global protected headers section. This is... by the books. JWE
// in Compact serialization asks us to shove the per-recipient
// headers in the protected header section, because there is nowhere
// else to store this information.
//
// If this were a full JWE JSON message, you might have to juggle
// between the global protected headers, global unprotected headers,
// and per-recipient unprotected headers
json.NewEncoder(os.Stdout).Encode(msg.ProtectedHeaders())
// OUTPUT:
// {"alg":"RSA-OAEP","enc":"A256GCM","x-example":true}
}
source: examples/jwe_encrypt_with_headers_example_test.go
To decrypt a JWE message using a single key, use jwe.Decrypt()
with the jwe.WithKey()
option.
It will automatically do the right thing whether it's serialized in compact form or JSON form.
The alg
must be explicitly specified.
package examples_test
import (
"fmt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
)
func ExampleJWE_VerifyWithKey() {
const payload = "Lorem ipsum"
encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP, jwkRSAPublicKey))
if err != nil {
fmt.Printf("failed to sign payload: %s\n", err)
return
}
decrypted, err := jwe.Decrypt(encrypted, jwe.WithKey(jwa.RSA_OAEP, jwkRSAPrivateKey))
if err != nil {
fmt.Printf("failed to sign payload: %s\n", err)
return
}
fmt.Printf("%s\n", decrypted)
// OUTPUT:
// Lorem ipsum
}
source: examples/jwe_decrypt_with_key_example_test.go
To decrypt a payload using JWKS, by default you will need your payload and JWKS to have matching alg
field.
The alg
field's requirement is the same for using a single key. See "Why don't you automatically infer the algorithm for jws.Verify
?", it's the same for jwe.Decrypt()
.
Note that unlike in JWT, the kid
is not required by default, although you can make it so
by passing jwe.WithRequireKid(true)
.
For more discussion on why/how alg
/kid
values work, please read the relevant section in the JWT documentation.
package examples_test
import (
"crypto/rand"
"crypto/rsa"
"fmt"
"github.com/lestrrat-go/jwx/v2/jwa"
"github.com/lestrrat-go/jwx/v2/jwe"
"github.com/lestrrat-go/jwx/v2/jwk"
)
func ExampleJWE_VerifyWithJWKSet() {
privkey, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
fmt.Printf("failed to create private key: %s\n", err)
return
}
const payload = "Lorem ipsum"
encrypted, err := jwe.Encrypt([]byte(payload), jwe.WithKey(jwa.RSA_OAEP, privkey.PublicKey))
if err != nil {
fmt.Printf("failed to sign payload: %s\n", err)
return
}
// Create a JWK Set
set := jwk.NewSet()
// Add some bogus keys
k1, _ := jwk.FromRaw([]byte("abracadabra"))
set.AddKey(k1)
k2, _ := jwk.FromRaw([]byte("opensesame"))
set.AddKey(k2)
// Add the real thing
k3, _ := jwk.FromRaw(privkey)
k3.Set(jwk.AlgorithmKey, jwa.RSA_OAEP)
set.AddKey(k3)
// Up to this point, you probably will replace with a simple jwk.Fetch()
if _, err := jwe.Decrypt(encrypted, jwe.WithKeySet(set, jwe.WithRequireKid(false))); err != nil {
fmt.Printf("Failed to decrypt using jwk.Set: %s", err)
}
// OUTPUT:
}