The discussion revolves around migrating existing Discourse users to a new platform, intercoin.app, using Single Sign-On (SSO). EGreg seeks a secure way to import user data, including password hashes and salts, to enable SSO login. RGJ explains that Discourse uses PBKDF2 with 64,000 iterations and provides the relevant code. EGreg asks about an API endpoint for user data, and RGJ suggests direct database access or a custom plugin. EGreg proposes building a plugin to expose user data via an authenticated endpoint, with HMAC for security. simonk recommends using the Data Explorer plugin for exporting data. RGJ suggests extending the existing admin API. EGreg asks about the PBKDF2 iteration count, and RGJ confirms it’s 64,000. EGreg seeks help porting the PBKDF2 algorithm to PHP, and RGJ recommends using PHP’s built-in hash_pbkdf2 function. EGreg thanks RGJ for the solution. EGreg reports success in importing users and asks two questions: 1) whether the password hashing can be split between client and server, and 2) whether Discourse can act as an SSO provider. RGJ dismisses the client-side hashing concern and confirms Discourse can be an SSO provider. EGreg counters that client-side hashing is important for security and suggests a split approach, but RGJ argues it would reduce security and add complexity without significant benefit.
What is the proper way to import all existing Discourse users to intercoin.app from Discourse? Is there some sort of REST endpoint that returns all users with their hashed passwords and salts, among other things? What is the link to the hash algorithm on github? I will have to code on our side, to use the same hash algorithm with the entered password and salt, if our own doesn’t work, in order to let those guys log in. I think #2 is relevant whenever a Discourse user turns on SSO later (like we are) so solving it would help other Discourse users too.
def confirm_password?(password)
return false unless password_hash && salt
self.password_hash == hash_password(password, salt)
end
def hash_password(password, salt)
raise StandardError.new("password is too long") if password.size > User.max_password_length
Pbkdf2.hash_password(password, salt, Rails.configuration.pbkdf2_iterations, Rails.configuration.pbkdf2_algorithm)
end
Excellent! Now what is the HTTP endpoint that I call in order to get all the user info, including password hash and salt?
I imagine that there isn’t one to serve up to the public at large (why make it easier to hack people?) So what can I do? Connect to the MySQL database? Write a Discourse plugin?
I just spoke to our team, and they agree, I’m prepared to pay someone to build a small Discourse plugin that would expose via an endpoint some JSON info about a user, given their email address.
So given an email the plugin will just search user_email table by the mail, then find user_id and grab the user row, and send all the “safe” fields, including the salt.
For extra security, the requests can be signed via HMAC using a shared secret that can be provided to the plugin.
Does anyone want to make this one? Message me or reply here and let me know how to contact you. Hopefully it’s straightforward (a couple SELECTs and a check for HMAC if the secret was set). We’ll read the JSON.
The xorcist library is just a faster string xor, right? What if one of the characters ends up being 0 because ‘a’ was xored with ‘a’ – what happens to that string? Aren’t strings null-terminated?
My goal is to port this to PHP, so anything you can do to help (such as giving me info on how to replicate it in PHP) will be very helpful.
Also what is this line doing? ret.bytes.map { |b| ("0" + b.to_s(16))[-2..-1] }.join("")
$u = hash_hmac('sha256', $password, $salt . pack('N', 1));
$ret = $u = hash_hmac('sha256', $password, $u);
for ($i=2; $i<$iterations; ++$i) {
$u = hash_hmac('sha256', $password, $u);
$ret = ($ret ^ $u);
}
// todo: figure out what RUBY is doing on this last line
Is this close? Can you please fix up this PHP code?
Yes! I was able to create a script that goes through all the Discourse users, and imports them with their passphrase hash into our platform.
Soon, we’ll be able to let anyone who has a Discourse forum add also Events, Videoconferencing, Media, and more, with Discourse living in the “Discuss” tab. You can see the result on https://intercoin.app
Basically turning any Discourse installation into a modern social network a la Facebook. We worked for years on those features and now we want to integrate them tightly with Discourse and Wordpress too. So people can combine Wordpress, Discourse and Qbix and self-host their entire community.
In Qbix, we hash the password on the client at least with sha1(password + userId) before sending it to the server. Even when it’s https. We do it so the server or any MITM NEVER has the password, to re-use it across sites. But, Discourse simply sends the password to the server. So we had to turn off this hashing on the client side. Is it possible to do some iterations of hash_pbkdf2 on the client side, and the rest on the server side? I tried it and it doesn’t seem to line up:
Would it be possible to just use Discourse as an SSO Provider, instead of using our site as the SSO provider? Then hosts of Discourse forums would be even more likely to expand it with Qbix features, since the login would remain exactly the same, and on Discourse’s side. Facebook, Google, and whatever else. Is there any documentation on what kind of information Discourse Connect as an SSO Provider returns to our consumer site? Does it include things like the photo we can download, firstname, lastname, and username at least?
TBH I don’t think Discourse submitting a password over HTTPS is your largest security challenge at this moment.
Sure. I think you get most stuff in the standard user serializer.
But if that’s not enough you can always use the API to grab more information from Discourse.
I would recommend to Discourse to hash things with a salt (userId works) before sending the password over the wire. Why not? It doesn’t have to become incompatible with what you are storing in the database now. Simply do the first 100 iterations in Javascript, then subtract 10 from 64000. You have a custom implementation of it anyway (copied from rails) so you’d just send along an isHashed variable, and if it’s true, then do the “last” 64K-10 steps only.
The user ID is not known before login so that won’t work…
10 iterations are not secure, and 63990 iterations are less secure than 64000 iterations. So although it’s marginal it seems like you are replacing one secure method with two less secure methods and a lot of extra complexity.