ueberauth / guardian

Elixir Authentication
MIT License
3.44k stars 381 forks source link

Recommendation on a "sane default" for the type of secret key? #152

Closed thbar closed 7 years ago

thbar commented 8 years ago

Hello there! Coming from a Rails & "Devise" background, I'm rewriting an app and start using Guardian.

The first "newbie" question that comes up is : wow, that's a lot of different keys types (backed by jose), but how do I choose?

I'm going to do my homework, document myself & make due diligence, and will come up with conclusions, but I wondered if some basic orientation could be provided in the readme in order to help a newcomer choose that.

Thoughts?

hassox commented 8 years ago

@thbar I'm going to ask @potatosalad to jump in. I personally use HS512 which is just a simple sha signing. This means that the JWT can be read but not tampered with. I'm hoping @potatosalad can provide more information on the more exotic key types.

potatosalad commented 8 years ago

Short Answer

A cryptographically secure randomly generated 16 byte (or 128 bit) symmetric oct key for use with signature algorithm HS256 should be sufficient for many HTTP bearer authentication use cases.

## Generate a 128-bit oct JWK
JOSE.JWK.generate_key({:oct, 16}) |> JOSE.JWK.to_map |> elem(1)
# %{"k" => "5Fn8i7r5cRWZW_yyr9Flkg", "kty" => "oct"}

## Configure Guardian to use HS256 and our generated JWK
config :guardian, Guardian,
  allowed_algos: ["HS256"],
  secret_key: %{"k" => "5Fn8i7r5cRWZW_yyr9Flkg", "kty" => "oct"}

Anything larger than that (like using HS512 that @hassox mentioned with a 64 byte key), is also perfectly acceptable and could potentially change the time required for a brute force attack from decades to centuries.

If you want public-key cryptography, I provide my personal opinion at the very bottom of this comment, but it's much more difficult to recommend one as the "sane default".

Long Answer

There are really only two major types of keys to choose from for the signature operations: symmetric and asymmetric.

In simpler terms:

The main difference in behavior between the two is:

Relating the above to SSL/TLS, certificates are signed using an asymmetric secret key and distributed to clients with the embedded public key. This means (for example) your browser can verify that a certificate has been signed by Google. A symmetric key would be useless in this case because anyone who had the key would be able to sign a certificate as if they were Google.

Symmetric cryptography

The following algorithms use a symmetric key (or an oct key type), which is really just a sequence of bytes.

The number at the end of the signature algorithm specifies the output length of the signature in bits. The signature is always the same for the same given secret key and message.

The recommended key length is mentioned in RFC 2104:

The key for HMAC can be of any length (keys longer than B bytes are first hashed using H). However, less than L bytes is strongly discouraged as it would decrease the security strength of the function. Keys longer than L bytes are acceptable but the extra length would not significantly increase the function strength. (A longer key may be advisable if the randomness of the key is considered weak.)

So for HS256 the B is 64 bytes and the L is 32 bytes, which means any key between 32 and 64 bytes would be recommended. For HS384, any key between 48 and 128 bytes. For HS512, any key between 64 and 128 bytes.

That said, any key 16 bytes (or 128 bits) or greater should be sufficient for most general purpose use. A 128-bit key would currently take a very long time to brute force (many years or decades on current technology). The HMAC group is the only group of signature algorithms in jose that are likely quantum computer resistant.

Asymmetric cryptography (or public-key cryptography)

There are three major types of asymmetric key types for signature algorithm use.

  1. ECDSA - Elliptic Curve Digital Signature Algorithm
  2. EdDSA - Edwards-curve Digital Signature Algorithm
  3. RSA - RSA signature padding schemes for PKCS#1.5 and PSS

Note: EdDSA in jose is still in draft form and the exact specification may change.

For ECDSA, there are three algorithms which are tied to a specific curve for the EC key type:

  1. ES256 - ECDSA using P-256 and SHA-256
  2. ES384 - ECDSA using P-384 and SHA-384
  3. ES512 - ECDSA using P-521 and SHA-512

For EdDSA, there are two algorithms which are tied to a specific curve for the OKP key type:

  1. Ed25519 - EdDSA using Ed25519
  2. Ed448 - EdDSA using Ed448

