Asymmetric cryptography signers
It is common to divide cryptography in three categories:
- Symmetric cryptography (also called symmetric-key algorithms):
- Clear text is passed to an algorithm that uses a key for encrypting and decrypting to generate cipher text;
- Examples include: AES (Advanced Encryption Standard), Blowfish, DES (Data Encryption Standard), etc;
- In the BEAM ecosystem, it is represented by the
:crypto.supports()[:ciphers]
.
- Asymmetric cryptography (also called public-key cryptography):
- It uses a pair of keys where one is private (only known to the owner) and the other is public;
- Clear text is passed to an algorithm plus the private key and generates a cipher text;
- The cipher text can be verified by the public key, that is, we can check that it was generated by the private key.
- Hash cryptography (also called one-way functions):
- Clear text is passed to a function that may or may not receive a key to generate irreversible cipher text.
In the JWA specification we have algorithms that use both symmetric and asymmetric cryptography functions. Let's see them (taken from the specification):
------- | ------------------------------- | -------------- |
HS256 | HMAC using SHA-256 | Required |
HS384 | HMAC using SHA-384 | Optional |
HS512 | HMAC using SHA-512 | Optional |
RS256 | RSASSA-PKCS1-v1_5 using | Recommended |
SHA-256 | ||
RS384 | RSASSA-PKCS1-v1_5 using | Optional |
SHA-384 | ||
RS512 | RSASSA-PKCS1-v1_5 using | Optional |
SHA-512 | ||
ES256 | ECDSA using P-256 and SHA-256 | Recommended+ |
ES384 | ECDSA using P-384 and SHA-384 | Optional |
ES512 | ECDSA using P-521 and SHA-512 | Optional |
PS256 | RSASSA-PSS using SHA-256 and | Optional |
MGF1 with SHA-256 | ||
PS384 | RSASSA-PSS using SHA-384 and | Optional |
MGF1 with SHA-384 | ||
PS512 | RSASSA-PSS using SHA-512 and | Optional |
MGF1 with SHA-512 | ||
------- | ------------------------------- | -------------- |
(removed the none algorithm we don't support**)
Besides the HSxxx family of algorithms, all others use asymmetric cryptography.
Using asymmetric algorithms
What is nice about public-key cryptography is that you don't need to "share a key" between parties. The one with the private key can generate tokens and publish his public key online so that anybody can check that token was generated by the owner of the private key.
In Joken that usually means a private key can be used to call Joken.encode_and_sign
and Joken.verify
. If only have the public key, then you can only call Joken.verify
.
There are some specifications that use this: OpenID Connect being one very famous. It specifies that authentication servers must publish their keys in JWK (JSON Web Key) format on a public URL. Here are some examples:
- Google: https://www.googleapis.com/oauth2/v3/certs
- Microsoft: https://login.microsoftonline.com/common/discovery/v2.0/keys
We have a Joken Hook that can fetch these keys, turn them into signers and verify tokens. This is JokenJwks.
KEY formats
Different algorithms use different key formats. For example, RSA is based on huge prime numbers arithmetic. Its keys contain data about these prime numbers and other variables in the RSA function specification.
Elliptic curve cryptography is based on elliptic curve arithmetic. Its keys represent the function that generates infinite points on a curve specification.
Because of those differences, we usually have a container for each key type. There is a specification for how to represent these keys in JSON format. That is the JSON Web Key specification. It has a JSON format for each type of key.
We recommend reading on the appendix examples for each type of key. Here is the appendix of the JSON Web Key specification with examples for public and private RSA and EC keys.
Let's see some examples on parsing asymmetric RSA keys with Joken:
RSxxx keys
This algorithm uses the RSASSA-PKCS1-v1_5 that uses SHA2 hash algorithms. The base for this algorithm is the RSA public key standard. So, to use this algorithm we need a pair of RSA keys. There are many ways to generate these keys in different environments and is outside the scope of this library. Here is one of these ways just for an example:
➜ joken git:(master) openssl genrsa -out mykey.pem 4096
Generating RSA private key, 4096 bit long modulus (2 primes)
.............++++
...........................................................................................................++++
This will generate a private RSA key in PEM (privacy enhanced mail) format on a file named mykey.pem. With this key we can sign and verify a token signature.
To use it with Joken we can call one of the Joken.Signer.create
variants:
With the RAW PEM contents:
signer = Joken.Signer.create(%{"pem" => pem_contents})
With the pem file in the configuration:
use Mix.Config config :joken, my_rsa_key: [ signer_alg: "RS256", key_pem: """ -----BEGIN RSA PRIVATE KEY----- MIICWwIBAAKBg ... -----END RSA PRIVATE KEY----- """ ] signer = Joken.Signer.parse_config(:my_rsa_key)
With the key in JWK format:
# example of key parameters from https://tools.ietf.org/html/rfc7517#appendix-A.1 # This is for demonstration purposes. We don't allow those parameters in the map like # "alg", "kid", "kty". Although they are part of the JWK Set specification. key = %{ "alg" => "RS256", "e" => "AQAB", "kid" => "2011-04-29", "kty" => "RSA", "n" => "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" } signer = Joken.Signer.create(key)
With the key in JWK format on the config:
use Mix.Config config :joken, my_rsa_public_key: [ signer_alg: "RS256", key_map: %{ "alg" => "RS256", "e" => "AQAB", "kid" => "2011-04-29", "kty" => "RSA", "n" => "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMstn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbISD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw" } ] signer = Joken.Signer.parse_config(:my_rsa_public_key)
Other algorithms
Joken has a test suite that runs all supported algorithms. Please have a look at other algorithms from our test suite.
Also, check :erlang-jose
documentation about keys: https://github.com/potatosalad/erlang-jose/blob/master/examples/KEY-GENERATION.md