What algorhythm is used for encrypting user passwords?
-
Seems like I'm not getting how it all works here. First of all, bcrypt generates hashes according to salt, but where that salt is set? And how then someone is actually checked against database on login, client must know what is salt also, right?
I need to manually replicate login system of NodeBB on another site. I just want to use user database of NodeBB on some other site.
-
Tried this also:
https://www.devglan.com/online-tools/bcrypt-hash-generatorOnline Bcrypt Hashed Matcher can't match passwords no way.
-
@pyc4 The file you should be looking at is
src/passwords.js
Specifically, the plaintext input is hashed using sha512, and then a salt is generated alongside. The password is then hashed using bcrypt before being stored into the database.
The sha wrapping allows usage of passwords longer than 73 characters in length. Prior to this, the input was truncated down to 73 characters due to a limitation in bcrypt.
-
But if I hash password using SHA512 (https://passwordsgenerator.net/sha512-hash-generator/), I get 128 characters, how then bcrypt hashes 128 chars when 73 is the limit?
Sorry this must sound like some crappy question But definitely I just can't wrap my head around it now, seems it's so simple but yet complicated.
Anyways, is salt somehow important when making checks if passwords match against database or not? In other words, how would I know what (random?) salt is generated when hash is made for the first time and written to database?
I see the number of rounds for bcrypt is custom, but where and how it is set?
-
@pyc4 My crypto is rusty. That said, if I understand correctly, the sha512 hash, regardless of initial input, results in 64 characters (/ 512 8), which then ducks under bcrypts 73 char limit?
Hope this helps. Have fun down the crypto rabbit hole.
-
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