Migrated password hashes support

Here is the password migration support plugin:

https://github.com/communiteq/discourse-migratepassword/

The original thread follows.


We do regular forum conversions (yes, we’ll open source the converter once it’s stable enough) and one of the big quirks when migrating to Discourse is the fact that all users have to set a new password, because the password in the original forum is encrypted.

So we thought of the following mechanism.

  • when creating users in the conversion step, we store the original password hash in a custom field
  • when a user logs in for the first time and the password that was entered is incorrect, the login mechanism uses the original hash method of the former forum and calculates and compares the hash with the stored value
  • if there is a match, the password is set to the entered password and the user is logged in. The original hash can now be cleared.

This sounds like a neat plugin :smile:

Two questions:

  • Any comments, objections (security?) and maybe even something that is even better?
  • Can someone give us any pointers about which places to hook and how to store such a custom field?
13 Likes

1.The plugin should probably have its own table To manage the process.

User_id | hash | hash_type | progress_status

2.This kind of plugin will introduce additional overhead for all login. But it is not that bad.
And it may be worth it for the extra convenience to the end user.

3.The plugin should be disabled after the majority of user base finalized their login.

4.Depends on the system you are migrating. You might need to implement different hash algorithms and be aware if “salt” was used.

5.You will have to keep track of migrated users and only perform the additional check on their first login. If the user reset their password the plugin will not get involve in any future login of that user.

I am not sure what is the best practices to introduce custom fields to built in tables on discourse

1 Like

Thanks for your feedback,

  1. hash_type could be a global setting, after all, all users on a certain forum will have the same hash type, progress_status is not needed, since the hash can be cleared after use. So a custom field will do.

  2. no, only for a failed login, and indeed (3) only for the first few days/weeks

Ok - I found the way how to add a custom user field
https://meta.discourse.org/t/custom-user-fields/14956

Now we just need to know how to override the login…

This is reeeeally hacky, barely tested and requires Ruby ≥ 2.0:

https://gist.github.com/Elberet/31ef485a8f723af7c11d

The idea here is that old passwords are stored as salted SHA1 hashes and the import process has stored these in the form "#{salt}:#{password}" in a custom field named import_pass.

4 Likes

You’ve got a rogue rescue and binding.pry in there… :wink:

Whoops… but I did say that it’s hacky. :wink:

1 Like

Nice - I’m going to turn this into a plugin!

Ok, extended this to a neat little plugin that supports vBulletin, MD5 and Wordpress hashes

https://github.com/communiteq/discourse-migratepassword/

Thanks @elberet !

4 Likes

Nice work. Though I’d have extended the code a bit to support an algorithm identifier as part of the password string that would be used to delegate the crypto to a single class instead of trying all available hash functions… :slight_smile:

3 Likes

Yeah, I (of course :smiley: ) actually thought about that, but decided not to, for a few reasons:

  • this would require naming all those non-standard algorithms, introducing a new kind of semi-standard, which requires more thought that you’d expect. For instance, I would be naming the md5(md5(pass) + salt) method vbulletin and two months I’d find out that forum X uses the same algorithm, and we would end up with something very counter-intuitive.
  • ease of parsing. Your gist had a nasty bug in salt, password_hash = self.custom_fields['import_pass'].split(':') because salt can contain a colon. I turned them around and used the limit parameter in this case but this made me decide to treat all crypted password strings as an opaque string.

Did I mention that it was really hacky? :grin:

I actually thought of that case but decided against spending more time on a solution because, after all, both packing the password data into a custom field and parsing that data would be heavily implementation specific. Although in hindsight, packing and unpacking JSON data would have been sufficiently simple to implement. :sweat_smile:

Hm. Didn’t think of that either.

I used this plugin for a migration yesterday and although it was working okay in a test migration, now on the live site it’s failing. :frowning:

They are all md5 hashes but the console shows a problem with BCrypt:

BCrypt::Errors::InvalidHash (invalid hash)
/var/www/discourse/plugins/discourse-migratepassword/gems/2.3.1/gems/bcrypt-3.1.3/lib/bcrypt/password.rb:60:in `initialize' 

Can I disable the use of this gem as I don’t need it? I commented out all lines in plugin.rb that my migration won’t need but that seems to have no effect, the error remains. :thinking:

I’ve pushed a fix.
Can you please upgrade the plugin to latest and try again?

5 Likes

Yes, now it is working again! :smiley: :heart:

3 Likes

Hi everyone,
I have been trying to migrate from vBulletin to Discourse. I was following this link to do that: Importing from vBulletin 4
I updated the import script to copy over the password hashes and salt from my vBulletin DB to Discourse DB. The database has been successfully migrated.

The problem I am having is that the old users are not able to log in due to different password hashing scheme followed by Discourse. So basically copying over those password hashes was futile.

Then I came across this plugin which would convert my old hashes to the ones that Discourse uses.
After installing this plugin, I am still not able to log in using an old user.
I am not sure where to make make the suggested change for this plugin to work:

user = User.find_by(username: 'user')
user.custom_fields['import_pass'] = '5f4dcc3b5aa765d61d8327deb882cf99'
user.save

Can anyone guide me with this?

Thanks in advance.

Hi. I am thankful for this plugin, it’s proving very useful for my Kunena4 migration.

But I ran into problems which were quite hard to understand - only after setting up an IDE and debugging through the code I was able to figure out why my logins weren’t being accepted.

The passwords I was trying were right, and the hashing was also working fine. The problem was with Discourse’s password requirements:

  • I had to go in Admin / Settings / Plugins and check migratepassword allow insecure passwords

  • I had to go in the database and run UPDATE email_tokens SET confirmed = TRUE to bypass the email tokens mechanism (note that my approach was a bit brute force here - if you have existing users you’re not migrating, use a less aggressive approach)

The difficulty was especially with the first part, because I had no clue why the login was being rejected. I hope this helps someone.

And again, thanks for the plugin! :slight_smile:

2 Likes

This is more an issue with the importer, independent of the migratepassword plugin.

4 Likes

Hi,

This plugin is just what I’m after, can it be used with Drupal?

Thanks,

Rich.

It’s not listed in the plugin:

https://github.com/communiteq/discourse-migratepassword/blob/master/plugin.rb#L11-L19

You’d need to do some testing and/or change the plugin to match Drupal’s password encoding (or maybe one of those forums is Drupal-based and it’ll just work).

1 Like