Skip to content

Commit d5656b3

Browse files
committed
Add helper to check for password input length
1 parent e09eb9a commit d5656b3

File tree

4 files changed

+127
-80
lines changed

4 files changed

+127
-80
lines changed

README.md

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ increasing computation power. ([see](http://en.wikipedia.org/wiki/Bcrypt))
1414
While bcrypt.js is compatible to the C++ bcrypt binding, it is written in pure JavaScript and thus slower ([about 30%](https://github.com/dcodeIO/bcrypt.js/wiki/Benchmark)), effectively reducing the number of iterations that can be
1515
processed in an equal time span.
1616

17-
The maximum input length is 72 bytes (note that UTF8 encoded characters use up to 4 bytes) and the length of generated
18-
hashes is 60 characters.
17+
The maximum input length is 72 bytes (note that UTF-8 encoded characters use up to 4 bytes) and the length of generated
18+
hashes is 60 characters. Note that maximum input length is not implicitly checked by the library for compatibility with
19+
the C++ binding on Node.js, but should be checked with `bcrypt.truncates(password)` where necessary.
1920

2021
## Usage
2122

@@ -150,23 +151,26 @@ Usage: bcrypt <input> [rounds|salt]
150151
- bcrypt.**genSalt**([rounds: `number`, ]callback: `Callback<string>`): `void`<br />
151152
Asynchronously generates a salt. Number of rounds defaults to 10 when omitted.
152153

153-
- bcrypt.**hashSync**(s: `string`, salt?: `number | string`): `string`
154-
Synchronously generates a hash for the given string. Number of rounds defaults to 10 when omitted.
154+
- bcrypt.**truncates**(password: `string`): `boolean`<br />
155+
Tests if a password will be truncated when hashed, that is its length is greater than 72 bytes when converted to UTF-8.
155156

156-
- bcrypt.**hash**(s: `string`, salt: `number | string`): `Promise<string>`<br />
157-
Asynchronously generates a hash for the given string.
157+
- bcrypt.**hashSync**(password: `string`, salt?: `number | string`): `string`
158+
Synchronously generates a hash for the given password. Number of rounds defaults to 10 when omitted.
158159

159-
- bcrypt.**hash**(s: `string`, salt: `number | string`, callback: `Callback<string>`, progressCallback?: `ProgressCallback`): `void`<br />
160-
Asynchronously generates a hash for the given string.
160+
- bcrypt.**hash**(password: `string`, salt: `number | string`): `Promise<string>`<br />
161+
Asynchronously generates a hash for the given password.
161162

162-
- bcrypt.**compareSync**(s: `string`, hash: `string`): `boolean`<br />
163-
Synchronously tests a string against a hash.
163+
- bcrypt.**hash**(password: `string`, salt: `number | string`, callback: `Callback<string>`, progressCallback?: `ProgressCallback`): `void`<br />
164+
Asynchronously generates a hash for the given password.
164165

165-
- bcrypt.**compare**(s: `string`, hash: `string`): `Promise<boolean>`<br />
166-
Asynchronously compares a string against a hash.
166+
- bcrypt.**compareSync**(password: `string`, hash: `string`): `boolean`<br />
167+
Synchronously tests a password against a hash.
167168

168-
- bcrypt.**compare**(s: `string`, hash: `string`, callback: `Callback<boolean>`, progressCallback?: `ProgressCallback`)<br />
169-
Asynchronously compares a string against a hash.
169+
- bcrypt.**compare**(password: `string`, hash: `string`): `Promise<boolean>`<br />
170+
Asynchronously compares a password against a hash.
171+
172+
- bcrypt.**compare**(password: `string`, hash: `string`, callback: `Callback<boolean>`, progressCallback?: `ProgressCallback`)<br />
173+
Asynchronously compares a password against a hash.
170174

171175
- bcrypt.**getRounds**(hash: `string`): `number`<br />
172176
Gets the number of rounds used to encrypt the specified hash.

index.js

Lines changed: 57 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -148,42 +148,42 @@ export function genSalt(rounds, seed_length, callback) {
148148
}
149149

150150
/**
151-
* Synchronously generates a hash for the given string.
152-
* @param {string} s String to hash
151+
* Synchronously generates a hash for the given password.
152+
* @param {string} password Password to hash
153153
* @param {(number|string)=} salt Salt length to generate or salt to use, default to 10
154154
* @returns {string} Resulting hash
155155
*/
156-
export function hashSync(s, salt) {
156+
export function hashSync(password, salt) {
157157
if (typeof salt === "undefined") salt = GENSALT_DEFAULT_LOG2_ROUNDS;
158158
if (typeof salt === "number") salt = genSaltSync(salt);
159-
if (typeof s !== "string" || typeof salt !== "string")
160-
throw Error("Illegal arguments: " + typeof s + ", " + typeof salt);
161-
return _hash(s, salt);
159+
if (typeof password !== "string" || typeof salt !== "string")
160+
throw Error("Illegal arguments: " + typeof password + ", " + typeof salt);
161+
return _hash(password, salt);
162162
}
163163

164164
/**
165-
* Asynchronously generates a hash for the given string.
166-
* @param {string} s String to hash
165+
* Asynchronously generates a hash for the given password.
166+
* @param {string} password Password to hash
167167
* @param {number|string} salt Salt length to generate or salt to use
168168
* @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting hash
169169
* @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed
170170
* (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms.
171171
* @returns {!Promise} If `callback` has been omitted
172172
* @throws {Error} If `callback` is present but not a function
173173
*/
174-
export function hash(s, salt, callback, progressCallback) {
174+
export function hash(password, salt, callback, progressCallback) {
175175
function _async(callback) {
176-
if (typeof s === "string" && typeof salt === "number")
176+
if (typeof password === "string" && typeof salt === "number")
177177
genSalt(salt, function (err, salt) {
178-
_hash(s, salt, callback, progressCallback);
178+
_hash(password, salt, callback, progressCallback);
179179
});
180-
else if (typeof s === "string" && typeof salt === "string")
181-
_hash(s, salt, callback, progressCallback);
180+
else if (typeof password === "string" && typeof salt === "string")
181+
_hash(password, salt, callback, progressCallback);
182182
else
183183
nextTick(
184184
callback.bind(
185185
this,
186-
Error("Illegal arguments: " + typeof s + ", " + typeof salt),
186+
Error("Illegal arguments: " + typeof password + ", " + typeof salt),
187187
),
188188
);
189189
}
@@ -220,39 +220,41 @@ function safeStringCompare(known, unknown) {
220220
}
221221

222222
/**
223-
* Synchronously tests a string against a hash.
224-
* @param {string} s String to compare
223+
* Synchronously tests a password against a hash.
224+
* @param {string} password Password to compare
225225
* @param {string} hash Hash to test against
226226
* @returns {boolean} true if matching, otherwise false
227227
* @throws {Error} If an argument is illegal
228228
*/
229-
export function compareSync(s, hash) {
230-
if (typeof s !== "string" || typeof hash !== "string")
231-
throw Error("Illegal arguments: " + typeof s + ", " + typeof hash);
229+
export function compareSync(password, hash) {
230+
if (typeof password !== "string" || typeof hash !== "string")
231+
throw Error("Illegal arguments: " + typeof password + ", " + typeof hash);
232232
if (hash.length !== 60) return false;
233233
return safeStringCompare(
234-
hashSync(s, hash.substring(0, hash.length - 31)),
234+
hashSync(password, hash.substring(0, hash.length - 31)),
235235
hash,
236236
);
237237
}
238238

239239
/**
240-
* Asynchronously compares the given data against the given hash.
241-
* @param {string} s Data to compare
242-
* @param {string} hashValue Data to be compared to
240+
* Asynchronously tests a password against a hash.
241+
* @param {string} password Password to compare
242+
* @param {string} hashValue Hash to test against
243243
* @param {function(Error, boolean)=} callback Callback receiving the error, if any, otherwise the result
244244
* @param {function(number)=} progressCallback Callback successively called with the percentage of rounds completed
245245
* (0.0 - 1.0), maximally once per `MAX_EXECUTION_TIME = 100` ms.
246246
* @returns {!Promise} If `callback` has been omitted
247247
* @throws {Error} If `callback` is present but not a function
248248
*/
249-
export function compare(s, hashValue, callback, progressCallback) {
249+
export function compare(password, hashValue, callback, progressCallback) {
250250
function _async(callback) {
251-
if (typeof s !== "string" || typeof hashValue !== "string") {
251+
if (typeof password !== "string" || typeof hashValue !== "string") {
252252
nextTick(
253253
callback.bind(
254254
this,
255-
Error("Illegal arguments: " + typeof s + ", " + typeof hashValue),
255+
Error(
256+
"Illegal arguments: " + typeof password + ", " + typeof hashValue,
257+
),
256258
),
257259
);
258260
return;
@@ -262,7 +264,7 @@ export function compare(s, hashValue, callback, progressCallback) {
262264
return;
263265
}
264266
hash(
265-
s,
267+
password,
266268
hashValue.substring(0, 29),
267269
function (err, comp) {
268270
if (err) callback(err);
@@ -314,6 +316,18 @@ export function getSalt(hash) {
314316
return hash.substring(0, 29);
315317
}
316318

319+
/**
320+
* Tests if a password will be truncated when hashed, that is its length is
321+
* greater than 72 bytes when converted to UTF-8.
322+
* @param {string} password The password to test
323+
* @returns {boolean} `true` if truncated, otherwise `false`
324+
*/
325+
export function truncates(password) {
326+
if (typeof password !== "string")
327+
throw Error("Illegal arguments: " + typeof password);
328+
return utf8Length(password) > 72;
329+
}
330+
317331
/**
318332
* Continues with the callback on the next tick.
319333
* @function
@@ -960,7 +974,7 @@ function _crypt(b, salt, rounds, callback, progressCallback) {
960974
j;
961975

962976
//Use typed arrays when available - huge speedup!
963-
if (Int32Array) {
977+
if (typeof Int32Array === "function") {
964978
P = new Int32Array(P_ORIG);
965979
S = new Int32Array(S_ORIG);
966980
} else {
@@ -1014,18 +1028,18 @@ function _crypt(b, salt, rounds, callback, progressCallback) {
10141028
}
10151029

10161030
/**
1017-
* Internally hashes a string.
1018-
* @param {string} s String to hash
1031+
* Internally hashes a password.
1032+
* @param {string} password Password to hash
10191033
* @param {?string} salt Salt to use, actually never null
10201034
* @param {function(Error, string=)=} callback Callback receiving the error, if any, and the resulting hash. If omitted,
10211035
* hashing is performed synchronously.
10221036
* @param {function(number)=} progressCallback Callback called with the current progress
10231037
* @returns {string|undefined} Resulting hash if callback has been omitted, otherwise `undefined`
10241038
* @inner
10251039
*/
1026-
function _hash(s, salt, callback, progressCallback) {
1040+
function _hash(password, salt, callback, progressCallback) {
10271041
var err;
1028-
if (typeof s !== "string" || typeof salt !== "string") {
1042+
if (typeof password !== "string" || typeof salt !== "string") {
10291043
err = Error("Invalid string / salt: Not a string");
10301044
if (callback) {
10311045
nextTick(callback.bind(this, err));
@@ -1070,9 +1084,9 @@ function _hash(s, salt, callback, progressCallback) {
10701084
r2 = parseInt(salt.substring(offset + 1, offset + 2), 10),
10711085
rounds = r1 + r2,
10721086
real_salt = salt.substring(offset + 3, offset + 25);
1073-
s += minor >= "a" ? "\x00" : "";
1087+
password += minor >= "a" ? "\x00" : "";
10741088

1075-
var passwordb = utf8Array(s),
1089+
var passwordb = utf8Array(password),
10761090
saltb = base64_decode(real_salt, BCRYPT_SALT_LEN);
10771091

10781092
/**
@@ -1115,23 +1129,23 @@ function _hash(s, salt, callback, progressCallback) {
11151129
/**
11161130
* Encodes a byte array to base64 with up to len bytes of input, using the custom bcrypt alphabet.
11171131
* @function
1118-
* @param {!Array.<number>} b Byte array
1119-
* @param {number} len Maximum input length
1132+
* @param {!Array.<number>} bytes Byte array
1133+
* @param {number} length Maximum input length
11201134
* @returns {string}
11211135
*/
1122-
export function encodeBase64(b, len) {
1123-
return base64_encode(b, len);
1136+
export function encodeBase64(bytes, length) {
1137+
return base64_encode(bytes, length);
11241138
}
11251139

11261140
/**
11271141
* Decodes a base64 encoded string to up to len bytes of output, using the custom bcrypt alphabet.
11281142
* @function
1129-
* @param {string} s String to decode
1130-
* @param {number} len Maximum output length
1143+
* @param {string} string String to decode
1144+
* @param {number} length Maximum output length
11311145
* @returns {!Array.<number>}
11321146
*/
1133-
export function decodeBase64(s, len) {
1134-
return base64_decode(s, len);
1147+
export function decodeBase64(string, length) {
1148+
return base64_decode(string, length);
11351149
}
11361150

11371151
export default {
@@ -1144,6 +1158,7 @@ export default {
11441158
compare,
11451159
getRounds,
11461160
getSalt,
1161+
truncates,
11471162
encodeBase64,
11481163
decodeBase64,
11491164
};

tests/index.js

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,13 @@ const tests = [
116116
assert.equal(bcrypt.getRounds(hash1), 10);
117117
done();
118118
},
119+
function truncates(done) {
120+
assert(!bcrypt.truncates(""));
121+
assert(!bcrypt.truncates("a".repeat(72)));
122+
assert(bcrypt.truncates("a".repeat(73)));
123+
assert(bcrypt.truncates("๏ เป็นมนุษย์สุดประเสริฐเลิศคุณค่า"));
124+
done();
125+
},
119126
function progress(done) {
120127
bcrypt.genSalt(12, function (err, salt) {
121128
assert(!err);
@@ -173,7 +180,11 @@ const tests = [
173180
},
174181
function compat_hash(done) {
175182
var pass = [
176-
" space ",
183+
"",
184+
" ",
185+
" a ",
186+
"a".repeat(72),
187+
"a".repeat(73),
177188
"Heizölrückstoßabdämpfung",
178189
"Ξεσκεπάζω τὴν ψυχοφθόρα βδελυγμία",
179190
"El pingüino Wenceslao hizo kilómetros bajo exhaustiva lluvia y ",
@@ -197,7 +208,7 @@ const tests = [
197208
}
198209
done();
199210
},
200-
function compat_roundsOOB(done) {
211+
function compat_rounds(done) {
201212
var salt1 = bcrypt.genSaltSync(0), // $10$ like not set
202213
salt2 = bcryptcpp.genSaltSync(0);
203214
assert.strictEqual(salt1.substring(0, 7), "$2b$10$");

0 commit comments

Comments
 (0)