What algorhythm is used for encrypting user passwords?
-
So I guess if I get something like this from online SHA512 generator: 4ffcc3cce9a56025aa0126c2fe7ec247e8a6e5ee6fdd5e854bb3761b66e5c5c4909189ad054e44a473cc29fd461fccf1965aebe5b17dfbaac9b994112c5a33a3
It looks like it can be converted to 64 (ascii?) characters...
-
@pyc4 Be advised that it has been many years since I tried wrapping my noggin' around this stuff.... How it gets from 512 bits to 64 bytes for bcrypt input I do not know.
But maybe this helps. Seems to me @julian and crew may have opted for something akin to "Solution 3"?
Password hash truncation
The bcrypt algorithm involves repeatedly encrypting the 24-byte text:
OrpheanBeholderScryDoubt (24-bytes)
This generates 24 bytes of ciphertext, e.g.:
85 20 af 9f 03 3d b3 8c 08 5f d2 5e 2d aa 5e 84 a2 b9 61 d2 f1 29 c9 a4 (24-bytes)
The canonical OpenBSD implementation truncates this to 23 bytes:
85 20 af 9f 03 3d b3 8c 08 5f d2 5e 2d aa 5e 84 a2 b9 61 d2 f1 29 c9 (23-bytes)
It is unclear why the canonical implementation deletes 8-bits from the resulting password hash.
These 23 bytes become 31 characters when radix-64 encoded:
fQAtluK7q2uGV7HcJYncfII3WbJvIai (31-characters)
-
@gotwf Well I don't exactly need to know the depths of such algorithms, what I need is just a little bit of information on how to successfully use functions that are involved here... think password.js have some crucial information, I see what libraries are used and what is most important, think that would be sufficient but I guess I'll have few more questions here most probably.
-
-
@pyc4 As mentioned previously hereabouts on community, I do not code nodebb but somewhere in the recesses of my mind I seem to recall that the default number of rounds for bcrypt itself is seven. Since I am so rusty w/that stuff, I decided to duckduckgo it and ran across the node.bcrypt.js bcrypt API:
API
BCrypt.
genSaltSync(rounds, minor)
rounds - [OPTIONAL] - the cost of processing the data. (default - 10)
minor - [OPTIONAL] - minor version of bcrypt to use. (default - b)
genSalt(rounds, minor, cb)
rounds - [OPTIONAL] - the cost of processing the data. (default - 10)
minor - [OPTIONAL] - minor version of bcrypt to use. (default - b)
cb - [OPTIONAL] - a callback to be fired once the salt has been generated. uses eio making it asynchronous. If cb is not specified, a Promise is returned if Promise support is available.
err - First parameter to the callback detailing any errors.
salt - Second parameter to the callback providing the generated salt.Which seems to indicate a default salt rounds of ten.
Maybe grep that code/file for "genSalt" could provide additional insights? Hope this helps get you sorted. Else you'll have to wait/hope for one of the code savvy dev helpful helper types to weigh in because this is about all I have got on the subject.
Good luck!
-
Means that if bcrypt_rounds is not found in nconf, 12 rounds is used?
Yes
Any help on where nconf configuration is stored?
config.json but generally it's not specified so our default of 12 is used.
-
@PitaJ is correct,
bcrypt_rounds
can be set anywhere that nconf reads configs from, so mainlyconfig.json
but also via env var, or passed into the nodebb executable as an argument.But it's optional, so it usually just falls back to the default of 12 rounds. As computation power increases, we increase this number to compensate. A login attempt should block for some noticible amount of time (perhaps a second or two).
@pyc4 said:
Well I don't exactly need to know the depths of such algorithms,
Correct, and neither do we. We implicitly trust in the security of the bcrypt library because rolling our own crypto is almost always a bad idea
That said the sha-wrapping is outside of the bcrypt lib, so I honestly couldn't say whether there are bugs in that implementation.
-
Thanks for your replies, it was useful and I'm making some progress here. Please stay with me a bit more so I can finish this, help's appreciated here very much
Right now I'm struggling with SHA512 hashing, here's the code that I took from password.js to do hashing:
passwordSHA512 = crypto.createHash('sha512').update(passwordSHA512).digest('hex'); console.log(passwordSHA512); // 4ffcc3cce9a56025aa0126c2fe7ec247e8a6e5ee6fdd5e854bb3761b66e5c5c4909189ad054e44a473cc29fd461fccf1965aebe5b17dfbaac9b994112c5a33a3 const salt = bcrypt.genSaltSync(12); const hash = bcrypt.hashSync(passwordSHA512, salt); console.log(salt); // $2a$12$ZDFzbDOVrZSZ.L.RFWQiqe console.log(hash); // $2a$12$ZDFzbDOVrZSZ.L.RFWQiqej/ed5M6b/C06Hz/mRCuOd1fMVpzVF/.
So it's used in completely the same way as in password.js, but what I finally get is some other hash string, which is not the same as hash string generated by NodeBB which is saved in its database.
As I've previously said, the length of hash I get after SHA512 is applied looks suspicious to me, and it's 128 chars long hex value which I don't know how it's supposed to be supplied to bcrypt because of 72 chars limit. Probably that HEX value is somehow converted to shorter string? But if it's somehow automatic, it's supposed to work, but what I get is completely some other value than I need to get.
-
@pyc4 if you have older user passwords, it's likely those were hashed without the sha step
-
@pyc4 if the salt is different between NodeBB and your app, the resulting hash will of course be different.
The salt is randomly generated for each password, so it can't be shared.
But the salt itself is prepended to the hash, so the real test is whether running .compare() on your app shows that the password matches.
-
OK, just let me be clear on the step really I'm not fully sure it's ok?
-
user-entered password is put through
crypto.createHash('sha512').update(password).digest('hex'); -
then I use that as the first argument for bcrypt.compare() function, and the second argument is password fetched from NodeBB database, and if true is returned entered password is matched against stored password.
Still I don't understand how is it possible to compare if I don't have original salt, but if it could work like how I described it, it's good enough for me.
-
-
@pyc4 share your code please
-
passwordSHA512 = "b33bcf64f2744712deb66354b1d6a6d0"; passwordSHA512 = crypto.createHash('sha512').update(passwordSHA512).digest('hex'); console.log(passwordSHA512); // 455800380a39d8c49b976eb4bc31b98710ceb5beecd88c823c50ca2bdfd7cf1a581d92d9f64df5cfb2f9e50dfc3b2240e119b5ceffc99e584b310838f999aebc const match = bcrypt.compareSync(passwordSHA512, "$2a$12$56c7LRlpF9Mt47eeXDBgBuIBsuf3NPU4hAFzQRyxM7pZWMwhz3EOG"); console.log(match); // false
match should be true, not false.
-
@pyc4 and you got the password hash+salt directly from the database?
-
I tested this and Im getting the expected result.
router.get('/test', async (req, res) => { const crypto = require('crypto'); const bcrypt = require('bcryptjs'); const rounds = 12; const mypassword = '123456'; const shaPassword = crypto.createHash('sha512').update(mypassword).digest('hex'); const salt = await bcrypt.genSalt(parseInt(rounds, 10)); const hashedPassword = await bcrypt.hash(shaPassword, salt); console.log('hashedPassword', hashedPassword); // testing const mypasswordtry = '123456'; const shaPasswordtry = crypto.createHash('sha512').update(mypasswordtry).digest('hex'); res.json({ 'should be true': bcrypt.compareSync(shaPasswordtry, hashedPassword), 'should be false': bcrypt.compareSync('asdasdasa', hashedPassword), }); });
Prints out
{ "should be true": true, "should be false": false }
-
@pyc4 salt is included in the password hash and stored in the same string in the database, which is why compare is passed only two things.