bcrypt.js
1 const path = require('path'); 2 const bindings = require('node-gyp-build')(path.resolve(__dirname)); 3 4 const crypto = require('crypto'); 5 6 const promises = require('./promises'); 7 8 /// generate a salt (sync) 9 /// @param {Number} [rounds] number of rounds (default 10) 10 /// @return {String} salt 11 function genSaltSync(rounds, minor) { 12 // default 10 rounds 13 if (!rounds) { 14 rounds = 10; 15 } else if (typeof rounds !== 'number') { 16 throw new Error('rounds must be a number'); 17 } 18 19 if (!minor) { 20 minor = 'b'; 21 } else if (minor !== 'b' && minor !== 'a') { 22 throw new Error('minor must be either "a" or "b"'); 23 } 24 25 return bindings.gen_salt_sync(minor, rounds, crypto.randomBytes(16)); 26 } 27 28 /// generate a salt 29 /// @param {Number} [rounds] number of rounds (default 10) 30 /// @param {Function} cb callback(err, salt) 31 function genSalt(rounds, minor, cb) { 32 let error; 33 34 // if callback is first argument, then use defaults for others 35 if (typeof arguments[0] === 'function') { 36 // have to set callback first otherwise arguments are overridden 37 cb = arguments[0]; 38 rounds = 10; 39 minor = 'b'; 40 // callback is second argument 41 } else if (typeof arguments[1] === 'function') { 42 // have to set callback first otherwise arguments are overridden 43 cb = arguments[1]; 44 minor = 'b'; 45 } 46 47 if (!cb) { 48 return promises.promise(genSalt, this, [rounds, minor]); 49 } 50 51 // default 10 rounds 52 if (!rounds) { 53 rounds = 10; 54 } else if (typeof rounds !== 'number') { 55 // callback error asynchronously 56 error = new Error('rounds must be a number'); 57 return process.nextTick(function () { 58 cb(error); 59 }); 60 } 61 62 if (!minor) { 63 minor = 'b' 64 } else if (minor !== 'b' && minor !== 'a') { 65 error = new Error('minor must be either "a" or "b"'); 66 return process.nextTick(function () { 67 cb(error); 68 }); 69 } 70 71 crypto.randomBytes(16, function (error, randomBytes) { 72 if (error) { 73 cb(error); 74 return; 75 } 76 77 bindings.gen_salt(minor, rounds, randomBytes, cb); 78 }); 79 } 80 81 /// hash data using a salt 82 /// @param {String|Buffer} data the data to encrypt 83 /// @param {String} salt the salt to use when hashing 84 /// @return {String} hash 85 function hashSync(data, salt) { 86 if (data == null || salt == null) { 87 throw new Error('data and salt arguments required'); 88 } 89 90 if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) { 91 throw new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); 92 } 93 94 if (typeof salt === 'number') { 95 salt = module.exports.genSaltSync(salt); 96 } 97 98 return bindings.encrypt_sync(data, salt); 99 } 100 101 /// hash data using a salt 102 /// @param {String|Buffer} data the data to encrypt 103 /// @param {String} salt the salt to use when hashing 104 /// @param {Function} cb callback(err, hash) 105 function hash(data, salt, cb) { 106 let error; 107 108 if (typeof data === 'function') { 109 error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); 110 return process.nextTick(function () { 111 data(error); 112 }); 113 } 114 115 if (typeof salt === 'function') { 116 error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); 117 return process.nextTick(function () { 118 salt(error); 119 }); 120 } 121 122 // cb exists but is not a function 123 // return a rejecting promise 124 if (cb && typeof cb !== 'function') { 125 return promises.reject(new Error('cb must be a function or null to return a Promise')); 126 } 127 128 if (!cb) { 129 return promises.promise(hash, this, [data, salt]); 130 } 131 132 if (data == null || salt == null) { 133 error = new Error('data and salt arguments required'); 134 return process.nextTick(function () { 135 cb(error); 136 }); 137 } 138 139 if (!(typeof data === 'string' || data instanceof Buffer) || (typeof salt !== 'string' && typeof salt !== 'number')) { 140 error = new Error('data must be a string or Buffer and salt must either be a salt string or a number of rounds'); 141 return process.nextTick(function () { 142 cb(error); 143 }); 144 } 145 146 147 if (typeof salt === 'number') { 148 return module.exports.genSalt(salt, function (err, salt) { 149 return bindings.encrypt(data, salt, cb); 150 }); 151 } 152 153 return bindings.encrypt(data, salt, cb); 154 } 155 156 /// compare raw data to hash 157 /// @param {String|Buffer} data the data to hash and compare 158 /// @param {String} hash expected hash 159 /// @return {bool} true if hashed data matches hash 160 function compareSync(data, hash) { 161 if (data == null || hash == null) { 162 throw new Error('data and hash arguments required'); 163 } 164 165 if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') { 166 throw new Error('data must be a string or Buffer and hash must be a string'); 167 } 168 169 return bindings.compare_sync(data, hash); 170 } 171 172 /// compare raw data to hash 173 /// @param {String|Buffer} data the data to hash and compare 174 /// @param {String} hash expected hash 175 /// @param {Function} cb callback(err, matched) - matched is true if hashed data matches hash 176 function compare(data, hash, cb) { 177 let error; 178 179 if (typeof data === 'function') { 180 error = new Error('data and hash arguments required'); 181 return process.nextTick(function () { 182 data(error); 183 }); 184 } 185 186 if (typeof hash === 'function') { 187 error = new Error('data and hash arguments required'); 188 return process.nextTick(function () { 189 hash(error); 190 }); 191 } 192 193 // cb exists but is not a function 194 // return a rejecting promise 195 if (cb && typeof cb !== 'function') { 196 return promises.reject(new Error('cb must be a function or null to return a Promise')); 197 } 198 199 if (!cb) { 200 return promises.promise(compare, this, [data, hash]); 201 } 202 203 if (data == null || hash == null) { 204 error = new Error('data and hash arguments required'); 205 return process.nextTick(function () { 206 cb(error); 207 }); 208 } 209 210 if (!(typeof data === 'string' || data instanceof Buffer) || typeof hash !== 'string') { 211 error = new Error('data and hash must be strings'); 212 return process.nextTick(function () { 213 cb(error); 214 }); 215 } 216 217 return bindings.compare(data, hash, cb); 218 } 219 220 /// @param {String} hash extract rounds from this hash 221 /// @return {Number} the number of rounds used to encrypt a given hash 222 function getRounds(hash) { 223 if (hash == null) { 224 throw new Error('hash argument required'); 225 } 226 227 if (typeof hash !== 'string') { 228 throw new Error('hash must be a string'); 229 } 230 231 return bindings.get_rounds(hash); 232 } 233 234 module.exports = { 235 genSaltSync, 236 genSalt, 237 hashSync, 238 hash, 239 compareSync, 240 compare, 241 getRounds, 242 }