:description: Usage of OctKey, RSAKey, ECKey, and OKPKey. .. _jwk: JSON Web Key ============ .. module:: joserfc.jwk :noindex: A JSON Web Key (JWK) is a JavaScript Object Notation (JSON) data structure that represents a cryptographic key (via RFC7517_). .. _RFC7517: https://www.rfc-editor.org/rfc/rfc7517 Examples -------- Here are some examples on importing and generating keys: .. code-block:: python from joserfc import jwk # import a JWK dict jwk.import_key({ 'kty': 'oct', 'k': 'eW91ci1zZWNyZXQta2V5', 'use': 'sig', }) # import a oct key jwk.import_key("a-random-string-as-key", "oct") # same as: jwk.OctKey.import_key("a-random-string-as-key") # generate a key jwk.generate_key("RSA", 2048, {"use": "sig"}) # same as: jwk.RSAKey.generate_key(2048, {"use": "sig"}) .. _OctKey: OctKey ------ An :class:`OctKey` is a symmetric key defined in `RFC7518 section 6.4 `_. Create an "oct" key ~~~~~~~~~~~~~~~~~~~ You can generate an ``OctKey`` with the :meth:`OctKey.generate_key` method: .. code-block:: python from joserfc.jwk import OctKey key_size = 256 # in bit size, 256 equals 32 bytes key = OctKey.generate_key(key_size) Import an "oct" key ~~~~~~~~~~~~~~~~~~~ You can import an ``OctKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import OctKey OctKey.import_key("a-random-string-as-key") OctKey.import_key(b"a-random-bytes-as-key") OctKey.import_key({ "kty": "oct", "k": "eW91ci1zZWNyZXQta2V5", }) When importing a key, you can add extra parameters into a key: .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("your-secret-key", {"use": "sig"}) >>> key.as_dict() {'k': 'eW91ci1zZWNyZXQta2V5', 'use': 'sig', 'kty': 'oct'} .. _RSAKey: RSAKey ------ An :class:`RSAKey` is an asymmetric key defined in `RFC7518 section 6.3 `_. It represents RSA keys. Generate an "RSA" key ~~~~~~~~~~~~~~~~~~~~~ You can generate an "RSA" key with a given key size (in bit): .. code-block:: python from joserfc.jwk import RSAKey key_size = 2048 key = RSAKey.generate_key(key_size) Import an "RSA" key ~~~~~~~~~~~~~~~~~~~ You can import an ``RSAKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import RSAKey pem_file = """ -----BEGIN PUBLIC KEY----- MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAm0tWm31IQ3zYU27bk/NZ 3wMJOJ+Moska3WqnptWyiVR+p/qCBlV18NUSwshoctTkETi8+HIhOjUPb0WRvQV0 YcpsqBVdSuPZ3m4Q+uX/rudAoDKHJ6B7vwjfeg4w9aT/YF+Zi61tEy1c15rHKyXA HjSQGzIasOiXK1eSssim6Exx+caRL0/vWV8+0QICmEBVJiJyfDB4O3WXKac+QsI3 LM7ZjWqQFdvx3o1v7sDycz0zdpk4qEK7hEHUsYIsyYHb70iKSkiuo3nqq2HUHklW y322djy/IqEq03KWuePRUZdPTDzlx5qyKpVLpMswYporngvXKpMTCal5HYfAGuYS MuOAVa1oL1gX8W+N4+XNrVCHSCh1JHjnO2qUT6em/HJ2gERj3kZDDfE6UXVjAw2i US2lP+GEim3AdUQ1jTO27Vjvuv+rNk7UjL8iDW1THlvYI9AeQnqtTTBib2b5+k6a 8AzSPhMX/F7WP9hf0NUbkYyrJ7zRfERKqLrwpZu83PRWclnB6afPIZcN58uc+4J5 516Ryk6PUawbBHj6zfSIDEuwKj71ki+t0GHaG4RO9QFk75ArsHWrRZNQhELBVep/ ohwl4vscRMQFgdwdzZN8ZaaJRPFih7B+YiwIhuxpAF9fPrETa6UGoBK6MlWKE6EZ i5YRKx6rVWvFfMWAV3Tx9uECAwEAAQ== -----END PUBLIC KEY----- """ RSAKey.import_key(pem_file) RSAKey.import_key({ "kty": "RSA", "kid": "bilbo.baggins@hobbiton.example", "use": "sig", "n": "n4EPtAOCc9AlkeQHPzHSt...", "e": "AQAB", "d": "bWUC9B-...", "q": "uKE2dh-...", "dp": "B8PV...", "dq": "CLDm...", "qi": "3PiFU4..." }) .. _ECKey: ECKey ----- An :class:`ECKey` is an asymmetric key defined in `RFC7518 section 6.2 `_. It represents Elliptic Curve [DSS] keys. Generate an "EC" key ~~~~~~~~~~~~~~~~~~~~ You can generate an "EC" key with the given curve: .. code-block:: python from joserfc.jwk import ECKey key = ECKey.generate_key("P-256") The "crv" values that :class:`ECKey` supports: - ``P-256`` via RFC7518 - ``P-384`` via RFC7518 - ``P-521`` via RFC7518 - ``secp256k1`` via RFC8812 .. hint:: It is ``P-521``, not ``P-512``, it is not a typo. Import an "EC" key ~~~~~~~~~~~~~~~~~~ You can import an ``ECKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import ECKey pem_file = """ -----BEGIN EC PRIVATE KEY----- MHcCAQEEIBnRS4Tf1PY6Jb7QOwAM7OWUOMJTBenEWRvGBCGgctBfoAoGCCqGSM49 AwEHoUQDQgAE3r15c+Yd+0GXKysfWtwkqF7k12ylNE9LdfRP4TfkUcJSQXyGQjcx U8E81rOHjo+9xv2e64n4X6pC3yuP+pX4eA== -----END EC PRIVATE KEY----- """ ECKey.import_key(pem_file) ECKey.import_key({ "kty": "EC", "crv": "P-256", "x": "WKn-ZIGevcwGIyyrzFoZNBdaq9_TsqzGl96oc0CWuis", "y": "y77t-RvAHRKTsSGdIYUfweuOvwrvDD-Q3Hv5J0fSKbE", "d": "Hndv7ZZjs_ke8o9zXYo3iq-Yr8SewI5vrqd0pAvEPqg" }) Derive an "EC" Key ~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.6.0 ``joserfc`` provides deterministic key derivation helpers for EC key. This method allows applications to derive a *stable* JWK from an application secret (for example, ``SECRET_KEY`` in a web framework), while still producing valid cryptographic keys for use in JWS/JWE. .. code-block:: python from joserfc.jwk import ECKey ECKey.derive_key("your-secret-key", "P-256") ECKey.derive_key("your-secret-key", "P-256", kdf_name="HKDF") Supported key derivation functions: - **HKDF** (recommended) - **PBKDF2** (password-based, may require higher iteration counts) .. warning:: Deterministic keys are useful for applications that want a stable default JWK, but **they should not be used where random key generation is required**. .. _OKPKey: OKPKey ------ An :class:`OKPKey` is an asymmetric key defined in RFC8037_ CFRG Elliptic Curve Diffie-Hellman (ECDH) and Signatures in JSON Object Signing and Encryption (JOSE). .. _RFC8037: https://www.rfc-editor.org/rfc/rfc8037#section-2 Generate an "OKP" key ~~~~~~~~~~~~~~~~~~~~~ You can generate an "OKP" key with the given curve: .. code-block:: python from joserfc.jwk import OKPKey key = OKPKey.generate_key("Ed25519") :class:`OKPKey` accepts "crv" values of ``Ed25519``, ``Ed448``, ``X25519``, and ``X448``. Import an "OKP" key ~~~~~~~~~~~~~~~~~~~ You can import an ``OKPKey`` from string, bytes and a JWK (in dict). .. code-block:: python from joserfc.jwk import OKPKey pem_file = """ -----BEGIN PRIVATE KEY----- MEcCAQAwBQYDK2VxBDsEOaVsPKMXOBfq9aHlDEaMlBY+FR63hwrINHa2X74uHXUr 3/VXE8eMhrr8stXn41CQKqVmFEeL5Uj5Gg== -----END PRIVATE KEY----- """ OKPKey.import_key(pem_file) OKPKey.import_key({ "kty": "OKP", "crv": "Ed25519", "x": "t-nFRaxyM5DZcpg5lxiEeJcZpMRB8JgcKaQC0HRefXU", "d": "gUF17HCe-pbN7Ej2rDSXl-e7uSj7rQW5u2dNu0KINP0", "kid": "5V_IcL-iX5IbaNz9vg0CjXtWLZiJ94-ESnHI-HN1L2Y" }) Derive an "OKP" Key ~~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.6.0 Just like above ``ECKey``, ``joserfc`` provides a ``OKPKey.derive_key`` method to derive a *stable* JWK. .. code-block:: python from joserfc.jwk import OKPKey OKPKey.derive_key("your-secret-key", "Ed25519") OKPKey.derive_key("your-secret-key", "Ed25519", kdf_name="HKDF") Supported key derivation functions: - **HKDF** (recommended) - **PBKDF2** (password-based, may require higher iteration counts) Key Set ------- A JWK Set is a JSON object that represents a set of JWKs. An example of a JWK Set: .. code-block:: none {"keys": [ { "kty":"EC", "crv":"P-256", "x":"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", "y":"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", "use":"enc", "kid":"1" }, { "kty":"RSA", "n": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx...", "e":"AQAB", "alg":"RS256", "kid":"2011-04-29" } ]} Create a key set ~~~~~~~~~~~~~~~~ You can create a key set with a given set of keys: .. code-block:: python from joserfc.jwk import KeySet key_set = KeySet([rsa_key1, rsa_key2, ec_key1]) Or, you can generate a key set for a certain "kty": .. code-block:: python key_set = KeySet.generate_key_set("EC", "P-256", count=4) Import a key set ~~~~~~~~~~~~~~~~ An example about importing JWKS from a local file: .. code-block:: python import json with open("your-jwks.json") as f: data = json.load(f) key_set = KeySet.import_key_set(data) An example about importing JWKS from a URL: .. code-block:: python import requests resp = requests.get("https://example.com/jwks.json") key_set = KeySet.import_key_set(resp.json()) Key methods ----------- .. _thumbprint: ``thumbprint`` ~~~~~~~~~~~~~~ Calling this method will generate the thumbprint of the JWK, per RFC7638. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.generate_key() >>> key.thumbprint() 'DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' You can also use the ``jwk.thumbprint`` method: .. code-block:: python from joserfc import jwk jwk.thumbprint({ 'kty': 'oct', 'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU', }) # 'DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' ``thumbprint_uri`` ~~~~~~~~~~~~~~~~~~ .. versionadded:: 1.2.0 Calling this method will generate the JWK thumbprint URI, per RFC9278. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.generate_key() >>> key.thumbprint_uri() 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' You can also use the ``jwk.thumbprint_uri`` method: .. code-block:: python from joserfc import jwk jwk.thumbprint_uri({ 'kty': 'oct', 'k': 'sTBpI_oCHSyW-n0exSwhzNHwU9FGRioPauxWA84bnRU', }) # 'urn:ietf:params:oauth:jwk-thumbprint:sha-256:DCdRGGDKvhAJgmVlCp6tosc2T9ELtd30S_15vn8bhrI' .. _ensure_kid: ``ensure_kid`` ~~~~~~~~~~~~~~ Call this method to make sure the key contains a ``kid``. If the key has no ``kid``, generate one with the above ``.thumbprint`` method. .. code-block:: python >>> from joserfc.jwk import OctKey >>> key = OctKey.import_key("your-secret-key") >>> key.kid None >>> key.ensure_kid() >>> key.kid 'IGfff6pETIMRgwAOHtzIQ_Sflgv5wzWl6SYZlcZ_JDU' ``as_dict`` ~~~~~~~~~~~ Dump a key or key set into dict format, which can be used to convert to JSON: .. code-block:: python data = key.as_dict(private=False) # dump as a public key # data = key.as_dict(private=True) # dump as a private key with open("my-key.json", "w") as f: json.dump(data, f) ``as_pem`` ~~~~~~~~~~ Dump an asymmetric key into PEM format (in bytes): .. code-block:: python # text = key.as_pem(private=False) # dump as a public key text: bytes = key.as_pem(private=True) # dump as a private key with open("my-key.pem", "w") as f: f.write(text) ``as_der`` ~~~~~~~~~~ Dump an asymmetric key into DER format (in bytes): .. code-block:: python # text = key.as_der(private=False) # dump as a public key text: bytes = key.as_der(private=True) # dump as a private key with open("my-key.der", "w") as f: f.write(text) Utilities --------- The ``jwk`` module offers a means to dynamically import and generate keys. Import keys ~~~~~~~~~~~ .. versionadded:: 1.1.0 The :meth:`import_key` can choose the correct key type automatically when importing a JWK in dict: .. code-block:: python from joserfc import jwk data = {"kty": "oct", "k": "..."} key = jwk.import_key(data) # returns a OctKey data = {"kty": "RSA", ...} key = jwk.import_key(data) # returns a RSAKey data = {"kty": "EC", ...} key = jwk.import_key(data) # returns a ECKey data = {"kty": "OKP", ...} key = jwk.import_key(data) # returns a OKPKey If the key is in bytes or string, not dict, developers SHOULD specify the key type manually: .. code-block:: python data = b"---- BEGIN RSA PRIVATE KEY ----\n..." key = jwk.import_key(data, "RSA") Generate keys ~~~~~~~~~~~~~ .. versionadded:: 1.1.0 The :meth:`generate_key` can generate a key with all the supported key types. For ``oct`` and ``RSA`` the parameters in this method: .. code-block:: python from joserfc import jwk # (key_type: str, size: int, parameters: Optional[dict], private: bool=True) key = jwk.generate_key("oct", 256) key = jwk.generate_key("RSA", 2048, {"use": "sig"}) For ``EC`` and ``OKP`` keys, the parameters are: .. code-block:: python # (key_type: str, crv: str, parameters: Optional[dict], private: bool=True) key = jwk.generate_key("EC", "P-256") key = jwk.generate_key("OKP", "Ed25519") Options ------- The ``import_key`` and ``generate_key`` methods available on ``OctKey``, ``RSAKey``, ``ECKey``, ``OKPKey``, and ``jwk`` classes have an optional ``parameters`` parameter. This ``parameters`` allows you to provide a dict that includes additional key parameters to be included in the JWK. Some of the standard (registered) header fields are: - ``kty``: Key Type, it is automatically added - ``use``: Public Key Use, "sig" or "enc" - ``key_ops``: Key Operations, allowed operations of this key - ``alg``: Algorithm, allowed algorithm of this key - ``kid``: Key ID, a string of the key ID When using ``import_key`` and ``generate_key``, developers can pass the extra key ``parameters``: .. code-block:: python parameters = {"use": "sig", "alg": "RS256", "key_ops": ["verify"]} RSAKey.import_key(data, parameters=parameters) The above ``RSAKey`` then can only be used for ``JWS`` with ``alg`` of ``RS256``, and it can only be used for deserialization (``verify``).