For RSA, there are six algorithms which use different padding schemes (PKCS#1.5 versus PSS) for the RSA key type:

  1. RS256 - RSASSA-PKCS1-v1_5 using SHA-256
  2. RS384 - RSASSA-PKCS1-v1_5 using SHA-384
  3. RS512 - RSASSA-PKCS1-v1_5 using SHA-512
  4. PS256 - RSASSA-PSS using SHA-256 and MGF1 with SHA-256
  5. PS384 - RSASSA-PSS using SHA-384 and MGF1 with SHA-384
  6. PS512 - RSASSA-PSS using SHA-512 and MGF1 with SHA-512
My Opinion

As of today (2016-05-19), my recommendation for asymmetric cryptography would be ES512.

My near future recommendations are Ed25519 and Ed448. They are still in draft algorithms, but in my opinion, they are near perfect asymmetric key types for HTTP bearer authentication.

Some of the cryptography community is working on public-key cryptography algorithms which are more quantum computer resistant, such as XMSS and Hash-based signatures which may finally provide a "sane default" for asymmetric keys.

If you want to use RSA, I would recommend the PS256, PS384, and PS512 algorithms over the other RSA algorithms. The PKCS#1.5 padding scheme (RS256, RS384, and RS512) does not provide a high enough level of security generally should be replaced by the PSS padding scheme.

Opinionated Examples
#############
### ES512 ###
#############

## Generate a JWK for use with ES512
JOSE.JWS.generate_key(%{"alg" => "ES512"}) |> JOSE.JWK.to_map |> elem(1)
# %{"alg" => "ES512", "crv" => "P-521", "d" => "AVDuOBokicU-yXj4zNwrK-29vmUvmOBNxHB-7_PgRO6e3VKTl-wuUmPEQnHtG_GYoC0cUHAJtpGlaeGF1mIRpeSk", "kty" => "EC", "use" => "sig", "x" => "ANT4yNLVHCeYtQOpjbhuXnoB69C4VoWLESxxbnEKt8W8BTL_7kdqUcCMBxxQvPhrf3fmliosAxb1BcspPtV4aofP", "y" => "AAPlYHP2qGpubv_qzYbNvMOxJUOuypaVeEYV6NnEtWpW2jHupr5xMaINDXgpN1CDwddZxQ-WpE4jQEl8onXD9_su"}

## Configure Guardian to use ES512 and our generated JWK
config :guardian, Guardian,
  allowed_algos: ["ES512"],
  secret_key: %{
    "alg" => "ES512",
    "crv" => "P-521",
    "d" => "AVDuOBokicU-yXj4zNwrK-29vmUvmOBNxHB-7_PgRO6e3VKTl-wuUmPEQnHtG_GYoC0cUHAJtpGlaeGF1mIRpeSk",
    "kty" => "EC",
    "use" => "sig",
    "x" => "ANT4yNLVHCeYtQOpjbhuXnoB69C4VoWLESxxbnEKt8W8BTL_7kdqUcCMBxxQvPhrf3fmliosAxb1BcspPtV4aofP",
    "y" => "AAPlYHP2qGpubv_qzYbNvMOxJUOuypaVeEYV6NnEtWpW2jHupr5xMaINDXgpN1CDwddZxQ-WpE4jQEl8onXD9_su"
  }

#############
### Ed448 ###
#############

## Generate a JWK for use with Ed448
## Note: you will need to run JOSE.crypto_fallback(true) or have erlang-libdecaf installed
JOSE.JWS.generate_key(%{"alg" => "Ed448"}) |> JOSE.JWK.to_map |> elem(1)
# %{"alg" => "Ed448", "crv" => "Ed448", "d" => "ArlWJD8C8B4yyBhDDSSpjjJgC9ASFSiPACajGpJFd1wLdfWNQdwvUdHUN-ZnRBBGUfebZgvb-ZTe", "kty" => "OKP", "use" => "sig", "x" => "OabW8I7n2qidiuShu_LlW7ntrG99_Q7d4Vps4tnxBF-ROnRZ4IQn2diPdMrX8vi0xCtDhGeUq-6A"}

## Configure Guardian to use Ed448 and our generated JWK
config :guardian, Guardian,
  allowed_algos: ["Ed448"],
  secret_key: %{
    "alg" => "Ed448",
    "crv" => "Ed448",
    "d" => "ArlWJD8C8B4yyBhDDSSpjjJgC9ASFSiPACajGpJFd1wLdfWNQdwvUdHUN-ZnRBBGUfebZgvb-ZTe",
    "kty" => "OKP",
    "use" => "sig",
    "x" => "OabW8I7n2qidiuShu_LlW7ntrG99_Q7d4Vps4tnxBF-ROnRZ4IQn2diPdMrX8vi0xCtDhGeUq-6A"
  }

#############
### PS256 ###
#############

## Generate a JWK for use with PS256
## Note: you will need to run JOSE.crypto_fallback(true) since RSA PSS is not natively supported as of OTP 18
JOSE.JWS.generate_key(%{"alg" => "PS256"}) |> JOSE.JWK.to_map |> elem(1)
# %{"alg" => "PS256", "d" => "jDoVruE_9ilASyMyBqk-9EXTjnoaJwL6ruK0iOaftJw0i7AQC841jFuAbNpRIIfKfNqq7GKYWC9KDriRS0hbicUUwUoN__PmiSxMD6pthCGyjSL-_Sa5BkuhHkUnP5jtcsK86UJrVkLWf96m-R0Ks-Bm0xCEVww5kZH_uPw-JiTJpxVuwF9dSbjFEbNz_kxn0efD1toGGJ-GJXVlkt-n84buUzUmEhvQwq9j34a9ZEG_mNQ3NZoq2jZ3OUF_z7BQeYawKuuSWxlJ3sRVuVU75uQ-QGyUR__K-EmIFSGFhp3n9iFe2u7pGXpihlR27BdVVj8HJCb8QqbfbvUYfcDDxQ", "dp" => "l9as4VlICMtCImS0oBqNKjpDI1-34OFqsI0UWfmGSE0e9FTz-sVpNt6Yd97Y2SKCbJZu7DPqnh3C3LrdoTqYTrUao5s5MxzasMT1kT6xpcYbQEq5FeXl_eXClec_19SToBsRUa5Cp35T3k4jpX3io8SQ0kLdNrPvCDw4X_kjuEU", "dq" => "SY_FtN75qEl1MMbfdzTBnoKokk6lc1gkGt9K3BhSrB83sFuujswVc4GaKdSmzHrncC0PbRpcnl8DhLDl9xxkECC0V1gkWyXOHnUqyNpqyFIZNHTOE-WPwrLJ1o26JoVA3fLY_YEirqOcIGP5OvjdCRE_vE9Wtf2OZ9DmYowPasU", "e" => "AQAB", "kty" => "RSA", "n" => "zzsAdivyQfcixV06tn4YfxJ9JDLHqNbkInWQBQ1POayRlfW2Kr4CT_UVKKk-HOc5TxMilBm6J8WAVP0YsFRdyIdBoagIO1qX1q8jUpqQXaaqk0T3do0nbWbBc-M843d-h3Brq-xUiejm99OoyQzX1ViweoPbB4Zoh7N8MV4YVvu6DDlofd0xrDpPlmmqAocDLvRRyv7wshqT08FKJoJj9MQNnzu-RHN9bHbDFRECvYEL_sxcO_dOHEfVW9U2TmaiAUa9B1p_QkzvWtQO8ttzLz_o7ujZMxfbnJzZi2aOg6_4Zftc_1HhxEqKI6GmsGFvUI4yuFN8NfQ6laUMAn2qJQ", "p" => "-7VhtutTjz69dMVC7CQW4FTWS7q1PQu-YLCT_xsHt7RPbJeuxTL2LA7CfIFbpzsDqMS4fUd-gR9phuUDQFQWSLCA1wXEszpAdD6r5fht9LssN4Uuy6ivOVuazLalA52zBj8vAOqyroGo-WUlYwT2BoxJqDDmLFNJDpc-ioGphe8", "q" => "0sN9Piqlfsrmh7v090AjUB0WmIMqICkXBYWgXvEOcTyif9jzeHmieiiT2-eQdA4ZxoZXXVY31SdkyiCqNET6B4eJjA8VR1xBrRVj4Bg3ASbDTVuCezHYHZMh-ITgVINt061wE_1n1aGeDMJ8ZUlOV2kk7YX0STJvN2-mrwWBhSs", "qi" => "LownZt59Rjp8vpH9rzkXLLvJchc46mBeGVSKmfcortkh4PTkpv0pFU83RsVuLjjWAIwTVncMqXebLraxPI4m-Eo4ysQGFq6fLHYKHJGdUOZc15O4iZHz7A11PixHoD_DPQvjD3_Nu71dTY18-FNKUwcYErgnPessP2JcE1SgEzA", "use" => "sig"}

## Configure Guardian to use PS256 and our generated JWK
config :guardian, Guardian,
  allowed_algos: ["PS256"],
  secret_key: %{
    "alg" => "PS256",
    "d" => "jDoVruE_9ilASyMyBqk-9EXTjnoaJwL6ruK0iOaftJw0i7AQC841jFuAbNpRIIfKfNqq7GKYWC9KDriRS0hbicUUwUoN__PmiSxMD6pthCGyjSL-_Sa5BkuhHkUnP5jtcsK86UJrVkLWf96m-R0Ks-Bm0xCEVww5kZH_uPw-JiTJpxVuwF9dSbjFEbNz_kxn0efD1toGGJ-GJXVlkt-n84buUzUmEhvQwq9j34a9ZEG_mNQ3NZoq2jZ3OUF_z7BQeYawKuuSWxlJ3sRVuVU75uQ-QGyUR__K-EmIFSGFhp3n9iFe2u7pGXpihlR27BdVVj8HJCb8QqbfbvUYfcDDxQ",
    "dp" => "l9as4VlICMtCImS0oBqNKjpDI1-34OFqsI0UWfmGSE0e9FTz-sVpNt6Yd97Y2SKCbJZu7DPqnh3C3LrdoTqYTrUao5s5MxzasMT1kT6xpcYbQEq5FeXl_eXClec_19SToBsRUa5Cp35T3k4jpX3io8SQ0kLdNrPvCDw4X_kjuEU",
    "dq" => "SY_FtN75qEl1MMbfdzTBnoKokk6lc1gkGt9K3BhSrB83sFuujswVc4GaKdSmzHrncC0PbRpcnl8DhLDl9xxkECC0V1gkWyXOHnUqyNpqyFIZNHTOE-WPwrLJ1o26JoVA3fLY_YEirqOcIGP5OvjdCRE_vE9Wtf2OZ9DmYowPasU",
    "e" => "AQAB",
    "kty" => "RSA",
    "n" => "zzsAdivyQfcixV06tn4YfxJ9JDLHqNbkInWQBQ1POayRlfW2Kr4CT_UVKKk-HOc5TxMilBm6J8WAVP0YsFRdyIdBoagIO1qX1q8jUpqQXaaqk0T3do0nbWbBc-M843d-h3Brq-xUiejm99OoyQzX1ViweoPbB4Zoh7N8MV4YVvu6DDlofd0xrDpPlmmqAocDLvRRyv7wshqT08FKJoJj9MQNnzu-RHN9bHbDFRECvYEL_sxcO_dOHEfVW9U2TmaiAUa9B1p_QkzvWtQO8ttzLz_o7ujZMxfbnJzZi2aOg6_4Zftc_1HhxEqKI6GmsGFvUI4yuFN8NfQ6laUMAn2qJQ",
    "p" => "-7VhtutTjz69dMVC7CQW4FTWS7q1PQu-YLCT_xsHt7RPbJeuxTL2LA7CfIFbpzsDqMS4fUd-gR9phuUDQFQWSLCA1wXEszpAdD6r5fht9LssN4Uuy6ivOVuazLalA52zBj8vAOqyroGo-WUlYwT2BoxJqDDmLFNJDpc-ioGphe8",
    "q" => "0sN9Piqlfsrmh7v090AjUB0WmIMqICkXBYWgXvEOcTyif9jzeHmieiiT2-eQdA4ZxoZXXVY31SdkyiCqNET6B4eJjA8VR1xBrRVj4Bg3ASbDTVuCezHYHZMh-ITgVINt061wE_1n1aGeDMJ8ZUlOV2kk7YX0STJvN2-mrwWBhSs",
    "qi" => "LownZt59Rjp8vpH9rzkXLLvJchc46mBeGVSKmfcortkh4PTkpv0pFU83RsVuLjjWAIwTVncMqXebLraxPI4m-Eo4ysQGFq6fLHYKHJGdUOZc15O4iZHz7A11PixHoD_DPQvjD3_Nu71dTY18-FNKUwcYErgnPessP2JcE1SgEzA",
    "use" => "sig"
  }
thbar commented 8 years ago

Thank you so much for the detailed answer!

octosteve commented 8 years ago

Thanks for such a detailed response. Is there a sample app somewhere? I tried to set up an app using ES512 like in the example and am getting

    ** (FunctionClauseError) no function clause matching in :base64url.encode/1
        src/base64url.erl:22: :base64url.encode(%{"alg" => "ES512", "crv" => "P-521", "d" => "mhEZkkY6eUJKEVl4Qnjfnik3GZmvPsiQEJC3-9f-jiM2OSHPTTYkOzPfjWbOqhb5JARUaJzXL9YyqHJBDxM_VB8", "kty" => "EC", "use" => "sig", "x" => "AALDyto5SKJ73a7uqLjKLwoCjUvn6Ldpcade5FCOt1zsl9hJGIHVGlXN9ApEsAMo5QmmOlQ5s4C4Fhi2PvvHb9bc", "y" => "AUZGWn4yuFxXKuqYEds0cm31A0LxY5v7Bmur3LoJaykQrXxd-GdcX4ZH6_hot6VnVWBlsDcHjCBXjxLg0_uJLNM6"})

I'm guessing it's trying to pass that whole hash to base encode it.

Rest of config for reference

config :guardian, Guardian,
  allowed_algos: ["ES512"],
  issuer: "BlogBackend",
  ttl: { 30, :days },
  secret_key: %{
    "alg" => "ES512",
    "crv" => "P-521",
    "d" => "mhEZkkY6eUJKEVl4Qnjfnik3GZmvPsiQEJC3-9f-jiM2OSHPTTYkOzPfjWbOqhb5JARUaJzXL9YyqHJBDxM_VB8",
    "kty" => "EC", "use" => "sig",
    "x" => "AALDyto5SKJ73a7uqLjKLwoCjUvn6Ldpcade5FCOt1zsl9hJGIHVGlXN9ApEsAMo5QmmOlQ5s4C4Fhi2PvvHb9bc",
    "y" => "AUZGWn4yuFxXKuqYEds0cm31A0LxY5v7Bmur3LoJaykQrXxd-GdcX4ZH6_hot6VnVWBlsDcHjCBXjxLg0_uJLNM6"
  },
  serializer: BlogBackend.GuardianSerializer
potatosalad commented 8 years ago

@StevenNunez Which version of guardian are you using? Support for key types other than oct was added in version 0.11.1.

I put together an example project potatosalad/guardian-example-app using the same config you provided with the dependency version set to guardian 0.11.1.

Here is a sign and verify example from the project while running iex -S mix:

## Sign
{:ok, signed_binary, jwt_map} = Guardian.encode_and_sign("test")
# {:ok,
#  "eyJhbGciOiJFUzUxMiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJ0ZXN0IiwiZXhwIjoxNDY2Njg3Mzg0LCJpYXQiOjE0NjQwOTUzODQsImlzcyI6IkJsb2dCYWNrZW5kIiwianRpIjoiMWU4OTg2M2YtZGI2My00MmJmLWE5YjYtOTY1MTU1NDk2ODA1IiwicGVtIjp7fSwic3ViIjoidGVzdCIsInR5cCI6InRva2VuIn0.AYMPeB1lgx90_79xBoKRJbKXGcIo5w8sS4ArJ3A0gZFzHf1cNlME1JHqbhzShcHsb6qH94n6gvqswgQgnLGKd2stAY8IIokRVcD8DTFwcyf6gf7Y-CO3LTOxWEsE6p2DbMaenTBdJsTe8Qhx41wRta8DOunXs33mAtApdwBIZzS9yxiJ",
#  %{"aud" => "test", "exp" => 1466687384, "iat" => 1464095384,
#    "iss" => "BlogBackend", "jti" => "1e89863f-db63-42bf-a9b6-965155496805",
#    "pem" => %{}, "sub" => "test", "typ" => "token"}}

## Verify
{:ok, ^jwt_map} = Guardian.decode_and_verify(signed_binary)
# {:ok,
#  %{"aud" => "test", "exp" => 1466687384, "iat" => 1464095384,
#    "iss" => "BlogBackend", "jti" => "1e89863f-db63-42bf-a9b6-965155496805",
#    "pem" => %{}, "sub" => "test", "typ" => "token"}}

Note: With ES512, the signature value will change each time a message is signed. So even with the the exact same message and key, your signed_binary may not match the one provided in the example above.

Is there any reason to not just use the result of mix phoenix.gen.secret? I got it working that way.

That's probably a POLA violation (or bug) that you have uncovered in jose. You are no longer using ES512, but actually HS512 due to the key type being oct. Since both key types use SHA-512, the sign and verify functions related to the ECDSA algorithms are allowing the oct key type to pass through.

I will post an update here once the jose library has been fixed.

octosteve commented 8 years ago

@potatosalad Thank you!

rlopzc commented 8 years ago

@potatosalad Hello, i have been using ES512 as you recommended, and my guardian set up was working until i updated jose from 1.7.9 to 1.8.0 my key is like this:

%{"alg" => "ES512", "crv" => "P-521",
  "d" => "AZ-WCoPA2Kt-Jio-DFuOChf0SBinAYgklUFIdVLmlZwjHmRUntVVatkBGnGyjyUWC0aAqwPdwf5s1ubXtRWT5st3",
  "kty" => "EC", "use" => "sig",
  "x" => "AUgiiCT_ycsyZXpHXoYA4s-VpObDQZ99Sqi3vXXDH3lOoSNWg9QiusZ_p-z3OqQ-6wLFZJWORu6COWOF7ROCAEtQ",
  "y" => "APmuazEPeBWB_C8xrY0xZSoawsYcL8CycvaOHwiYVh_lHUCVAltLzofi-jM-TxsFzmjUofnwI-gfQCYiZQz1b9lB"}

And the error from the console when i try to log in:

2016-08-31T23:57:17.281619+00:00 app[web.1]: Request: POST /sessions
2016-08-31T23:57:17.281620+00:00 app[web.1]: ** (exit) an exception was raised:
2016-08-31T23:57:17.281620+00:00 app[web.1]:     ** (ErlangError) erlang error: {:not_supported, ["P-521", :HS512]}
2016-08-31T23:57:17.281628+00:00 app[web.1]:         (jose) src/jose_jwk_kty_ec.erl:355: :jose_jwk_kty_ec.jws_alg_to_digest_type/2
2016-08-31T23:57:17.281629+00:00 app[web.1]:         (jose) src/jose_jwk_kty_ec.erl:186: :jose_jwk_kty_ec.sign/3
2016-08-31T23:57:17.281630+00:00 app[web.1]:         (jose) src/jose_jws.erl:311: :jose_jws.sign/4
2016-08-31T23:57:17.281631+00:00 app[web.1]:         (jose) src/jose_jwt.erl:171: :jose_jwt.sign/3
2016-08-31T23:57:17.281631+00:00 app[web.1]:         lib/guardian.ex:303: Guardian.encode_claims/1
2016-08-31T23:57:17.281632+00:00 app[web.1]:         lib/guardian.ex:94: Guardian.encode_from_hooked/1
2016-08-31T23:57:17.281632+00:00 app[web.1]:         lib/guardian/plug.ex:118: Guardian.Plug.sign_in/4
potatosalad commented 8 years ago

Hey @RomarioLopezC,

This is due to the release of jose 1.8.0 which is more strict about which algorithms are allowed to be used with specific keys as mentioned in potatosalad/erlang-jose#24.

It should be resolved if you add the following line to your guardian configuration:

allowed_algos: ["ES512"]

Let me know if you have any other issues. Also, it may go without saying, but you may need to change your secret key if that was your actual secret key you posted and not just an example.