Skip to content

Commit aac593c

Browse files
authoredApr 6, 2018
Merge pull request #587 from agathver/hash-version-support
Allow to choose bcrypt minor version
2 parents 0ea1b36 + 2d45be1 commit aac593c

File tree

8 files changed

+111
-33
lines changed

8 files changed

+111
-33
lines changed
 

‎CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# UNRELEASED
2+
3+
* Make `2b` the default bcrypt version
4+
15
# 1.0.2 (2016-12-31)
26

37
* Fix `compare` promise rejection with invalid arguments

‎README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -181,10 +181,12 @@ If you are using bcrypt on a simple script, using the sync mode is perfectly fin
181181

182182
`BCrypt.`
183183

184-
* `genSaltSync(rounds)`
184+
* `genSaltSync(rounds, minor)`
185185
* `rounds` - [OPTIONAL] - the cost of processing the data. (default - 10)
186-
* `genSalt(rounds, cb)`
186+
* `minor` - [OPTIONAL] - minor version of bcrypt to use. (default - b)
187+
* `genSalt(rounds, minor, cb)`
187188
* `rounds` - [OPTIONAL] - the cost of processing the data. (default - 10)
189+
* `minor` - [OPTIONAL] - minor version of bcrypt to use. (default - b)
188190
* `cb` - [OPTIONAL] - a callback to be fired once the salt has been generated. uses eio making it asynchronous. If `cb` is not specified, a `Promise` is returned if Promise support is available.
189191
* `err` - First parameter to the callback detailing any errors.
190192
* `salt` - Second parameter to the callback providing the generated salt.
@@ -267,7 +269,7 @@ The code for this comes from a few sources:
267269
* [Nate Rajlich][tootallnate] - Bindings and build process.
268270
* [Sean McArthur][seanmonstar] - Windows Support
269271
* [Fanie Oosthuysen][weareu] - Windows Support
270-
* [Amitosh Swain Mahapatra][agathver] - ES6 Promise Support
272+
* [Amitosh Swain Mahapatra][agathver] - $2b$ hash support, ES6 Promise support
271273

272274
## License
273275
Unless stated elsewhere, file headers or otherwise, the license as stated in the LICENSE file.

‎bcrypt.js

+23-5
Original file line numberDiff line numberDiff line change
@@ -12,36 +12,45 @@ var promises = require('./lib/promises');
1212
/// generate a salt (sync)
1313
/// @param {Number} [rounds] number of rounds (default 10)
1414
/// @return {String} salt
15-
module.exports.genSaltSync = function genSaltSync(rounds) {
15+
module.exports.genSaltSync = function genSaltSync(rounds, minor) {
1616
// default 10 rounds
1717
if (!rounds) {
1818
rounds = 10;
1919
} else if (typeof rounds !== 'number') {
2020
throw new Error('rounds must be a number');
2121
}
2222

23-
return bindings.gen_salt_sync(rounds, crypto.randomBytes(16));
23+
if(!minor) {
24+
minor = 'b';
25+
} else if(minor !== 'b' && minor !== 'a') {
26+
console.log(minor, typeof minor);
27+
throw new Error('minor must be either "a" or "b"');
28+
}
29+
30+
return bindings.gen_salt_sync(minor, rounds, crypto.randomBytes(16));
2431
};
2532

2633
/// generate a salt
2734
/// @param {Number} [rounds] number of rounds (default 10)
2835
/// @param {Function} cb callback(err, salt)
29-
module.exports.genSalt = function genSalt(rounds, ignore, cb) {
36+
module.exports.genSalt = function genSalt(rounds, minor, cb) {
3037
var error;
3138

3239
// if callback is first argument, then use defaults for others
3340
if (typeof arguments[0] === 'function') {
3441
// have to set callback first otherwise arguments are overriden
3542
cb = arguments[0];
3643
rounds = 10;
44+
minor = 'b';
3745
// callback is second argument
3846
} else if (typeof arguments[1] === 'function') {
3947
// have to set callback first otherwise arguments are overriden
4048
cb = arguments[1];
49+
minor = 'b';
4150
}
4251

4352
if (!cb) {
44-
return promises.promise(genSalt, this, [rounds, ignore]);
53+
return promises.promise(genSalt, this, [rounds, minor]);
4554
}
4655

4756
// default 10 rounds
@@ -55,13 +64,22 @@ module.exports.genSalt = function genSalt(rounds, ignore, cb) {
5564
});
5665
}
5766

67+
if(!minor) {
68+
minor = 'b'
69+
} else if(minor !== 'b' && minor !== 'a') {
70+
error = new Error('minor must be either "a" or "b"');
71+
return process.nextTick(function() {
72+
cb(error);
73+
});
74+
}
75+
5876
crypto.randomBytes(16, function(error, randomBytes) {
5977
if (error) {
6078
cb(error);
6179
return;
6280
}
6381

64-
bindings.gen_salt(rounds, randomBytes, cb);
82+
bindings.gen_salt(minor, rounds, randomBytes, cb);
6583
});
6684
};
6785

‎src/bcrypt.cc

