bcrypt.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /**
  2. * bcrypt namespace.
  3. * @type {Object.<string,*>}
  4. */
  5. var bcrypt = {};
  6. /**
  7. * The random implementation to use as a fallback.
  8. * @type {?function(number):!Array.<number>}
  9. * @inner
  10. */
  11. var randomFallback = null;
  12. /**
  13. * Generates cryptographically secure random bytes.
  14. * @function
  15. * @param {number} len Bytes length
  16. * @returns {!Array.<number>} Random bytes
  17. * @throws {Error} If no random implementation is available
  18. * @inner
  19. */
  20. function random(len) {
  21. /* node */ if (typeof module !== 'undefined' && module && module['exports'])
  22. try {
  23. return require("crypto")['randomBytes'](len);
  24. } catch (e) {}
  25. /* WCA */ try {
  26. var a; (self['crypto']||self['msCrypto'])['getRandomValues'](a = new Uint32Array(len));
  27. return Array.prototype.slice.call(a);
  28. } catch (e) {}
  29. /* fallback */ if (!randomFallback)
  30. throw Error("Neither WebCryptoAPI nor a crypto module is available. Use bcrypt.setRandomFallback to set an alternative");
  31. return randomFallback(len);
  32. }
  33. // Test if any secure randomness source is available
  34. var randomAvailable = false;
  35. try {
  36. random(1);
  37. randomAvailable = true;
  38. } catch (e) {}
  39. // Default fallback, if any
  40. randomFallback = /*? if (ISAAC) { */function(len) {
  41. for (var a=[], i=0; i<len; ++i)
  42. a[i] = ((0.5 + isaac() * 2.3283064365386963e-10) * 256) | 0;
  43. return a;
  44. };/*? } else { */null;/*? }*/
  45. /**
  46. * Sets the pseudo random number generator to use as a fallback if neither node's `crypto` module nor the Web Crypto
  47. * API is available. Please note: It is highly important that the PRNG used is cryptographically secure and that it
  48. * is seeded properly!
  49. * @param {?function(number):!Array.<number>} random Function taking the number of bytes to generate as its
  50. * sole argument, returning the corresponding array of cryptographically secure random byte values.
  51. * @see http://nodejs.org/api/crypto.html
  52. * @see http://www.w3.org/TR/WebCryptoAPI/
  53. */
  54. bcrypt.setRandomFallback = function(random) {
  55. randomFallback = random;
  56. };
  57. /**
  58. * Synchronously generates a salt.
  59. * @param {number=} rounds Number of rounds to use, defaults to 10 if omitted
  60. * @param {number=} seed_length Not supported.
  61. * @returns {string} Resulting salt
  62. * @throws {Error} If a random fallback is required but not set
  63. * @expose
  64. */
  65. bcrypt.genSaltSync = function(rounds, seed_length) {
  66. rounds = rounds || GENSALT_DEFAULT_LOG2_ROUNDS;
  67. if (typeof rounds !== 'number')
  68. throw Error("Illegal arguments: "+(typeof rounds)+", "+(typeof seed_length));
  69. if (rounds < 4)
  70. rounds = 4;
  71. else if (rounds > 31)
  72. rounds = 31;
  73. var salt = [];
  74. salt.push("$2a$");
  75. if (rounds < 10)
  76. salt.push("0");
  77. salt.push(rounds.toString());
  78. salt.push('$');
  79. salt.push(base64_encode(random(BCRYPT_SALT_LEN), BCRYPT_SALT_LEN)); // May throw
  80. return salt.join('');
  81. };
  82. /**
  83. * Asynchronously generates a salt.
  84. * @param {(number|function(Error, string=))=} rounds Number of rounds to use, defaults to 10 if omitted
  85. * @param {(number|function(Error, string=))=} seed_length Not supported.
  86. * @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting salt
  87. * @returns {!Promise} If `callback` has been omitted
  88. * @throws {Error} If `callback` is present but not a function
  89. * @expose
  90. */
  91. bcrypt.genSalt = function(rounds, seed_length, callback) {
  92. if (typeof seed_length === 'function')
  93. callback = seed_length,
  94. seed_length = undefined; // Not supported.
  95. if (typeof rounds === 'function')
  96. callback = rounds,
  97. rounds = undefined;
  98. if (typeof rounds === 'undefined')
  99. rounds = GENSALT_DEFAULT_LOG2_ROUNDS;
  100. else if (typeof rounds !== 'number')
  101. throw Error("illegal arguments: "+(typeof rounds));
  102. function _async(callback) {
  103. nextTick(function() { // Pretty thin, but salting is fast enough
  104. try {
  105. callback(null, bcrypt.genSaltSync(rounds));
  106. } catch (err) {
  107. callback(err);
  108. }
  109. });
  110. }
  111. if (callback) {
  112. if (typeof callback !== 'function')
  113. throw Error("Illegal callback: "+typeof(callback));
  114. _async(callback);
  115. } else
  116. return new Promise(function(resolve, reject) {
  117. _async(function(err, res) {
  118. if (err) {
  119. reject(err);
  120. return;
  121. }
  122. resolve(res);
  123. });
  124. });
  125. };
  126. /**
  127. * Synchronously generates a hash for the given string.
  128. * @param {string} s String to hash
  129. * @param {(number|string)=} salt Salt length to generate or salt to use, default to 10
  130. * @returns {string} Resulting hash
  131. * @expose
  132. */
  133. bcrypt.hashSync = function(s, salt) {
  134. if (typeof salt === 'undefined')
  135. salt = GENSALT_DEFAULT_LOG2_ROUNDS;
  136. if (typeof salt === 'number')
  137. salt = bcrypt.genSaltSync(salt);
  138. if (typeof s !== 'string' || typeof salt !== 'string')
  139. throw Error("Illegal arguments: "+(typeof s)+', '+(typeof salt));
  140. return _hash(s, salt);
  141. };
  142. /**
  143. * Asynchronously generates a hash for the given string.
  144. * @param {string} s String to hash
  145. * @param {number|string} salt Salt length to generate or salt to use
  146. * @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting hash
  147. * @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed
  148. * (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms.
  149. * @returns {!Promise} If `callback` has been omitted
  150. * @throws {Error} If `callback` is present but not a function
  151. * @expose
  152. */
  153. bcrypt.hash = function(s, salt, callback, progressCallback) {
  154. function _async(callback) {
  155. if (typeof s === 'string' && typeof salt === 'number')
  156. bcrypt.genSalt(salt, function(err, salt) {
  157. _hash(s, salt, callback, progressCallback);
  158. });
  159. else if (typeof s === 'string' && typeof salt === 'string')
  160. _hash(s, salt, callback, progressCallback);
  161. else
  162. nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof s)+', '+(typeof salt))));
  163. }
  164. if (callback) {
  165. if (typeof callback !== 'function')
  166. throw Error("Illegal callback: "+typeof(callback));
  167. _async(callback);
  168. } else
  169. return new Promise(function(resolve, reject) {
  170. _async(function(err, res) {
  171. if (err) {
  172. reject(err);
  173. return;
  174. }
  175. resolve(res);
  176. });
  177. });
  178. };
  179. /**
  180. * Compares two strings of the same length in constant time.
  181. * @param {string} known Must be of the correct length
  182. * @param {string} unknown Must be the same length as `known`
  183. * @returns {boolean}
  184. * @inner
  185. */
  186. function safeStringCompare(known, unknown) {
  187. var right = 0,
  188. wrong = 0;
  189. for (var i=0, k=known.length; i<k; ++i) {
  190. if (known.charCodeAt(i) === unknown.charCodeAt(i))
  191. ++right;
  192. else
  193. ++wrong;
  194. }
  195. // Prevent removal of unused variables (never true, actually)
  196. if (right < 0)
  197. return false;
  198. return wrong === 0;
  199. }
  200. /**
  201. * Synchronously tests a string against a hash.
  202. * @param {string} s String to compare
  203. * @param {string} hash Hash to test against
  204. * @returns {boolean} true if matching, otherwise false
  205. * @throws {Error} If an argument is illegal
  206. * @expose
  207. */
  208. bcrypt.compareSync = function(s, hash) {
  209. if (typeof s !== "string" || typeof hash !== "string")
  210. throw Error("Illegal arguments: "+(typeof s)+', '+(typeof hash));
  211. if (hash.length !== 60)
  212. return false;
  213. return safeStringCompare(bcrypt.hashSync(s, hash.substr(0, hash.length-31)), hash);
  214. };
  215. /**
  216. * Asynchronously compares the given data against the given hash.
  217. * @param {string} s Data to compare
  218. * @param {string} hash Data to be compared to
  219. * @param {function(Error, boolean)=} callback Callback receiving the error, if any, otherwise the result
  220. * @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed
  221. * (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms.
  222. * @returns {!Promise} If `callback` has been omitted
  223. * @throws {Error} If `callback` is present but not a function
  224. * @expose
  225. */
  226. bcrypt.compare = function(s, hash, callback, progressCallback) {
  227. function _async(callback) {
  228. if (typeof s !== "string" || typeof hash !== "string") {
  229. nextTick(callback.bind(this, Error("Illegal arguments: "+(typeof s)+', '+(typeof hash))));
  230. return;
  231. }
  232. if (hash.length !== 60) {
  233. nextTick(callback.bind(this, null, false));
  234. return;
  235. }
  236. bcrypt.hash(s, hash.substr(0, 29), function(err, comp) {
  237. if (err)
  238. callback(err);
  239. else
  240. callback(null, safeStringCompare(comp, hash));
  241. }, progressCallback);
  242. }
  243. if (callback) {
  244. if (typeof callback !== 'function')
  245. throw Error("Illegal callback: "+typeof(callback));
  246. _async(callback);
  247. } else
  248. return new Promise(function(resolve, reject) {
  249. _async(function(err, res) {
  250. if (err) {
  251. reject(err);
  252. return;
  253. }
  254. resolve(res);
  255. });
  256. });
  257. };
  258. /**
  259. * Gets the number of rounds used to encrypt the specified hash.
  260. * @param {string} hash Hash to extract the used number of rounds from
  261. * @returns {number} Number of rounds used
  262. * @throws {Error} If `hash` is not a string
  263. * @expose
  264. */
  265. bcrypt.getRounds = function(hash) {
  266. if (typeof hash !== "string")
  267. throw Error("Illegal arguments: "+(typeof hash));
  268. return parseInt(hash.split("$")[2], 10);
  269. };
  270. /**
  271. * Gets the salt portion from a hash. Does not validate the hash.
  272. * @param {string} hash Hash to extract the salt from
  273. * @returns {string} Extracted salt part
  274. * @throws {Error} If `hash` is not a string or otherwise invalid
  275. * @expose
  276. */
  277. bcrypt.getSalt = function(hash) {
  278. if (typeof hash !== 'string')
  279. throw Error("Illegal arguments: "+(typeof hash));
  280. if (hash.length !== 60)
  281. throw Error("Illegal hash length: "+hash.length+" != 60");
  282. return hash.substring(0, 29);
  283. };
  284. //? include("bcrypt/util.js");
  285. //? include("bcrypt/impl.js");
  286. /**
  287. * Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet.
  288. * @function
  289. * @param {!Array.<number>} b Byte array
  290. * @param {number} len Maximum input length
  291. * @returns {string}
  292. * @expose
  293. */
  294. bcrypt.encodeBase64 = base64_encode;
  295. /**
  296. * Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet.
  297. * @function
  298. * @param {string} s String to decode
  299. * @param {number} len Maximum output length
  300. * @returns {!Array.<number>}
  301. * @expose
  302. */
  303. bcrypt.decodeBase64 = base64_decode;