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
Make has_secure_password
pluginable/extendable with different password hashing algorithms
#48993
base: main
Are you sure you want to change the base?
Make has_secure_password
pluginable/extendable with different password hashing algorithms
#48993
Conversation
While BCrypt may be the desirable default in Rails for the foreseeable future, making the mechanism for hashing and verifying passwords in `has_secure_password` extendable/modular is valuable. Developers may opt for different hashing algorithms and can now extend or plug in their own alongside BCrypt in a clean and maintainable way. This also unlocks Rails shipping with more than BCrypt as an available hashing algorithm.
has_secure_password
pluginable/extenable with different password hashing algorithmshas_secure_password
pluginable/extendable with different password hashing algorithms
Co-authored-by: Zeeshan Ishtiaq <4838801+mzishtiaq@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great contribution to the framework and makes Rails more future proof.
What instantly comes to my mind is: When we provide the feature of setting the algorithm which is used for hashing per attribute, should we then also add tooling to make a transition from one hash algorithm to another? Is that something that could be achieved at all (can we now what algorithm was used for a hash)? Probably this is just out of scope for this PR and should be done in a separate PR, but thinking about it should be done beforehand. If the answer is "no, we don't provide tooling" we should at least add some documentation about it. Same for the feature itself.
end | ||
self.min_cost = false | ||
# TODO: is this the best pattern to discover/register password hashing algo classes? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say the "Rails way" would be to make this configurable so one can write something like
config.active_model.secure_password_algorithms << :some_algo
or
config.active_model.secure_password_algorithms << 'SomeAlgo'
The default value of that option would then be just all Algorithms that are supported by default (only BCrypt for now?).
An example of how to add configurable options can be seen in: #41158 (or just search for something like "add config" in the commit history, there are plenty of examples)
@Flixt I think you raise a good point. Migrating from one hash algorithm to another is fraught with security pitfalls and can drastically reduce the overall difficulty to crack passwords, so it's important to get right. To that end, either Rails endeavours to make that decision on behalf of the developer, or the documentation provides guidance that mirrors OWASP's Password Storage Cheat Sheet or security industry's best practices. I may get this slightly wrong, but generally the (most accepted?) approach is:
The goal of the pepper is to prevent "password shucking" and is good defense in depth for very little effort. Note that is just but one approach with pros and cons. Other approaches include forcefully resetting all passwords for users, requiring them to change or re-input their existing password to forcefully update all rows, or maintaining incremental hash changes but not bothering with layering. I'm sure there are also other approaches to peppering that isn't a simple suffixing, such as HMAC with the pepper as the key/secret. I don't have a hard preference, but I think that password hash migration can be a deepish topic with different approaches and trying to shoehorn that into Rails may be too cumbersome. I lean towards documentation as an approach. I agree whatever the outcome, be it providing migration tooling, documentation, or nothing at all it should take place outside this PR. EDIT: I should quickly point out that while most discussions on updating password hashes are centred around migrating off of legacy or insecure hashing algos (e.g. |
I agree migrating from one password hashing algorithm to another can be cumbersome and contain security relevant pitfalls, but I also tend to say that this is exactly something Rails excels at. Making hard stuff less hard :) .. anyway.. I think we should hear some more pros/cons and then decide wether it is worth it to implement tooling around that problem in a separate PR. I personally think that migrating password hashing algorithms is not something that is done every day / very often and that it is not worth it to maintain code for tooling around it to automate the process. We can just describe the process that is needed and link to other sources with correct approaches to the topic. |
Yeah that is generally a very good idea.
That is simple, password hashes have a well defined header (e.g.
Absolutely. The usual method is this: Have exactly one object for creating new passwords (e.g. argon2). Have additional objects for validating other algorithms (e.g. bcrypt). Postel's Law applies here: "be conservative in what you do, be liberal in what you accept from others". So when verifying you just check what type of hash you're dealing with an route it to the right validator (e.g. "this starts with The next step is transitioning the values from old to new. You can do this by using a password_needs_rehash method. When a user logs in and they provide a valid password, you can check if the hash needs upgrading and if so you can recreate it with the new algorithm. That way you transparently upgrade your password database user-by-user. |
That is an approach you could use when transitioning from md5 or sha1 as a password hash, but that's not what we're dealing with here (and personally I find that approach dubious in general). |
This is security, you really don't want users to write homegrown solutions for that sort of thing because they'll get it almost right and therefore get pwned. |
@Leont Can you elaborate on that? |
Sure. I dislike it for three reasons.
|
Motivation / Background
Based on an idea by @rafaelfranca in #41420 (comment).
While BCrypt may be the desirable default password hashing algorithm in Rails for the foreseeable future, making the mechanism for hashing and verifying passwords in
has_secure_password
extendable/modular is valuable.Developers may opt for different hashing algorithms and can now extend or plug in their own alongside BCrypt in a clean and maintainable way. From the referenced thread, there is discourse amongst people on what should be the de facto password algorithm and disagreement on whether Argon2 should be it. Rails should make it easier for the community to progress on best password hashing practices.
This also unlocks Rails shipping with more than BCrypt as an available hashing algorithm.
I could see a world where Rails offers PBKDF2 (for those wanting FIPS 140 compliance), BCrypt (the safe default), and Argon2 (since OWASP recommends it).
Detail
This PR takes BCrypt-specific code in
ActiveModel::SecurePassword
and extracts it out into an objectActiveModel::SecurePassword::BCrypt
. The object adheres to a simple interface (ActiveModel::SecurePassword::Base
) so developers can supplement with different algorithms (or different libraries of the same underlying algo).A user of the Rails framework uses the newly added
:algorithm
option inhas_secure_password
to declare what password hashing algo should be used on a given attribute.Developers can write libraries or even initializers to add different password hashing algorithm support following a very clean interface.
Below is an example of someone opting to use Argon2 using
ruby-argon2
:Additional information
I'm a bit of a novice when it comes to making Rails aware of different files and how to "register" a series of subclasses (some of which could be defined in libraries or the initializers folder) to a part of Rails. Open to whatever pattern is the most appropriate.
Checklist
Before submitting the PR make sure the following are checked:
[Fix #issue-number]