+4-4
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,11 @@ decode_base64(u_int8_t *buffer, u_int16_t len, u_int8_t *data)
111111
}
112112

113113
void
114-
encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
114+
encode_salt(char *salt, u_int8_t *csalt, char minor, u_int16_t clen, u_int8_t logr)
115115
{
116116
salt[0] = '$';
117117
salt[1] = BCRYPT_VERSION;
118-
salt[2] = 'b';
118+
salt[2] = minor;
119119
salt[3] = '$';
120120

121121
snprintf(salt + 4, 4, "%2.2u$", logr);
@@ -130,14 +130,14 @@ encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
130130
from: http://mail-index.netbsd.org/tech-crypto/2002/05/24/msg000204.html
131131
*/
132132
void
133-
bcrypt_gensalt(u_int8_t log_rounds, u_int8_t *seed, char *gsalt)
133+
bcrypt_gensalt(char minor, u_int8_t log_rounds, u_int8_t *seed, char *gsalt)
134134
{
135135
if (log_rounds < 4)
136136
log_rounds = 4;
137137
else if (log_rounds > 31)
138138
log_rounds = 31;
139139

140-
encode_salt(gsalt, seed, BCRYPT_MAXSALT, log_rounds);
140+
encode_salt(gsalt, seed, minor, BCRYPT_MAXSALT, log_rounds);
141141
}
142142

143143
/* We handle $Vers$log2(NumRounds)$salt+passwd$

‎src/bcrypt_node.cc

+35-17
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,25 @@ bool ValidateSalt(const char* salt) {
6262
return true;
6363
}
6464

65+
char ToCharVersion(Local<String> str) {
66+
String::Utf8Value value(str);
67+
return **value;
68+
}
69+
6570
/* SALT GENERATION */
6671

6772
class SaltAsyncWorker : public Nan::AsyncWorker {
6873
public:
69-
SaltAsyncWorker(Nan::Callback *callback, std::string seed, ssize_t rounds)
74+
SaltAsyncWorker(Nan::Callback *callback, std::string seed, ssize_t rounds, char minor_ver)
7075
: Nan::AsyncWorker(callback, "bcrypt:SaltAsyncWorker"), seed(seed),
71-
rounds(rounds) {
76+
rounds(rounds), minor_ver(minor_ver) {
7277
}
7378

7479
~SaltAsyncWorker() {}
7580

7681
void Execute() {
7782
char salt[_SALT_LEN];
78-
bcrypt_gensalt(rounds, (u_int8_t *)&seed[0], salt);
83+
bcrypt_gensalt(minor_ver, rounds, (u_int8_t *)&seed[0], salt);
7984
this->salt = std::string(salt);
8085
}
8186

@@ -92,49 +97,62 @@ class SaltAsyncWorker : public Nan::AsyncWorker {
9297
std::string seed;
9398
std::string salt;
9499
ssize_t rounds;
100+
char minor_ver;
95101
};
96102

97103
NAN_METHOD(GenerateSalt) {
98104
Nan::HandleScope scope;
99105

100-
if (info.Length() < 3) {
101-
Nan::ThrowTypeError("3 arguments expected");
106+
if (info.Length() < 4) {
107+
Nan::ThrowTypeError("4 arguments expected");
102108
return;
103109
}
104110

105-
if (!Buffer::HasInstance(info[1]) || Buffer::Length(info[1].As<Object>()) != 16) {
106-
Nan::ThrowTypeError("Second argument must be a 16 byte Buffer");
111+
if(!info[0]->IsString()) {
112+
Nan::ThrowTypeError("First argument must be a string");
107113
return;
108114
}
109115

110-
const int32_t rounds = Nan::To<int32_t>(info[0]).FromMaybe(0);
111-
Local<Object> seed = info[1].As<Object>();
112-
Local<Function> callback = Local<Function>::Cast(info[2]);
116+
if (!Buffer::HasInstance(info[2]) || Buffer::Length(info[2].As<Object>()) != 16) {
117+
Nan::ThrowTypeError("Third argument must be a 16 byte Buffer");
118+
return;
119+
}
120+
121+
const char minor_ver = ToCharVersion(info[0]->ToString());
122+
const int32_t rounds = Nan::To<int32_t>(info[1]).FromMaybe(0);
123+
Local<Object> seed = info[2].As<Object>();
124+
Local<Function> callback = Local<Function>::Cast(info[3]);
113125

114126
SaltAsyncWorker* saltWorker = new SaltAsyncWorker(new Nan::Callback(callback),
115-
std::string(Buffer::Data(seed), 16), rounds);
127+
std::string(Buffer::Data(seed), 16), rounds, minor_ver);
116128

117129
Nan::AsyncQueueWorker(saltWorker);
118130
}
119131

120132
NAN_METHOD(GenerateSaltSync) {
121133
Nan::HandleScope scope;
122134

123-
if (info.Length() < 2) {
135+
if (info.Length() < 3) {
124136
Nan::ThrowTypeError("2 arguments expected");
125137
return;
126138
}
127139

128-
if (!Buffer::HasInstance(info[1]) || Buffer::Length(info[1].As<Object>()) != 16) {
129-
Nan::ThrowTypeError("Second argument must be a 16 byte Buffer");
140+
if(!info[0]->IsString()) {
141+
Nan::ThrowTypeError("First argument must be a string");
142+
return;
143+
}
144+
145+
if (!Buffer::HasInstance(info[2]) || Buffer::Length(info[2].As<Object>()) != 16) {
146+
Nan::ThrowTypeError("Third argument must be a 16 byte Buffer");
130147
return;
131148
}
132149

133-
const int32_t rounds = Nan::To<int32_t>(info[0]).FromMaybe(0);
134-
u_int8_t* seed = (u_int8_t*)Buffer::Data(info[1].As<Object>());
150+
const char minor_ver = ToCharVersion(info[0]->ToString());
151+
const int32_t rounds = Nan::To<int32_t>(info[1]).FromMaybe(0);
152+
u_int8_t* seed = (u_int8_t*)Buffer::Data(info[2].As<Object>());
135153

136154
char salt[_SALT_LEN];
137-
bcrypt_gensalt(rounds, seed, salt);
155+
bcrypt_gensalt(minor_ver, rounds, seed, salt);
138156

139157
info.GetReturnValue().Set(Nan::Encode(salt, strlen(salt), Nan::BINARY));
140158
}

‎src/node_blf.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@
4242
#define u_int64_t uint64_t
4343
#endif
4444

45-
#ifdef _WIN32
45+
#ifdef _WIN32
4646
#define u_int8_t unsigned __int8
4747
#define u_int16_t unsigned __int16
4848
#define u_int32_t unsigned __int32
@@ -103,9 +103,9 @@ void blf_cbc_decrypt(blf_ctx *, u_int8_t *, u_int8_t *, u_int32_t);
103103
u_int32_t Blowfish_stream2word(const u_int8_t *, u_int16_t , u_int16_t *);
104104

105105
/* bcrypt functions*/
106-
void bcrypt_gensalt(u_int8_t, u_int8_t*, char *);
106+
void bcrypt_gensalt(char, u_int8_t, u_int8_t*, char *);
107107
void bcrypt(const char *, const char *, char *);
108-
void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
108+
void encode_salt(char *, u_int8_t *, char, u_int16_t, u_int8_t);
109109
u_int32_t bcrypt_get_rounds(const char *);
110110

111111
#endif

‎test/async.js

+21-1
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,31 @@ module.exports = {
1919
});
2020
},
2121
test_salt_rounds_is_string_non_number: function(assert) {
22-
bcrypt.genSalt('b', function (err, salt) {
22+
bcrypt.genSalt('z', function (err, salt) {
2323
assert.ok((err instanceof Error), "Should throw an Error. genSalt requires rounds to of type number.");
2424
assert.done();
2525
});
2626
},
27+
test_salt_minor: function(assert) {
28+
assert.expect(3);
29+
bcrypt.genSalt(10, 'a', function(err, salt) {
30+
assert.strictEqual(29, salt.length, "Salt isn't the correct length.");
31+
var split_salt = salt.split('$');
32+
assert.strictEqual(split_salt[1], '2a');
33+
assert.strictEqual(split_salt[2], '10');
34+
assert.done();
35+
});
36+
},
37+
test_salt_minor_b: function(assert) {
38+
assert.expect(3);
39+
bcrypt.genSalt(10, 'b', function(err, salt) {
40+
assert.strictEqual(29, salt.length, "Salt isn't the correct length.");
41+
var split_salt = salt.split('$');
42+
assert.strictEqual(split_salt[1], '2b');
43+
assert.strictEqual(split_salt[2], '10');
44+
assert.done();
45+
});
46+
},
2747
test_hash: function(assert) {
2848
assert.expect(1);
2949
bcrypt.genSalt(10, function(err, salt) {

‎test/sync.js

+16
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ module.exports = {
2525
assert.throws(function() {bcrypt.genSaltSync('b');}, "Should throw an Error. gen_salt requires rounds to be a number.");
2626
assert.done();
2727
},
28+
test_salt_minor_a: function(assert) {
29+
var salt = bcrypt.genSaltSync(10, 'a');
30+
assert.strictEqual(29, salt.length, "Salt isn't the correct length.");
31+
var split_salt = salt.split('$');
32+
assert.strictEqual(split_salt[1], '2a');
33+
assert.strictEqual(split_salt[2], '10');
34+
assert.done();
35+
},
36+
test_salt_minor_b: function(assert) {
37+
var salt = bcrypt.genSaltSync(10, 'b');
38+
assert.strictEqual(29, salt.length, "Salt isn't the correct length.");
39+
var split_salt = salt.split('$');
40+
assert.strictEqual(split_salt[1], '2b');
41+
assert.strictEqual(split_salt[2], '10');
42+
assert.done();
43+
},
2844
test_hash: function(assert) {
2945
assert.ok(bcrypt.hashSync('password', bcrypt.genSaltSync(10)), "Shouldn't throw an Error.");
3046
assert.done();

0 commit comments

Comments
 (0)
Please sign in to comment.