Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

oauth2: BCrypt hashs inputs (passwords) of maximum of 72 bytes, limiting OAuth 2.0 Client Secret length #1438

Closed
aeneasr opened this issue May 17, 2019 · 7 comments

Comments

@aeneasr
Copy link
Member

aeneasr commented May 17, 2019

Originally reported by @leonfancy and removed to comply with security guidelines from our contribution guidelines which disallow disclosure of potential security vulnerabilities on GitHub.

Describe the bug

Only the begining 72 characters of client_secrect is used to authentication

Reproducing the bug

Steps to reproduce the behavior:

  1. Create a client with a client_secrect which has 128 charaters. For example: 525348e77144a9cee9a7471a8b67c50ea85b9e3eb377a3c1a3a23db88f9150eefe76e6a339fdbc62b817595f53d72549d9ebe36438f8c2619846b963e9f43a94
  2. Do a standard authentication code grant flow.
  3. In the last step POST to /oauth2/token, send the begining 72 characters of client_secrect, which is 525348e77144a9cee9a7471a8b67c50ea85b9e3eb377a3c1a3a23db88f9150eefe76e6a3. It pass the authentication, and return an access_token.
$ hydra token client --client-id foo --client-secret 525348e77144a9cee9a7471a8b67c50ea85b9e3eb377a3c1a3a23db88f9150eefe76e6a3 --endpoint http://localhost:4444

Server configuration

docker run -d --name hydra --rm -p 4444:4444 -p 4445:4445 \
    --network hydraguide \
    -e SECRETS_SYSTEM=abedfghijklmnopoqrsfdkfjdsa1234fdsafd \
    -e LOG_LEVEL=debug \
    -e OAUTH2_EXPOSE_INTERNAL_ERRORS=true \
    -e STRATEGIES_ACCESS_TOKEN=jwt \
    -e TTL_ACCESS_TOKEN=6h \
    -e URLS_CONSENT=http://127.0.0.1:8080/consent \
    -e URLS_LOGIN=http://127.0.0.1:8080/login \
    -e URLS_SELF_ISSUER=http://127.0.0.1:4444 \
    -e DSN=$DSN \
    -e WEBFINGER_JWKS_BROADCAST_KEYS=hydra.jwt.access-token \
   oryd/hydra:v1.0.0-rc.9 serve all --dangerous-force-http

Expected behavior

Must reject the request if the client_secrect is incorrect.

Environment

  • Version: v1.0.0-rc.9
  • Environment: Docker
@aeneasr
Copy link
Member Author

aeneasr commented May 17, 2019

This is reproducible with:

$ hydra clients create --id baz --secret 525348e77144a9cee9a7471a8b67c50ea85b9e3eb377a3c1a3a23db88f9150eefe76e6a339fdbc62b817595f53d72549d9ebe36438f8c2619846b963e9f43a94 --endpoint http://localhost:4445 --token-endpoint-auth-method client_secret_post --grant-types client_credentials

$ hydra token client --client-id baz --client-secret 525348e77144a9cee9a7471a8b67c50ea85b9e3eb377a3c1a3a23db88f9150eefe76e6a3 --endpoint http://localhost:4444

We are using BCrypt to hash secrets of clients. BCrypt has an upper limit:

Finally, the key argument is a secret encryption key, which can be a user-chosen password of up to 56 bytes (including a terminating zero byte when the key is an ASCII string). - Source

Secrets longer than what is supported by BCrypt will therefore be truncated/discarded by the BCrypt algorithm.

@aeneasr
Copy link
Member Author

aeneasr commented May 17, 2019

/cc @leonfancy

@leonfancy
Copy link

Will hydra fix this limitation?

One workaround is: Before Bcrypt, use SHA or other HASH function on the client_secrect to generate a shorter length hashed string, and then pass it to bcrypt hash function.

@aeneasr
Copy link
Member Author

aeneasr commented May 17, 2019

From a standpoint of how secure the password hash is that wouldn't make sense. Generating a collision (or brute-forcing) a 56 byte password with the default BCrypt cost of 10 would take longer than the universe is old.

