JSON Web Signature¶
JSON Web Signature (JWS) represents content secured with digital signatures or Message Authentication Codes (MACs) using JSON-based data structures. (via RFC7515)
Compact Signature¶
The JWS Compact Serialization represents digitally signed or MACed content as a compact, URL-safe string. This string is:
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload) || '.' ||
BASE64URL(JWS Signature)
An example of a compact serialization (line breaks for display purposes only):
eyJ0eXAiOiJKV1QiLA0KICJhbGciOiJIUzI1NiJ9.
eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFt
cGxlLmNvbS9pc19yb290Ijp0cnVlfQ.
dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
Serialization¶
You can call jws.serialize_compact()
to construct a compact JWS serialization:
from joserfc import jws
from joserfc.jwk import OctKey
key = OctKey.import_key("secret")
protected = {"alg": "HS256"}
jws.serialize_compact(protected, "hello", key)
# => 'eyJhbGciOiJIUzI1NiJ9.aGVsbG8.UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A'
A compact JWS is constructed by protected header, payload and a private key. In the above
example, protected
is the “protected header” part, “hello” is the payload part, and
“secret” is a plain private key.
Deserialization¶
Calling jws.deserialize_compact()
to extract and verify the compact
serialization with a public key.
from joserfc import jws
from joserfc.jwk import OctKey
text = "eyJhbGciOiJIUzI1NiJ9.aGVsbG8.UYmO_lPAY5V0Wf4KZsfhiYs1SxqXPhxvjuYqellDV5A"
key = OctKey.import_key("secret")
obj = jws.deserialize_compact(text, key)
# obj.protected => {"alg": "HS256"}
# obj.payload => b"hello"
JSON Signature¶
The JWS JSON Serialization represents digitally signed or MACed content as a JSON object. This representation is neither optimized for compactness nor URL-safe.
An example of a JSON serialization:
{
"payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signatures": [
{
"protected": "eyJhbGciOiJSUzI1NiJ9",
"header": {"kid":"2010-12-29"},
"signature": "cC4hiUPoj9Eetdgtv3hF80EGrhuB__dzERat0XF9g2VtQgr9PJbu3XOiZj5RZmh7AAuHIm4Bh-0Qc_lF5YKt_O8W2Fp5jujGbds9uJdbF9CUAr7t1dnZcAcQjbKBYNX4BAynRFdiuB--f_nZLgrnbyTyWzO75vRK5h6xBArLIARNPvkSjtQBMHlb1L07Qe7K0GarZRmB_eSN9383LcOLn6_dO--xi12jzDwusC-eOkHWEsqtFZESc6BfI7noOPqvhJ1phCnvWh6IeYI2w9QOYEUipUTI8np6LbgGY9Fs98rqVt5AXLIhWkWywlVmtVrBp0igcN_IoypGlUPQGe77Rw"
},
{
"protected": "eyJhbGciOiJFUzI1NiJ9",
"header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
"signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
}
]
}
Serialization¶
You can call jws.serialize_json()
to construct a JSON JWS serialization:
import json
from joserfc import jws
from joserfc.jwk import KeySet
members = [
{
"protected": {"alg": "RS256"},
"header": {"kid": "2010-12-29"},
},
{
"protected": {"alg": "ES256"},
"header": {"kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"},
},
]
payload = b'{"iss":"joe",\r\n "exp":1300819380,\r\n "http://example.com/is_root":true}'
with open("your-private-jwks.json") as f:
data = json.load(f)
# this key set SHOULD contains kid of "2010-12-29"
# and "e9bc097a-ce51-4036-9562-d2ade882db0d"
private_key_set = KeySet.import_key_set(data)
value = jws.serialize_json(members, payload, private_key_set)
#: this ``value`` is a dict which looks like the example above
The JSON JWS serialization is constructed by members, payload and private key. A member is a combination of protected header and public header:
member = {
"protected": {"alg": "RS256"},
"header": {"kid": "2010-12-29"},
}
The protected
header will be base64 encoded in the JSON serialization, together with
the payload to sign a signature for the member:
SIGNATURE INPUT =
BASE64URL(UTF8(JWS Protected Header)) || '.' ||
BASE64URL(JWS Payload)
SIGNATURE =
BASE64URL(SignMethod(SIGNATURE INPUT, Private Key))
In the above example, we passed a jwk.KeySet
as the private key parameter, the
jws.serialize_json()
will find the correct key in the key set by kid
.
Deserialization¶
Calling jws.deserialize_json()
to extract and verify the JSON
serialization with a public key.
with open("your-public-jwks.json") as f:
data = json.load(f)
# the public pair of your previous private key set
public_key_set = KeySet.import_key_set(data)
# value is the generated by above code
obj = jws.deserialize_json(value, public_key_set)
# => assert obj.payload == payload
General and Flattened¶
There are two types of JSON JWS serializations, “general” and “flattened”. The above example is a General JSON Serialization. A Flattened JSON Serialization contains only one member. Compair the bellow examples:
{
"payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"protected": "eyJhbGciOiJFUzI1NiJ9",
"header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
"signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
}
{
"payload": "eyJpc3MiOiJqb2UiLA0KICJleHAiOjEzMDA4MTkzODAsDQogImh0dHA6Ly9leGFtcGxlLmNvbS9pc19yb290Ijp0cnVlfQ",
"signatures": [
{
"protected": "eyJhbGciOiJFUzI1NiJ9",
"header": {"kid":"e9bc097a-ce51-4036-9562-d2ade882db0d"},
"signature": "DtEhU3ljbEg8L38VWAfUAqOyKAM6-Xx-F4GawxaepmXFCgfTjDxw5djxLa8ISlSApmWQxfKTUJqPP3-Kg6NU1Q"
}
]
}
You can pass a member dict to construct a flattened serialization; and a list of members to construct a general serialization:
member = {
"protected": {"alg": "ES256"},
"header": {"kid": "e9bc097a-ce51-4036-9562-d2ade882db0d"},
}
# flattened
jws.serialize_json(member, payload, private_key)
# general
jws.serialize_json([member], payload, private_key)
The returned value from deserialize_json
is an object of
jws.GeneralJSONSignature
or jws.FlattenedJSONSignature
,
you can tell if the signature is flattened or general with obj.flattened
:
Changed in version 0.6.0: jws.JSONSignature
is seperated to GeneralJSONSignature
and FlattenedJSONSignature
.
obj = jws.deserialize_json(data, public_key)
if obj.flattened:
print("Flattened JSON Serialization")
else:
print("General JSON Serialization")
Algorithms¶
joserfc.jws
module supports algorithms from RFC7518, RFC8037,
and RFC8812. Here lists all the algorithms joserfc.jws
supporting:
Algorithm name |
Description |
Recommended |
---|---|---|
none |
No digital signature or MAC performed |
No |
HS256 |
HMAC using SHA-256 |
YES |
HS384 |
HMAC using SHA-384 |
No |
HS512 |
HMAC using SHA-512 |
No |
RS256 |
RSASSA-PKCS1-v1_5 using SHA-256 |
YES |
RS384 |
RSASSA-PKCS1-v1_5 using SHA-384 |
No |
RS512 |
RSASSA-PKCS1-v1_5 using SHA-512 |
No |
ES256 |
ECDSA using P-256 and SHA-256 |
YES |
ES384 |
ECDSA using P-384 and SHA-384 |
No |
ES512 |
ECDSA using P-521 and SHA-512 |
No |
PS256 |
RSASSA-PSS using SHA-256 and MGF1 with SHA-256 |
No |
PS384 |
RSASSA-PSS using SHA-384 and MGF1 with SHA-384 |
No |
PS512 |
RSASSA-PSS using SHA-512 and MGF1 with SHA-512 |
No |
EdDSA |
Edwards-curve Digital Signature |
No |
ES256K |
ECDSA using secp256k1 curve and SHA-256 |
No |
Algorithm not allowed¶
The serialization and deserialization methods on joserfc.jws
module accept
an algorithms
parameter for specifying the allowed algorithms. By default,
those serialize
and deserialize
methods will ONLY allow recommended
algorithms defined by RFCs. With non recommended algorithms, you may encounter
the below error.
>>> from joserfc import jws
>>> from joserfc.jwk import OctKey
>>> key = OctKey.import_key("secret")
>>> jws.serialize_compact({"alg": "HS384"}, b"payload", key)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "$/joserfc/jws.py", line 99, in serialize_compact
alg: JWSAlgModel = registry.get_alg(header["alg"])
File "$/joserfc/rfc7515/registry.py", line 57, in get_alg
raise ValueError(f'Algorithm of "{name}" is not allowed')
ValueError: Algorithm of "HS384" is not allowed
joserfc
does support HS384
, but this algorithm is not recommended by
specifications, developers MUST explict specify the supported algorithms
either by the algorithms
parameter, or registry
parameter.
>>> from joserfc import jws
>>> from joserfc.jwk import OctKey
>>> key = OctKey.import_key("secret")
>>> jws.serialize_compact({"alg": "HS384"}, b"payload", key, algorithms=["HS384"])
'eyJhbGciOiJIUzM4NCJ9.cGF5bG9hZA.TJEvlp74g89hNRNGNZxCQvB7YDEAWP5vFAjgu1O9Qr5BLMj0NtvbxvYkVYPGp-xQ'
Developers can also apply the registry
parameter to resolve this issue. Here is an example
of using Registry.
>>> from joserfc import jws
>>> from joserfc.jwk import OctKey
>>> key = OctKey.import_key("secret")
>>> registry = jws.JWSRegistry(algorithms=["HS384"])
>>> jws.serialize_compact({"alg": "HS384"}, b"payload", key, registry=registry)
'eyJhbGciOiJIUzM4NCJ9.cGF5bG9hZA.TJEvlp74g89hNRNGNZxCQvB7YDEAWP5vFAjgu1O9Qr5BLMj0NtvbxvYkVYPGp-xQ'
Unencoded Payload Option¶
The unencoded payload option, defined in RFC7797, allows the payload of a JWS (JSON Web Signature) to remain unencoded, without using base64 encoding.
To enable this option, you need to set the b64
header parameter to false
in the JWS header.
To utilize the unencoded payload option in joserfc, you must import the
serialize and deserialize methods from joserfc.rfc7797
.
Here are examples demonstrating the usage of the b64
option:
from joserfc.rfc7797 import serialize_compact, deserialize_compact
from joserfc.jwk import OctKey
key = OctKey.import_key("secret")
protected = {"alg": "HS256", "b64": False, "crit": ["b64"]}
value = serialize_compact(protected, "hello", key)
# => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19.hello.mdPbZLtc3tqQ6NCV1pKF-qfEx-3jtR6rv109phKAc4I'
deserialize_compact(value, key)
Note
The crit
MUST be present with "b64"
in its value set when
b64
is in the header.
Since the payload is not base64 encoded, if the payload contains non urlsafe characters, the compact serialization will detach the payload:
from joserfc.rfc7797 import serialize_compact, deserialize_compact
from joserfc.jwk import OctKey
key = OctKey.import_key("secret")
protected = {"alg": "HS256", "b64": False, "crit": ["b64"]}
value = serialize_compact(protected, "$.02", key)
# => 'eyJhbGciOiJIUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..GbtzAD3Cwe6snTZnaAxapwQz5QftEz7agx_6aMtZ4w0'
# since the payload is detached, you need to specify the
# payload when calling deserialize_compact
deserialize_compact(value, key, payload="$.02")
There are also methods for JSON serialization: serialize_json
and
deserialize_json
.