unixpass
Version:
Native implementation of Unix compatible DES/MD5/SHA256/SHA512 password hashing.
157 lines (146 loc) • 4.36 kB
JavaScript
const crypto = require('crypto');
const pb64 = require('./pb64.js');
const desCrypt = require('./pwdes.js');
const bcrypt = require('./bcrypt.js');
const hashCrypt = require('./hash-crypt.js');
const UNIXPASS_CRYPT_STD_DES = 1;
const UNIXPASS_CRYPT_EXT_DES = 2;
const UNIXPASS_CRYPT_MD5 = 3;
const UNIXPASS_CRYPT_BCRYPT = 4;
const UNIXPASS_CRYPT_SHA256 = 5;
const UNIXPASS_CRYPT_SHA512 = 6;
const VOC_ALPHANUM = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';
const VOC_PB64 = pb64.voc;
function rndInt(min, max) {
if (! (Number.isSafeInteger(min) && Number.isSafeInteger(max))) {
throw new TypeError('Non integer limits');
}
if ((min > max) || (min < -0xffffffff) || (max > 0x100000000)) {
throw new RangeError('Unacceptable limits');
}
if (min == max) {
return min;
}
let b = crypto.randomBytes(7);
let n = (b.readUIntLE(0, 6) + ((b.readUInt8(6) & 0x0f) * 0x1000000000000));
return (n % (max - min)) + min;
}
function rndStr(len, voc) {
var r, i;
if (! (Number.isSafeInteger(len) && (typeof(voc) === 'string'))) {
throw new TypeError('Bad length or vocabulary');
}
if (! (len >= 0) && (len <= 0x100000) && (voc.length > 0)) {
throw new RangeError('Unacceptable length or vocabulary');
}
for (i = 0, r = ''; i < len; i++) {
r += voc.charAt(rndInt(0, voc.length));
}
return r;
}
const crypt = function(password, salt) {
var m;
if (! ((typeof(password) === 'string') && (typeof(salt) === 'string'))) {
return false;
}
if (salt.match(/^([\.\/0-9A-Za-z]{2})([\.\/0-9A-Za-z]{11})?$/) ||
salt.match(/^_([\.\/0-9A-Za-z]{4})([\.\/0-9A-Za-z]{4})([\.\/0-9A-Za-z]{11})?$/)) {
let r;
try {
r = desCrypt(password, salt);
} catch (e) {
console.log(e);
r = false;
}
return r;
}
m = salt.match(/^((\$([156])\$)(rounds=([1-9][0-9]{0,13})\$)?)([^\$]+)(\$([\.\/0-9A-Za-z]*)?)?$/);
if (m) {
// We deviate a little from the original MD5 password hashing,
// since it doesn't allow explicitly given number of
// rounds. If in the original implementation, rounds are given
// explicitly, the salt becomes "rounds=#" where # is the
// first digit of the number of rounds. This is already so
// brain damaged, that we'll just ignore it.
let r;
try {
let alg = Number.parseInt(m[3]);
let rounds = Math.max(1000, Math.min(999999999, (m[5] === undefined) ? ((alg == 1) ? 1000 : 5000) : Number.parseInt(m[5])));
let salt = m[6];
r = hashCrypt(password, salt, alg, rounds, (m[4] !== undefined));
} catch (e) {
console.log(e);
r = false;
}
return r;
}
m = salt.match(/^(\$2([abxy]|)\$(\d\d)\$)([./0-9A-Za-z]{22})/);
if (m) {
let r;
try {
r = bcrypt(password, salt);
} catch (e) {
console.log(e);
r = false;
}
return r;
}
return false;
};
var mksalt = function(type) {
var r, n, b;
switch (type) {
case UNIXPASS_CRYPT_STD_DES:
r = rndStr(2, VOC_PB64);
break;
case UNIXPASS_CRYPT_EXT_DES:
n = rndInt(1024, 1280) | 1;
r = ('_' +
pb64.n2c(n & 0x3f) + pb64.n2c((n >> 6) & 0x3f) +
pb64.n2c((n >> 12) & 0x3f) + pb64.n2c((n >> 18) & 0x3f) +
rndStr(4, VOC_PB64));
break;
case UNIXPASS_CRYPT_MD5:
r = '$1$' + rndStr(8, VOC_ALPHANUM) + '$';
break;
case UNIXPASS_CRYPT_BCRYPT:
r = '$2a$11$' + rndStr(21, VOC_ALPHANUM) + 'z';
break;
case UNIXPASS_CRYPT_SHA256:
r = '$5$rounds=' + rndInt(5000, 6000).toString() + '$' + rndStr(16, VOC_ALPHANUM) + '$';
break;
case UNIXPASS_CRYPT_SHA512:
r = '$6$rounds=' + rndInt(5000, 6000).toString() + '$' + rndStr(16, VOC_ALPHANUM) + '$';
break;
default:
throw new RangeError('Unsupported password type');
}
return r;
}
var mkpass = function(password, type) {
if (! type) {
type = UNIXPASS_CRYPT_SHA512;
}
var salt = mksalt(type);
var r = crypt(password, salt);
return r;
};
var check = function(password, hash) {
if (! ((typeof(password) === 'string') && (typeof(hash) === 'string'))) {
return false;
}
var hash2 = crypt(password, hash);
return hash2 && hash2.length && (hash2 === hash);
}
module.exports = {
crypt: crypt,
mkpass: mkpass,
check: check,
UNIXPASS_CRYPT_STD_DES: UNIXPASS_CRYPT_STD_DES,
UNIXPASS_CRYPT_EXT_DES: UNIXPASS_CRYPT_EXT_DES,
UNIXPASS_CRYPT_MD5: UNIXPASS_CRYPT_MD5,
UNIXPASS_CRYPT_BCRYPT: UNIXPASS_CRYPT_BCRYPT,
UNIXPASS_CRYPT_SHA256: UNIXPASS_CRYPT_SHA256,
UNIXPASS_CRYPT_SHA512: UNIXPASS_CRYPT_SHA512
};
;