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

feat(hash): Add PBKDF2 as password hashing algorithm #1774

Merged
merged 24 commits into from
Oct 25, 2021

Conversation

sawadashota
Copy link
Contributor

Related issue(s)

#1659

Checklist

Further Comments

Copy link
Member

@zepatrik zepatrik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks pretty good already. I am not sure though if this is the intended way discussed in the issue. This does not allow to update imported hashes but just adds PBKDF2 as an additional hasher. The password upgrade process might actually be implemented on top, so we might actually need it as is.
We could have something along the lines of

hashers:
  bcrypt:
    cost: 10
  pbkdf2:
    upgrade_to: bcrypt

in the config, which would mean that all pbkdf2 hashes will be upgraded once checked.

driver/config/.schema/config.schema.json Outdated Show resolved Hide resolved
driver/config/.schema/config.schema.json Outdated Show resolved Hide resolved
@sawadashota
Copy link
Contributor Author

@zepatrik Thank you for your super fast review!
I have fixed it to use PBKDF2 as just compare hash for existing credential and upgrade hashing algorithm after authenticated.

And I remove configuration for PBKDF2 because we can upgrade verified and not primary hashing algorithm automatically.

@codecov
Copy link

codecov bot commented Sep 23, 2021

Codecov Report

Merging #1774 (30b1b5c) into master (dc36fda) will decrease coverage by 0.69%.
The diff coverage is 76.59%.

❗ Current head 30b1b5c differs from pull request most recent head 0baeccb. Consider uploading reports for the commit 0baeccb to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1774      +/-   ##
==========================================
- Coverage   74.85%   74.16%   -0.70%     
==========================================
  Files         291      267      -24     
  Lines       14963    13242    -1721     
==========================================
- Hits        11201     9821    -1380     
+ Misses       2960     2757     -203     
+ Partials      802      664     -138     
Impacted Files Coverage Δ
driver/config/config.go 81.16% <ø> (-0.54%) ⬇️
hash/hasher_argon2.go 72.72% <0.00%> (-7.28%) ⬇️
selfservice/strategy/password/login.go 72.72% <47.36%> (-11.59%) ⬇️
hash/hasher_pbkdf2.go 81.81% <81.81%> (ø)
hash/hash_comparator.go 83.72% <89.47%> (+7.25%) ⬆️
hash/hasher_bcrypt.go 88.88% <100.00%> (+1.38%) ⬆️
text/message_system.go 0.00% <0.00%> (-100.00%) ⬇️
text/message_recovery.go 63.04% <0.00%> (-36.96%) ⬇️
text/message_validation.go 64.70% <0.00%> (-35.30%) ⬇️
ui/node/attributes.go 27.58% <0.00%> (-33.29%) ⬇️
... and 95 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update dc36fda...0baeccb. Read the comment docs.

@sawadashota sawadashota marked this pull request as draft September 23, 2021 09:03
@sawadashota sawadashota marked this pull request as ready for review September 23, 2021 09:36
@sawadashota
Copy link
Contributor Author

Added tests.

Copy link
Member

@zepatrik zepatrik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking pretty good now 👍
One big thing missing now is documentation. I am also not 💯 sure how that should look like, maybe you can start and we discuss it then?

@@ -68,6 +71,12 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow)
return nil, s.handleLoginError(w, r, f, &p, errors.WithStack(schema.NewInvalidCredentialsError()))
}

if !s.d.Hasher().IsSameAlgorithm([]byte(o.HashedPassword)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check does not make sense here. The clear-text password does not contain the algorithm. - you fixed that already 😉

In the long term, we have to make it configurable which hash alg should be upgraded to which one. And some, like PBKDF2, will just be upgraded by default, as they are only intended for import.
For this PR it would be sufficient to just auto-upgrade PBKDF2 without a config option. So adding a check here that returns whether a hash is auto-upgrade (currently only PBKDF2), should be the way to go IMO.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Made it configurable!

One big thing missing now is documentation

Hmm... currently, Kratos doesn't have ability to import user credentials now.
So we have 2 choices to document currently.

Which do you think better?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are nice :) It would be awesome to be able to import users - this has been something the community has asked for for a long time. However, I think it's fine with documenting that this is coming in the future for now in this PR

@seremenko-wish
Copy link
Contributor

hashers:
  bcrypt:
    cost: 10
  pbkdf2:
    upgrade_to: bcrypt

What's the point of the upgrade_to option under a specific algorithm? By default, hashing system works in compatibility mode, which means that all known password hashing types are supported during the hash validation process. So if you are going to migrate identities with pbkdf2 hashes, you could use those hashes and never migrate them. But if you want to gradually migrate to a different algorithm during login and setting flows, it makes sense to have one boolean setting hashers.migrate that would force migration to hashers.algorithm

@sawadashota
Copy link
Contributor Author

Sounds good!
Then configuration goes to like...

hashers:
  algorithm: bcrypt
  migrate: true
  bcrypt:
    cost: 10

@aeneasr @zepatrik Do you have any concerns?

@zepatrik
Copy link
Member

zepatrik commented Sep 30, 2021

Sounds reasonable to me, only thing would be that you might want to specify a per-alg migrate flag.
So e.g.

hashers:
  algorithm: bcrypt
  bcrypt:
    cost: 10
  pbkdf2:
    migrate: true
  argon2:
    migrate: false

which would keep argon2 hashes, but migrate pbkdf2. Not sure if this is actually a use case, but I can imagine you could safe some resources with that strategy.

But a general migrate flag is probably good for now.

@aeneasr
Copy link
Member

aeneasr commented Sep 30, 2021

I would enforce migration. We don't want to stick with old PKBDF2 hashes in the DB forever but slowly migrate everyone over :)

@seremenko-wish
Copy link
Contributor

I also think we have to force passwords migration, maybe even do not provide an option not to migrate.

As I separate topic, I wanted to discuss a format of hash. Because we already use the PHC standard for password hashes, we have to stick with it https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md. So hash for this algorithm should have a format:

$pbkdf2-<digest>$i=<iterations>$<salt>$<hash>

Example here https://www.npmjs.com/package/@phc/pbkdf2#phc-string-format

@aeneasr
Copy link
Member

aeneasr commented Oct 1, 2021

Ok, that makes a ton of sense @seremenko-wish - thank you for the input

@sawadashota
Copy link
Contributor Author

For now, I want to clarify what is configurable to enforce migration.
Is it from only pbkdf2 or all non-primary algorithms?
I think the later is simple if it is enough.

hashers:
  algorithm: bcrypt
  bcrypt:
    cost: 10
  # Cannot configure for pbkdf2. Auto-upgrade to primary algorithm.
  #pbkdf2:
  #  migrate: true
  argon2:
    migrate: false # Is this configurable like this, leave it or auto-upgrade to primary algorithm?

@aeneasr
Copy link
Member

aeneasr commented Oct 1, 2021

I would remove the migrate option. We always migrate to the globally configured hasher (hashers.algorithm). So:

hashers:
  algorithm: bcrypt
  bcrypt:
    cost: 10
  pbkdf2:
    # not sure if this needs any configuration?
  argon2:
    memory: ...
    # ....

@sawadashota sawadashota force-pushed the feat-1659 branch 2 times, most recently from bc887f9 to 713b1c3 Compare October 4, 2021 10:17
@sawadashota
Copy link
Contributor Author

I fixed to always migrate to the globally configured hasher.

@sawadashota
Copy link
Contributor Author

Resolved conflict.

@aeneasr
Copy link
Member

aeneasr commented Oct 12, 2021

Awesome, I will take a look at this now!

@aeneasr
Copy link
Member

aeneasr commented Oct 12, 2021

@sawadashota what do you think about this comment from @seremenko-wish ?

I also think we have to force passwords migration, maybe even do not provide an option not to migrate.

As I separate topic, I wanted to discuss a format of hash. Because we already use the PHC standard for password hashes, we have to stick with it https://github.com/P-H-C/phc-string-format/blob/master/phc-sf-spec.md. So hash for this algorithm should have a format:

$pbkdf2-<digest>$i=<iterations>$<salt>$<hash>

Example here https://www.npmjs.com/package/@phc/pbkdf2#phc-string-format

I think it would make sense to follow the PHC format here.

@sawadashota
Copy link
Contributor Author

I think it makes sense but I have a small question about this format $pbkdf2-<digest>$i=<iterations>$<salt>$<hash>.
The param name looks defined by simonepri/phc-pbkdf2 repository because PHC doesn't define for PBKDF2.
https://github.com/simonepri/phc-pbkdf2#phc-string-format

Referring to RFC2898, I expect iteration parameter is described as c.
Then format will be $pbkdf2-<digest>$c=<iterations>$<salt>$<hash>.

@aeneasr
Copy link
Member

aeneasr commented Oct 12, 2021

@seremenko-wish maybe you can chime in here?

@seremenko-wish
Copy link
Contributor

@sawadashota I see that number of iterations is defined as c in RFC, but I haven't found a module that uses c for it yet. But I found a bunch of packages that use i instead:

https://passlib.readthedocs.io/en/stable/lib/passlib.hash.pbkdf2_digest.html#passlib.hash.pbkdf2_sha1 does not use named parameters at all :/

As there is no defined standard for this specific algorithm, it would be up to you to decide what named parameter to use for iterations.

@aeneasr
Copy link
Member

aeneasr commented Oct 12, 2021

@sawadashota I see that number of iterations is defined as c in RFC

As far as I understand, the RFC itself does not touch on the PHC format. Might make sense though to use c for consistency as the RFC will probably rarely change?

I also did some research and found that Auth0 uses the format from https://github.com/simonepri/phc-pbkdf2#phc-string-format . I assume Auth0 probably uses that library internally.

I also found a few more docs but unfortunately no concrete guidance:

So yeah basically the question is:

  1. Do we want to stick with the nomenclautur from the RFC - so c and dkLen or
  2. stick to the nomenclautur established in the one NPM library + one or two other resources around this with i and l?

@sawadashota
Copy link
Contributor Author

@aeneasr Could you review PR?

@aeneasr aeneasr added this to the v0.8.0-alpha.1 milestone Oct 19, 2021
@aeneasr
Copy link
Member

aeneasr commented Oct 19, 2021

It's part of 0.8.0 now so due to be released before the ory summit

Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks ready to be merged from a code level! Just one thing :)

@@ -68,6 +71,12 @@ func (s *Strategy) Login(w http.ResponseWriter, r *http.Request, f *login.Flow)
return nil, s.handleLoginError(w, r, f, &p, errors.WithStack(schema.NewInvalidCredentialsError()))
}

if !s.d.Hasher().IsSameAlgorithm([]byte(o.HashedPassword)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both are nice :) It would be awesome to be able to import users - this has been something the community has asked for for a long time. However, I think it's fine with documenting that this is coming in the future for now in this PR

@aeneasr
Copy link
Member

aeneasr commented Oct 19, 2021

Failing e2e tests are probably my fault

@sawadashota
Copy link
Contributor Author

Added Hashed Password Format section to https://www.ory.sh/kratos/docs/next/concepts/credentials/username-email-password !
Documents for BCrypt and Argon2 are also added but if they are unnecessary, I will remove them.

Copy link
Member

@aeneasr aeneasr left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job! Thank you! :)

@aeneasr aeneasr merged commit 33cc7e0 into ory:master Oct 25, 2021
@sawadashota sawadashota deleted the feat-1659 branch October 26, 2021 01:06
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

Successfully merging this pull request may close these issues.

4 participants