Skip to content

Instantly share code, notes, and snippets.

@jaydson
Created June 22, 2018 16:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jaydson/cc48390591d43db7b9f9f23b8ad5ddb1 to your computer and use it in GitHub Desktop.
Save jaydson/cc48390591d43db7b9f9f23b8ad5ddb1 to your computer and use it in GitHub Desktop.
var crypto = require('crypto');
// NOTE: slower than 'bcrypt' but is pure JS and has no dependencies.
// for server side, consider using 'bcrypt' (C++), has the same API.
var bcrypt = require('bcryptjs');
/* Applies SHA256 to the given data, returns encoded as base64 string.
*
* Parameters:
* - data: any value usable by crypto's update() method, i.e: String
* or buffer.
*
* Returns: String encoded as base64 of the sha256
*/
function sha256AsBase64(data) {
return crypto.createHash('sha256')
.update(data)
.digest('base64');
}
/* Get bcrypt salt from username.
*
* Parameters:
* - `username`: string with username.
* - `rounds` (optional): use bcrypt rounds, or 12 if omitted.
* - `prefix` (optional): use bcrypt prefix, or `2a` if omitted.
*
* Returns: String encoded as bcrypt salt format. Example:
* `$2a$12$N9qo8uLOickgx2ZMRZoMye`
*/
function bcryptSaltFromUsername(username, rounds, prefix) {
var hash = crypto.createHash('sha256').update(username).digest();
return ('$' + (prefix || '2a') +
'$' + (rounds || 12) +
'$' + bcrypt.encodeBase64(hash, 16));
}
/* Hash the given password using the given salt.
*
* This basically calls bcrypt on the `sha256AsBase64(password)`.
*
* Parameters:
* - `password`: string with clear text password.
* - `salt`: salt encoded using bcrypt's standard format.
*
* Return: 60 characters string encoded with the standard bcrypt
* format, ie:
* `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy`
*/
function bcryptPasswordSaltSync(password, salt) {
return bcrypt.hashSync(sha256AsBase64(password), salt);
}
/* Create a `challenge` using the bcryptPassword and the given salt.
*
* This basically calls bcrypt on the `bcryptPassword`.
*
* Parameters:
* - `bcryptPassword`: string with hashed password, see
* `bcryptUserPasswordSync()`. Must be 60 characters in length.
* - `salt`: salt encoded using bcrypt's standard format.
*
* Return: 60 characters string encoded with the standard bcrypt
* format, ie:
* `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy`
*/
function bcryptChallengePasswordSync(bcryptPassword, salt) {
if (bcryptPassword.length !== 60) {
throw new Error('invalid bcryptPassword length!');
}
return bcrypt.hashSync(bcryptPassword, salt);
}
/* Given an username and its password, create the bcrypt hash expected
* by Elo API.
*
* The Elo API demands 12 salt-rounds (2^12 operations = 4096
* iterations) using the first 16 bytes of `salt16Bytes =
* sha256(username)` encoded as `bcrypt.encodeBase64(salt16Bytes,
* 16)`. **NOTE: bcrypt uses a custom base64 alphabet!**.
*
* Since bcrypt limits its hashed payload to 72 characters and
* passwords are of an unknown size we apply `sha256AsBase64(password)`
* so we're fixed to 44 characters resulting from the Base64 encode of
* the 256 bits from sha256.
*
* The resulting string is a 60 characters string encoded with the
* standard bcrypt format: a prefix, the salt-rounds, the salt and the
* actual hash, Example:
*
* - Prefix: `2a` (bcrypt version)
* - Rounds: `12`
* - Salt: `N9qo8uLOickgx2ZMRZoMye`
* - Hash: `IjZAgcfl7p92ldGxad68LJZdL17lhW`
* - Encoded: `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy`
*
* Parameters:
* - `username`: string with username.
* - `password`: string with clear text password.
*
* Return: 60 characters string encoded with the standard bcrypt
* format, ie:
* `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy`
*/
function bcryptUserPasswordSync(username, password) {
var salt = bcryptSaltFromUsername(username);
return bcryptPasswordSaltSync(password, salt);
}
/* Hash the given password using the given salt.
*
* This basically calls bcrypt on the `sha256AsBase64(password)`.
*
* Parameters:
* - `password`: string with clear text password.
* - `salt`: salt encoded using bcrypt's standard format.
* - `cb`: the callback function that receives 2 parameters: error and
* hash. It's called when the computation is done.
*
* Return: if no `cb` is provided, returns a `Promise`.
*/
function bcryptPasswordSalt(password, salt, cb) {
return bcrypt.hash(sha256AsBase64(password), salt, cb);
}
/* Create a `challenge` using the bcryptPassword and the given salt.
*
* This basically calls bcrypt on the `bcryptPassword`.
*
* Parameters:
* - `bcryptPassword`: string with hashed password, see
* `bcryptUserPassword()`. Must be 60 characters in length.
* - `salt`: salt encoded using bcrypt's standard format.
* - `cb`: the callback function that receives 2 parameters: error and
* hash. It's called when the computation is done.
*
* Return: if no `cb` is provided, returns a `Promise`.
*/
function bcryptChallengePassword(bcryptPassword, salt) {
if (bcryptPassword.length !== 60) {
throw new Error('invalid bcryptPassword length!');
}
return bcrypt.hash(bcryptPassword, salt);
}
/* Given an username and its password, create the bcrypt hash expected
* by Elo API.
*
* The Elo API demands 12 salt-rounds (2^12 operations = 4096
* iterations) using the first 16 bytes of `salt16Bytes =
* sha256(username)` encoded as `bcrypt.encodeBase64(salt16Bytes,
* 16)`. **NOTE: bcrypt uses a custom base64 alphabet!**.
*
* Since bcrypt limits its hashed payload to 72 characters and
* passwords are of an unknown size we apply `sha256AsBase64(password)`
* so we're fixed to 44 characters resulting from the Base64 encode of
* the 256 bits from sha256.
*
* The resulting string is a 60 characters string encoded with the
* standard bcrypt format: a prefix, the salt-rounds, the salt and the
* actual hash, Example:
*
* - Prefix: `2a` (bcrypt version)
* - Rounds: `12`
* - Salt: `N9qo8uLOickgx2ZMRZoMye`
* - Hash: `IjZAgcfl7p92ldGxad68LJZdL17lhW`
* - Encoded: `$2a$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy`
*
* Parameters:
* - `username`: string with username.
* - `password`: string with clear text password.
* - `cb`: the callback function that receives 2 parameters: error and
* hash. It's called when the computation is done.
*
* Return: if no `cb` is provided, returns a `Promise`.
*/
function bcryptUserPassword(username, password, cb) {
var salt = bcryptSaltFromUsername(username);
return bcryptPasswordSalt(password, salt, cb);
}
module.exports = {
sha256AsBase64: sha256AsBase64,
bcryptSaltFromUsername: bcryptSaltFromUsername,
bcryptUserPasswordSync: bcryptUserPasswordSync,
bcryptPasswordSaltSync: bcryptPasswordSaltSync,
bcryptChallengePasswordSync: bcryptChallengePasswordSync,
bcryptUserPassword: bcryptUserPassword,
bcryptPasswordSalt: bcryptPasswordSalt,
bcryptChallengePassword: bcryptChallengePassword,
bcrypt: bcrypt,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment