Skip to content

Commit

Permalink
Merge pull request #549 from agathver/2b-hashes
Browse files Browse the repository at this point in the history
Add support for 2b hashes
  • Loading branch information
recrsn committed Mar 16, 2018
2 parents e8cde51 + 4c44f20 commit 0ea1b36
Show file tree
Hide file tree
Showing 9 changed files with 95 additions and 62 deletions.
16 changes: 16 additions & 0 deletions .editorconfig
@@ -0,0 +1,16 @@
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[{package.json,*.yml}]
indent_style = space
indent_size = 2

[*.md]
trim_trailing_whitespace = false
6 changes: 6 additions & 0 deletions README.md
Expand Up @@ -42,6 +42,12 @@ To make it easier for people using this tool to analyze what has been surveyed,

* An [issue with passwords][jtr] was found with a version of the Blowfish algorithm developed for John the Ripper. This is not present in the OpenBSD version and is thus not a problem for this module. HT [zooko][zooko].

## Compatibility Note

This library supports `$2a$` and `$2b$` prefix bcrypt hashes. `$2x$` and `$2y$` hashes are specific to bcrypt implementation developed for Jon the Ripper. In theory, they should be compatible with `$2b$` prefix.

Compatibility with hashes generated by other languages is not 100% guaranteed due to difference in character encodings. However, it should not be an issue for most cases.

## Dependencies

* NodeJS
Expand Down
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -33,7 +33,7 @@
"node-pre-gyp": "0.7.0"
},
"devDependencies": {
"nodeunit": "~0.9.1"
"nodeunit": "~0.11.1"
},
"contributors": [
"Antonio Salazar Cardozo <savedfastcool@gmail.com> (https://github.com/Shadowfiend)",
Expand Down
90 changes: 42 additions & 48 deletions src/bcrypt.cc
@@ -1,46 +1,32 @@
/* $OpenBSD: bcrypt.c,v 1.24 2008/04/02 19:54:05 millert Exp $ */
/* $OpenBSD: bcrypt.c,v 1.31 2014/03/22 23:02:03 tedu Exp $ */

/*
* Copyright 1997 Niels Provos <provos@physnet.uni-hamburg.de>
* All rights reserved.
* Copyright (c) 1997 Niels Provos <provos@umich.edu>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Niels Provos.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/

/* This password hashing algorithm was designed by David Mazieres
* <dm@lcs.mit.edu> and works as follows:
*
* 1. state := InitState ()
* 2. state := ExpandKey (state, salt, password) 3.
* REPEAT rounds:
* state := ExpandKey (state, 0, salt)
* state := ExpandKey(state, 0, password)
* 2. state := ExpandKey (state, salt, password)
* 3. REPEAT rounds:
* state := ExpandKey (state, 0, password)
* state := ExpandKey (state, 0, salt)
* 4. ctext := "OrpheanBeholderScryDoubt"
* 5. REPEAT 64:
* ctext := Encrypt_ECB (state, ctext);
* ctext := Encrypt_ECB (state, ctext);
* 6. RETURN Concatenate (salt, ctext);
*
*/
Expand All @@ -51,7 +37,7 @@
#include <string.h>
#include "node_blf.h"

#ifdef _WIN32
#ifdef _WIN32
#define snprintf _snprintf
#endif

Expand All @@ -64,10 +50,6 @@
* time to come.
*/

/*char *bcrypt(const char *, const char *);
void encode_salt(char *, u_int8_t *, u_int16_t, u_int8_t);
char * bcrypt_gensalt(u_int8_t log_rounds);*/

static void encode_base64(u_int8_t *, u_int8_t *, u_int16_t);
static void decode_base64(u_int8_t *, u_int16_t, u_int8_t *);

Expand Down Expand Up @@ -133,7 +115,7 @@ encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
{
salt[0] = '$';
salt[1] = BCRYPT_VERSION;
salt[2] = 'a';
salt[2] = 'b';
salt[3] = '$';

snprintf(salt + 4, 4, "%2.2u$", logr);
Expand All @@ -150,12 +132,12 @@ encode_salt(char *salt, u_int8_t *csalt, u_int16_t clen, u_int8_t logr)
void
bcrypt_gensalt(u_int8_t log_rounds, u_int8_t *seed, char *gsalt)
{
if (log_rounds < 4)
log_rounds = 4;
else if (log_rounds > 31)
log_rounds = 31;
if (log_rounds < 4)
log_rounds = 4;
else if (log_rounds > 31)
log_rounds = 31;

encode_salt(gsalt, seed, BCRYPT_MAXSALT, log_rounds);
encode_salt(gsalt, seed, BCRYPT_MAXSALT, log_rounds);
}

/* We handle $Vers$log2(NumRounds)$salt+passwd$
Expand Down Expand Up @@ -185,8 +167,8 @@ bcrypt(const char *key, const char *salt, char *encrypted)
/* Check for minor versions */
if (salt[1] != '$') {
switch (salt[1]) {
case 'a':
/* 'ab' should not yield the same as 'abab' */
case 'a': /* 'ab' should not yield the same as 'abab' */
case 'b': /* cap input length at 72 bytes */
minor = salt[1];
salt++;
break;
Expand All @@ -205,7 +187,7 @@ bcrypt(const char *key, const char *salt, char *encrypted)
strcpy(encrypted, error);
return;
}

/* Computer power doesn't increase linear, 2^x should be fine */
n = atoi(salt);
if (n > 31 || n < 0) {
Expand All @@ -229,13 +211,25 @@ bcrypt(const char *key, const char *salt, char *encrypted)
/* We dont want the base64 salt but the raw data */
decode_base64(csalt, BCRYPT_MAXSALT, (u_int8_t *) salt);
salt_len = BCRYPT_MAXSALT;
key_len = strlen(key) + (minor >= 'a' ? 1 : 0);
if (minor <= 'a')
key_len = (u_int8_t)(strlen(key) + (minor >= 'a' ? 1 : 0));
else
{
/* strlen() returns a size_t, but the function calls
* below result in implicit casts to a narrower integer
* type, so cap key_len at the actual maximum supported
* length here to avoid integer wraparound */
key_len = strlen(key);
if (key_len > 72)
key_len = 72;
key_len++; /* include the NUL */
}


/* Setting up S-Boxes and Subkeys */
Blowfish_initstate(&state);
Blowfish_expandstate(&state, csalt, salt_len,
(u_int8_t *) key, key_len);
(u_int8_t *) key, key_len);
for (k = 0; k < rounds; k++) {
Blowfish_expand0state(&state, (u_int8_t *) key, key_len);
Blowfish_expand0state(&state, csalt, salt_len);
Expand Down Expand Up @@ -271,7 +265,7 @@ bcrypt(const char *key, const char *salt, char *encrypted)

encode_base64((u_int8_t *) encrypted + i + 3, csalt, BCRYPT_MAXSALT);
encode_base64((u_int8_t *) encrypted + strlen(encrypted), ciphertext,
4 * BCRYPT_BLOCKS - 1);
4 * BCRYPT_BLOCKS - 1);
memset(&state, 0, sizeof(state));
memset(ciphertext, 0, sizeof(ciphertext));
memset(csalt, 0, sizeof(csalt));
Expand Down
1 change: 1 addition & 0 deletions src/bcrypt_node.cc
Expand Up @@ -30,6 +30,7 @@ bool ValidateSalt(const char* salt) {
if (salt[1] != '$') {
switch (salt[1]) {
case 'a':
case 'b':
salt++;
break;
default:
Expand Down
6 changes: 3 additions & 3 deletions test/async.js
Expand Up @@ -84,7 +84,7 @@ module.exports = {
assert.expect(2);
bcrypt.genSalt(10, function(err, salt) {
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '10');
assert.done();
});
Expand All @@ -93,7 +93,7 @@ module.exports = {
assert.expect(2);
bcrypt.genSalt(1, function(err, salt) {
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '04');
assert.done();
});
Expand All @@ -102,7 +102,7 @@ module.exports = {
assert.expect(2);
bcrypt.genSalt(100, function(err, salt) {
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '31');
assert.done();
});
Expand Down
18 changes: 17 additions & 1 deletion test/implementation.js
Expand Up @@ -11,10 +11,19 @@ module.exports = {
assert.strictEqual(bcrypt.hashSync("0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", "$2a$05$abcdefghijklmnopqrstuu"), "$2a$05$abcdefghijklmnopqrstuu5s2v8.iXieOjg/.AySBTTZIIVFJeBui");
assert.done();
},
openbsd_bcrypt_tests: function(assert) {
assert.strictEqual(bcrypt.hashSync("000000000000000000000000000000000000000000000000000000000000000000000000", "$2a$05$CCCCCCCCCCCCCCCCCCCCC."), "$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS");
assert.strictEqual(bcrypt.hashSync("000000000000000000000000000000000000000000000000000000000000000000000000", "$2b$05$CCCCCCCCCCCCCCCCCCCCC."), "$2b$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS");
assert.done();
},
test_long_passwords: function(assert) {
// bcrypt wrap-around bug in $2a$
assert.strictEqual(bcrypt.hashSync("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234", "$2a$05$CCCCCCCCCCCCCCCCCCCCC."), "$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS");
assert.strictEqual(bcrypt.hashSync("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345", "$2a$05$CCCCCCCCCCCCCCCCCCCCC."), "$2a$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS");

// tests for $2b$ which fixes wrap-around bugs
assert.strictEqual(bcrypt.hashSync("012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234", "$2b$05$CCCCCCCCCCCCCCCCCCCCC."), "$2b$05$CCCCCCCCCCCCCCCCCCCCC.XxrQqgBi/5Sxuq9soXzDtjIZ7w5pMfK");
assert.strictEqual(bcrypt.hashSync("0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345", "$2b$05$CCCCCCCCCCCCCCCCCCCCC."), "$2b$05$CCCCCCCCCCCCCCCCCCCCC.6.O1dLNbjod2uo0DVcW.jHucKbPDdHS");
assert.done();
},
test_shorten_salt_to_128_bits: function(assert) {
Expand All @@ -23,5 +32,12 @@ module.exports = {
assert.strictEqual(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCM"), "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK");
assert.strictEqual(bcrypt.hashSync("U*U*", "$2a$05$CCCCCCCCCCCCCCCCCCCCCA"), "$2a$05$CCCCCCCCCCCCCCCCCCCCC.VGOzA784oUp/Z0DY336zx7pLYAy0lwK");
assert.done();
},
test_consistency: function(assert) {
assert.strictEqual(bcrypt.hashSync("ππππππππ", "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeu"), "$2a$10$.TtQJ4Jr6isd4Hp.mVfZeuh6Gws4rOQ/vdBczhDx.19NFK0Y84Dle");
assert.strictEqual(bcrypt.hashSync("p@5sw0rd", "$2b$12$zQ4CooEXdGqcwi0PHsgc8e"), "$2b$12$zQ4CooEXdGqcwi0PHsgc8eAf0DLXE/XHoBE8kCSGQ97rXwuClaPam");
assert.strictEqual(bcrypt.hashSync("C'est bon, la vie!", "$2b$12$cbo7LZ.wxgW4yxAA5Vqlv."), "$2b$12$cbo7LZ.wxgW4yxAA5Vqlv.KR6QFPt4qCdc9RYJNXxa/rbUOp.1sw.");
assert.strictEqual(bcrypt.hashSync("ἓν οἶδα ὅτι οὐδὲν οἶδα", "$2b$12$LeHKWR2bmrazi/6P22Jpau"), "$2b$12$LeHKWR2bmrazi/6P22JpauX5my/eKwwKpWqL7L5iEByBnxNc76FRW");
assert.done();
}
}
}
6 changes: 3 additions & 3 deletions test/promise.js
Expand Up @@ -124,7 +124,7 @@ if (typeof Promise !== 'undefined') {
assert.expect(2);
bcrypt.genSalt(10).then(function(salt) {
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '10');
assert.done();
});
Expand All @@ -133,7 +133,7 @@ if (typeof Promise !== 'undefined') {
assert.expect(2);
bcrypt.genSalt(1).then(function(salt) {
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '04');
assert.done();
});
Expand All @@ -142,7 +142,7 @@ if (typeof Promise !== 'undefined') {
assert.expect(2);
bcrypt.genSalt(100).then(function(salt) {
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '31');
assert.done();
});
Expand Down
12 changes: 6 additions & 6 deletions test/sync.js
Expand Up @@ -5,15 +5,15 @@ module.exports = {
var salt = bcrypt.genSaltSync(10);
assert.strictEqual(29, salt.length, "Salt isn't the correct length.");
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '10');
assert.done();
},
test_salt_no_params: function(assert) {
// same as test_verify_salt except using default rounds of 10
var salt = bcrypt.genSaltSync();
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '10');
assert.done();
},
Expand Down Expand Up @@ -55,29 +55,29 @@ module.exports = {
test_hash_salt_validity: function(assert) {
assert.expect(2);
assert.ok(bcrypt.hashSync('password', '$2a$10$somesaltyvaluertsetrse'));
assert.throws(function() {
assert.throws(function() {
bcrypt.hashSync('password', 'some$value');
});
assert.done();
},
test_verify_salt: function(assert) {
var salt = bcrypt.genSaltSync(10);
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '10');
assert.done();
},
test_verify_salt_min_rounds: function(assert) {
var salt = bcrypt.genSaltSync(1);
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '04');
assert.done();
},
test_verify_salt_max_rounds: function(assert) {
var salt = bcrypt.genSaltSync(100);
var split_salt = salt.split('$');
assert.strictEqual(split_salt[1], '2a');
assert.strictEqual(split_salt[1], '2b');
assert.strictEqual(split_salt[2], '31');
assert.done();
},
Expand Down

0 comments on commit 0ea1b36

Please sign in to comment.