I am trying to take a plaintext password and hash it for comparison to password hashes stored in FreeBSD's /etc/master.passwd. My goal is to have a node.js program able to authenticate against the master.passwd database using existing account credentials.
I've set up a FreeBSD account, called test1. I gave it a password of "password". Looking at master.password, I see this in the password field:
$6$nEIifU2XZ9VDx3l5$RUW0Udy60Hon9OsoTAz8DcH0uvZ4E3p5CXFScrC694EF1Cpkf8/5GUtC750NZXnMFYZsMlBZE52INFlBUvWMb0
My understanding is that the $ characters act as delimiters for three fields:
I tried to replicate the hashing algorithm in Node.js like this:
const crypto = require("crypto");
let passwordPlain = "password"
let salt64 = "nEIifU2XZ9VDx3l5"
let salt = Buffer.from(salt64, "base64");
let passwordHashed = crypto.scryptSync(salt, passwordPlain, 64);
let passwordHashed64 = passwordHashed.toString('base64');
console.log(`$6$${salt64}$${passwordHashed64}`);
I would have expected to see my plain-text password of "password" hashed with the salt and displayed to match what's in master.password. What I got was very different.
$6$nEIifU2XZ9VDx3l5$Y9/0OCikTExQlo0lLp5FVK6DuANVx7BOXZ/spLvTyFvJstUVpJGeanqE+U6Uca63PagSiGNDfMbg35MpTUT/dQ==
I've tried passing the salt without base64 decoding. I've tried including the $ delimiters as part of the salt. I even tried Hmac instead of scrypt. Nothing works.
I'm beginning to wonder if it's not base64 encoded. The document that suggested it was is fairly dated, mentioning only MD5 hashing. Also, I would expect to see at least some base64 padding with equal signs in my master.passwd and I don't. Finally, I came across an example on the FreeBSD website that shows a password hash with a dot in it, and as far as I know that's not part of the base64 set of characters. (Only letters, digits, and + /)
I'm also thinking about Node.js scrypt() vs. FreeBSD crypt(). It was the closest thing I could find and when I use the default key length of 64 and remove the two padding characters, the base64 encoded length is a perfect match for the length of what's in master.passwd. But still, the hashes are very different.
I'm hoping someone can shed some light on things as I have come to the limit of my internet searching skills on this one.
Links to reseached pages:
sha512crypt-node is a NodeJS implementation of the SHA512-based crypt implementation. The following code provides the desired hash:
const crypt = require('sha512crypt-node')
const hash = crypt.b64_sha512crypt('password', 'nEIifU2XZ9VDx3l5')
console.log( hash ) // $6$nEIifU2XZ9VDx3l5$RUW0Udy60Hon9OsoTAz8DcH0uvZ4E3p5CXFScrC694EF1Cpkf8/5GUtC750NZXnMFYZsMlBZE52INFlBUvWMb0
Further usage examples can be found here.
sha512crypt-node and also the SHA512 part of the FreeBSD implementation of crypt are based on the algorithm described here, see also [1][2].
crypto.scrypt
(or crypto.scryptSync
) from NodeJS is based on the scrypt algorithm described here, see also [3][4]. Ultimately, these are different algorithms, so that matching hashs cannot be expected.
This article here compares various algorithms including scrypt and the SHA512 part of crypt and should be of interest to you, especially with regard to security.