Changing the hashing algorithm in a backwards incompatible way is not an option. If at all, we could add other hashing algorithms and make this configurable.

Hashing an input before passing it down to another hash function will not increase security. In fact, choosing the wrong hash method will actually decrease it. This is especially true for SHA:

  • SHA 256 of i transforms i to 32 bytes. This is less than the allowed 56 bytes.
  • SHA 512 of i transforms i to 64 bytes. This is more than the allowed 56 bytes but adds an unpredicatbility regarding what collisions we might get.

Another option is to disallow secrets longer than 56 bytes. However, limiting secret length is considered bad practice as it makes password managers that use very long secrets unusable.

@aeneasr
Copy link
Member Author

aeneasr commented May 17, 2019

This is now a documented limitation: ory/docs#144

@aeneasr aeneasr added the docs label May 17, 2019
@aeneasr aeneasr changed the title Only the begining 72 characters of client_secrect is used to authentication oauth2: BCrypt hashs inputs (passwords) of maximum of 56byte length, limiting OAuth 2.0 Client Secret length May 17, 2019
@aeneasr aeneasr changed the title oauth2: BCrypt hashs inputs (passwords) of maximum of 56byte length, limiting OAuth 2.0 Client Secret length oauth2: BCrypt hashs inputs (passwords) of maximum of 73 byte length, limiting OAuth 2.0 Client Secret length May 17, 2019
@aeneasr aeneasr changed the title oauth2: BCrypt hashs inputs (passwords) of maximum of 73 byte length, limiting OAuth 2.0 Client Secret length oauth2: BCrypt hashs inputs (passwords) of maximum of 73 bytes, limiting OAuth 2.0 Client Secret length May 17, 2019
@aeneasr aeneasr changed the title oauth2: BCrypt hashs inputs (passwords) of maximum of 73 bytes, limiting OAuth 2.0 Client Secret length oauth2: BCrypt hashs inputs (passwords) of maximum of 72 bytes, limiting OAuth 2.0 Client Secret length May 17, 2019
@aeneasr
Copy link
Member Author

aeneasr commented May 21, 2019

I did some research on other algorithms. PBKDF2 supports a maximum of 64 bytes (depending on its core hash function))�. An alternative to bcrypt is scrypt which however never got any real traction because of several shortcomings. The algorithm chosen by the PHC is argon2 which is also what systems that deal with user-generated passwords (thisismypassword) diverge to. The idea for making the cracking of user-generated passwords hard is that those passwords are usually shared by the user among many sites, so cracking them (and thus gaining access to e.g. Google) should be as hard as possible.

OAuth 2.0 Clients however are usually computer generated and the credentials are generated by default from a cryptographic random function (e.g. /dev/urandom). They are usually not-very-confidential™ as third parties can create them to their liking (e.g. using OpenID Connect Dynamic Client Discovery) or your bespoke Developer Portal UI. Usually, they do not have any serious privileges but always act on behalf of someone else. In fact, most systems choose to store OAuth 2.0 Secrets as plain text, which is why you can see the Client Secret in your Web UI (including e.g. GitHub).

Secure mechanisms for authentication of OAuth 2.0 Clients are supported by ORY Hydra. You can, for example, use RS256 to authenticate OAuth 2.0 Clients, completely eliminating the need for a password.

As this limitation has now been documented and for the reasons above, I'm closing this as a known limitation.

TL;DR

  • Using BCrypt for OAuth 2.0 Client Secrets is already paranoid, GitHub and similar services store those credentials in plain text.
  • OAuth 2.0 Client Secrets should be generated by ORY Hydra or from a random source. 72 Byte entropy is way more than enough.
  • For "more secure"™ means of OAuth 2.0 Client Authentication, use RS256 or some other JWT-supported algorithm with a keylength of 2048,4096, or more bit.

@aeneasr
Copy link
Member Author

aeneasr commented Jun 13, 2019

This is documented now

@aeneasr aeneasr closed this as completed Jun 13, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants