10

I'm dealing with an old database with $2y hashes. I've dug into this a bit, also stumbled on the stack overflow on the difference between $2a and $2y.

I looked into the node module for bcrypt which seems to generate and compare only $2a hashes.

I found a website that generates $2y hashes so I can test them with bcrypt.

Here's an example of a $2y hash of the string helloworld.

helloworld:$2y$10$tRM7x9gGKhcAmpeqKEdhj.qRWCr4qoV1FU9se0Crx2hkMVNL2ktEW

Seems the module has no way of validating $2y hashes.

Here's my test.

var Promise = require('bluebird')
var bcrypt = require('bcrypt')

var string = 'helloworld'

Promise.promisifyAll(bcrypt)

// bcrypt.genSalt(10, function(err, salt) {
//   bcrypt.hash(string, salt, function(err, hash) {
//     console.log(hash)
//   })
// })

var hashesGeneratedUsingBcryptModule = [
  '$2a$10$6ppmIdlNEPwxWJskPaQ7l.d2fblh.GO6JomzrcpiD/hxGPOXA3Bsq',
  '$2a$10$YmpoYCDHzdAPMbd9B8l48.hkSnylnAPbOym367FKIEPa0ixY.o4b.',
  '$2a$10$Xfy3OPurrZEmbmmO0x1wGuFMdRTlmOgEMS0geg4wTj1vKcvXXjk06',
  '$2a$10$mYgwmdPZjiEncp7Yh5UB1uyPkoyavxrYcOIzzY4mzSniGpI9RbhL.',
  '$2a$10$dkBVTe2A2DAn24PUq1GZYe7AqL8WQqwOi8ZWBJAauOg60sk44DkOC'
]

var hashesGeneratedUsingAspirineDotOrg = [
  '$2y$10$MKgpAXLJkwx5tpijWX99Qek2gf/irwvp5iSfxuFoDswIjMIbj2.Ma',
  '$2y$10$tRM7x9gGKhcAmpeqKEdhj.qRWCr4qoV1FU9se0Crx2hkMVNL2ktEW'
]

var hashesGeneratedUsingAspirineDotOrgSwippedYForA = [
  '$2a$10$MKgpAXLJkwx5tpijWX99Qek2gf/irwvp5iSfxuFoDswIjMIbj2.Ma',
  '$2a$10$tRM7x9gGKhcAmpeqKEdhj.qRWCr4qoV1FU9se0Crx2hkMVNL2ktEW'
]

hashesGeneratedUsingBcryptModule = hashesGeneratedUsingBcryptModule.map(hash => bcrypt.compareAsync(string, hash))
hashesGeneratedUsingAspirineDotOrg = hashesGeneratedUsingAspirineDotOrg.map(hash => bcrypt.compareAsync(string, hash))
hashesGeneratedUsingAspirineDotOrgSwippedYForA = hashesGeneratedUsingAspirineDotOrgSwippedYForA.map(hash => bcrypt.compareAsync(string, hash))

Promise.all(hashesGeneratedUsingBcryptModule)
.tap(() => console.log('hashesGeneratedUsingBcryptModule'))
.then(console.log)

Promise.all(hashesGeneratedUsingAspirineDotOrg)
.tap(() => console.log('hashesGeneratedUsingAspirineDotOrg'))
.then(console.log)

Promise.all(hashesGeneratedUsingAspirineDotOrgSwippedYForA)
.tap(() => console.log('hashesGeneratedUsingAspirineDotOrgSwippedYForA'))
.then(console.log)

Here are the results:

// hashesGeneratedUsingAspirineDotOrg
// [ false, false ]
// hashesGeneratedUsingBcryptModule
// [ true, true, true, true, true ]
// hashesGeneratedUsingAspirineDotOrgSwippedYForA
// [ false, false ]

I'm stumped on how I can compare $2y hashes in node.

There's another Stack Overflow question / answer that says you can just change the $2y to $2a but that still fails for me.

Update!

I was using the generator incorrectly because it's a .htpasswd password generator you have to put in the username and password in this format.

reggi helloworld

And the output corresponds here:

reggi:$2y$10$iuC7GYH/h1Gl1aDmcpLFpeJXN9OZXZUYnaqD2NnGLQiVGQYBDtbtO

Before I as putting just

helloword

Which I'm assuming hashed a empty string.

With these changes changing the y to an a works in bcrypt. And twin-bcrypt just works.

Community
  • 1
  • 1
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424
  • 1
    I vaguely recall having better luck working the other way -- taking the `$2a$` hashes generated by `bcrypt` in javascript, replacing `2a` with `2y`, and then comparing using `2y` libraries in other languages (php natively and `BCrypt` from .net could both handle it, which struck me as very odd). I can dig up the test code I had, if that would be helpful to you. – dvlsg Mar 16 '16 at 16:42
  • 1
    @dvlsg Got it. That makes sense. So I need to compare `$2y` hashes in node, not `$2a` hashes in php, which I'm guessing works by replacing the `a` to `y`. – ThomasReggi Mar 16 '16 at 16:44
  • Yeah, I was actually storing the hashes as `$2y` in the database, using them as-is for both PHP and .NET, but when I used them in node I had an extra convert step which swapped `y` back to `a` before comparison. It felt wrong, but it looked like `2a` and `2y` used the same structure for the rest of the salt/hash. – dvlsg Mar 16 '16 at 16:47

1 Answers1

22
  • When using bcrypt change the y to an a.
  • When using twin-bcrypt the hash just works.

When using http://aspirine.org/htpasswd_en.html make sure that you provide a username and password.

reggi helloworld

Then:

reggi:$2y$10$Am0Nf/B6.S/Wkpr6IVdIZeuHWNa/fqoLyTNmlyrSg22AjRf2vS.T.

Here's a working example with both bcrypt and twin-bcrypt.

var twinBcrypt = require('twin-bcrypt')
var bcrypt = require('bcrypt')

var string = 'helloworld'

var bcryptAttempt = bcrypt.compareSync(string, "$2y$10$Am0Nf/B6.S/Wkpr6IVdIZeuHWNa/fqoLyTNmlyrSg22AjRf2vS.T.".replace(/^\$2y/, "$2a"))
console.log(bcryptAttempt)

var twinBcryptAttempt = twinBcrypt.compareSync(string, "$2y$10$Am0Nf/B6.S/Wkpr6IVdIZeuHWNa/fqoLyTNmlyrSg22AjRf2vS.T.")
console.log(twinBcryptAttempt)

Outputs:

true
true
ThomasReggi
  • 55,053
  • 85
  • 237
  • 424