Comparing version 0.16.2 to 0.17.0
@@ -11,2 +11,3 @@ // Type definitions for argon2 v0.14.0 | ||
type?: 0 | 1 | 2; | ||
salt?: Buffer; | ||
raw?: boolean; | ||
@@ -33,3 +34,4 @@ } | ||
export const limits: OptionLimits; | ||
export function hash(plain: Buffer | string, options?: Options): Promise<string>; | ||
export function hash(plain: Buffer | string, options?: Options & {raw: true}): Promise<Buffer>; | ||
export function hash(plain: Buffer | string, options?: Options & {raw?: false}): Promise<string>; | ||
export function verify(hash: string, plain: Buffer | string): Promise<boolean>; |
110
index.js
@@ -6,5 +6,5 @@ 'use strict' | ||
const argon2d = 0 | ||
const argon2i = 1 | ||
const argon2id = 2 | ||
const limits = Object.freeze(bindings.limits) | ||
const types = Object.freeze(bindings.types) | ||
const version = bindings.version | ||
@@ -16,14 +16,14 @@ const defaults = Object.freeze({ | ||
parallelism: 1, | ||
type: argon2i, | ||
type: types.argon2i, | ||
raw: false | ||
}) | ||
const limits = Object.freeze(bindings.limits) | ||
const type2string = [] | ||
const rightPad = encoded => encoded + '='.repeat(encoded.length % 4) | ||
const rightTrim = encoded => encoded.replace(/=+$/, '') | ||
module.exports = { | ||
defaults, | ||
limits, | ||
argon2d, | ||
argon2i, | ||
argon2id, | ||
@@ -35,4 +35,3 @@ hash (plain, options) { | ||
for (let key of Object.keys(limits)) { | ||
const max = limits[key].max | ||
const min = limits[key].min | ||
const {max, min} = limits[key] | ||
const value = options[key] | ||
@@ -44,20 +43,97 @@ if (value > max || value < min) { | ||
if ('salt' in options) { | ||
return resolve() | ||
} | ||
crypto.randomBytes(16, (err, salt) => { | ||
if (err) { | ||
reject(err) | ||
return reject(err) | ||
} | ||
bindings.hash(Buffer.from(plain), salt, options, resolve, reject) | ||
options.salt = salt | ||
return resolve() | ||
}) | ||
}).then(() => { | ||
return new Promise((resolve, reject) => { | ||
bindings.hash(Buffer.from(plain), options, resolve, reject) | ||
}) | ||
}).then(hash => { | ||
return new Promise((resolve, reject) => { | ||
if (options.raw) { | ||
return resolve(hash) | ||
} | ||
const algo = `$${type2string[options.type]}$v=${version}` | ||
const params = [ | ||
`m=${1 << options.memoryCost}`, | ||
`t=${options.timeCost}`, | ||
`p=${options.parallelism}` | ||
].join(',') | ||
const base64hash = rightTrim(hash.toString('base64')) | ||
const base64salt = rightTrim(options.salt.toString('base64')) | ||
return resolve([algo, params, base64salt, base64hash].join('$')) | ||
}) | ||
}) | ||
}, | ||
verify (hash, plain) { | ||
if (!/^\$argon2(i|d|id)(\$v=\d+)?\$m=\d+,t=\d+,p=\d+(?:\$[\w+/]+){2}$/.test(hash)) { | ||
return Promise.reject(new Error('Invalid hash, must be in MCF, generated by Argon2.')) | ||
} | ||
verify (hash, plain, options) { | ||
options = Object.assign({}, options) | ||
const parsed = {} | ||
const sections = hash.split('$') | ||
return new Promise((resolve, reject) => { | ||
bindings.verify(Buffer.from(hash), Buffer.from(plain), resolve, reject) | ||
if ('type' in options) { | ||
return resolve() | ||
} | ||
parsed.type = types[sections[1]] | ||
return resolve() | ||
}).then(() => { | ||
return new Promise((resolve, reject) => { | ||
const params = sections[sections.length - 3] | ||
if (!('memoryCost' in options)) { | ||
const memoryCost = /m=(\d+)/.exec(params) | ||
parsed.memoryCost = Math.log2(+memoryCost[1]) | ||
} | ||
if (!('timeCost' in options)) { | ||
const timeCost = /t=(\d+)/.exec(params) | ||
parsed.timeCost = +timeCost[1] | ||
} | ||
if (!('parallelism' in options)) { | ||
const parallelism = /p=(\d+)/.exec(params) | ||
parsed.parallelism = +parallelism[1] | ||
} | ||
return resolve() | ||
}) | ||
}).then(() => { | ||
return new Promise((resolve, reject) => { | ||
if ('salt' in options) { | ||
return resolve() | ||
} | ||
const salt = sections[sections.length - 2] | ||
parsed.salt = Buffer.from(rightPad(salt), 'base64') | ||
return resolve() | ||
}) | ||
}).then(() => { | ||
options = Object.assign({}, defaults, parsed, options) | ||
return new Promise((resolve, reject) => { | ||
return bindings.hash(Buffer.from(plain), options, resolve, reject) | ||
}) | ||
}).then(raw => { | ||
const expected = sections[sections.length - 1] | ||
const base64hash = rightTrim(raw.toString('base64')) | ||
return Promise.resolve(base64hash === expected) | ||
}) | ||
} | ||
} | ||
for (const k of Object.keys(types)) { | ||
module.exports[k] = types[k] | ||
type2string[types[k]] = k | ||
} |
{ | ||
"name": "argon2", | ||
"version": "0.16.2", | ||
"version": "0.17.0", | ||
"description": "An Argon2 library for Node", | ||
@@ -9,4 +9,4 @@ "main": "index.js", | ||
"benchmark": "node benchmark.js", | ||
"clean": "node-gyp clean && rm -r .nyc_output", | ||
"test": "tsc -p . && node test-d.js && standard && nyc --reporter=lcov ava" | ||
"clean": "node-gyp clean && rm -rf coverage .nyc_output", | ||
"test": "tsc -p . && node test-d.js && standard index.js && nyc --reporter=lcov jest" | ||
}, | ||
@@ -36,6 +36,4 @@ "repository": { | ||
"devDependencies": { | ||
"@types/node": "^8.0.0", | ||
"ava": "^0.22.0", | ||
"babel-plugin-transform-async-to-generator": "^6.16.0", | ||
"mockery": "^2.0.0", | ||
"@types/node": "^9.3.0", | ||
"jest": "^22.0.6", | ||
"nyc": "^11.0.1", | ||
@@ -47,3 +45,3 @@ "sandra": "^0.2.1", | ||
"engines": { | ||
"node": ">=4.0.0" | ||
"node": ">=6.0.0" | ||
}, | ||
@@ -50,0 +48,0 @@ "ava": { |
# node-argon2 | ||
[![Greenkeeper badge](https://badges.greenkeeper.io/ranisalt/node-argon2.svg)](https://greenkeeper.io/) | ||
[![NPM package][npm-image]][npm-url] [![Coverage status][coverage-image]][coverage-url] [![Code Quality][codequality-image]][codequality-url] [![Dependencies][david-dm-image]][david-dm-url] [![Codewake][codewake-image]][codewake-url] | ||
[![NPM package][npm-image]][npm-url] [![Coverage status][coverage-image]][coverage-url] [![Code Quality][codequality-image]][codequality-url] [![Dependencies][david-dm-image]][david-dm-url] | ||
- Linux: [![Linux build status][travis-image]][travis-url] | ||
@@ -231,3 +231,1 @@ - Windows: [![Windows build status][appveyor-image]][appveyor-url] | ||
[david-dm-url]: https://david-dm.org/ranisalt/node-argon2 | ||
[codewake-image]: https://www.codewake.com/badges/ask_question_flat_square.svg | ||
[codewake-url]: https://www.codewake.com/p/node-argon2 |
242
test.js
@@ -1,9 +0,8 @@ | ||
'use strict' | ||
const test = require('ava') | ||
const mockery = require('mockery') | ||
let argon2, defaults, limits | ||
/* global expect, test */ | ||
const argon2 = require('argon2') | ||
const {defaults, limits} = argon2 | ||
const password = 'password' | ||
const salt = Buffer.alloc(16, 'salt') | ||
// Like argon2's modified base64 implementation, this function truncates any | ||
// Like argon2's modified base64 implementation, expect function truncates any | ||
// trailing '=' characters for a more compact representation. | ||
@@ -23,21 +22,4 @@ | ||
mockery.registerMock('crypto', { | ||
randomBytes (size, callback) { | ||
callback(null, Buffer.alloc(size, 'salt')) | ||
} | ||
}) | ||
test.before(() => { | ||
mockery.enable({useCleanCache: true, warnOnUnregistered: false}) | ||
argon2 = require('./') | ||
defaults = argon2.defaults | ||
limits = argon2.limits | ||
}) | ||
test.after(() => { | ||
mockery.disable() | ||
}) | ||
test('defaults', t => { | ||
t.deepEqual(defaults, { | ||
test('defaults', () => { | ||
expect(defaults).toEqual({ | ||
hashLength: 32, | ||
@@ -52,141 +34,183 @@ timeCost: 3, | ||
test('basic hash', async t => { | ||
t.is(await argon2.hash(password), hashes.argon2i) | ||
test('basic hash', () => { | ||
return argon2.hash(password, {salt}).then(hash => { | ||
expect(hash).toBe(hashes.argon2i) | ||
}) | ||
}) | ||
test('hash with null in password', async t => { | ||
t.is(await argon2.hash('pass\0word'), hashes.withNull) | ||
test('hash with null in password', () => { | ||
return argon2.hash('pass\0word', {salt}).then(hash => { | ||
expect(hash).toBe(hashes.withNull) | ||
}) | ||
}) | ||
test('with raw hash', async t => { | ||
t.is((await argon2.hash(password, {raw: true})).equals(hashes.rawArgon2i), true) | ||
test('with raw hash', () => { | ||
return argon2.hash(password, {raw: true, salt}).then(hash => { | ||
expect(hash).toEqual(hashes.rawArgon2i) | ||
}) | ||
}) | ||
test('with raw hash, null in password', async t => { | ||
t.is((await argon2.hash('pass\0word', {raw: true})).equals(hashes.rawWithNull), true) | ||
test('with raw hash, null in password', () => { | ||
return argon2.hash('pass\0word', {raw: true, salt}).then(hash => { | ||
expect(hash).toEqual(hashes.rawWithNull) | ||
}) | ||
}) | ||
test('hash with argon2d', async t => { | ||
t.is(await argon2.hash(password, {type: argon2.argon2d}), hashes.argon2d) | ||
test('hash with argon2d', () => { | ||
return argon2.hash(password, {type: argon2.argon2d, salt}).then(hash => { | ||
expect(hash).toBe(hashes.argon2d) | ||
}) | ||
}) | ||
test('argon2d with raw hash', async t => { | ||
t.is((await argon2.hash(password, {type: argon2.argon2d, raw: true})).equals(hashes.rawArgon2d), true) | ||
test('argon2d with raw hash', () => { | ||
return argon2.hash(password, {type: argon2.argon2d, raw: true, salt}).then(hash => { | ||
expect(hash).toEqual(hashes.rawArgon2d) | ||
}) | ||
}) | ||
test('hash with argon2id', async t => { | ||
t.is(await argon2.hash(password, {type: argon2.argon2id}), hashes.argon2id) | ||
test('hash with argon2id', () => { | ||
return argon2.hash(password, {type: argon2.argon2id, salt}).then(hash => { | ||
expect(hash).toBe(hashes.argon2id) | ||
}) | ||
}) | ||
test('argon2id with raw hash', async t => { | ||
t.is((await argon2.hash(password, {type: argon2.argon2id, raw: true})).equals(hashes.rawArgon2id), true) | ||
test('argon2id with raw hash', () => { | ||
return argon2.hash(password, {type: argon2.argon2id, raw: true, salt}).then(hash => { | ||
expect(hash).toEqual(hashes.rawArgon2id) | ||
}) | ||
}) | ||
test('hash with time cost', async t => { | ||
t.regex(await argon2.hash(password, {timeCost: 4}), /t=4/) | ||
test('hash with time cost', () => { | ||
return argon2.hash(password, {timeCost: 4}).then(hash => { | ||
expect(hash).toMatch(/t=4/) | ||
}) | ||
}) | ||
test('hash with low time cost', async t => { | ||
await t.throws(argon2.hash(password, {timeCost: limits.timeCost.min - 1}), /invalid timeCost.+between \d+ and \d+/i) | ||
test('hash with low time cost', () => { | ||
return argon2.hash(password, {timeCost: limits.timeCost.min - 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid timeCost.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with high time cost', async t => { | ||
await t.throws(argon2.hash(password, {timeCost: limits.timeCost.max + 1}), /invalid timeCost.+between \d+ and \d+/i) | ||
test('hash with high time cost', () => { | ||
return argon2.hash(password, {timeCost: limits.timeCost.max + 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid timeCost.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with hash length', async t => { | ||
test('hash with hash length', () => { | ||
// 4 bytes ascii == 6 bytes base64 | ||
t.regex(await argon2.hash(password, {hashLength: 4}), /\$\w{6}$/) | ||
return argon2.hash(password, {hashLength: 4}).catch(err => { | ||
expect(err.message).toMatch(/\$\w{6}$/) | ||
}) | ||
}) | ||
test('hash with low hash length', async t => { | ||
await t.throws(argon2.hash(password, {hashLength: limits.hashLength.min - 1}), /invalid hashLength.+between \d+ and \d+/i) | ||
test('hash with low hash length', () => { | ||
return argon2.hash(password, {hashLength: limits.hashLength.min - 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid hashLength.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with high hash length', async t => { | ||
await t.throws(argon2.hash(password, {hashLength: limits.hashLength.max + 1}), /invalid hashLength.+between \d+ and \d+/i) | ||
test('hash with high hash length', () => { | ||
return argon2.hash(password, {hashLength: limits.hashLength.max + 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid hashLength.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with memory cost', async t => { | ||
t.regex(await argon2.hash(password, {memoryCost: 13}), /m=8192/) | ||
test('hash with memory cost', () => { | ||
return argon2.hash(password, {memoryCost: 13}).then(hash => { | ||
expect(hash).toMatch(/m=8192/) | ||
}) | ||
}) | ||
test('hash with low memory cost', async t => { | ||
await t.throws(argon2.hash(password, {memoryCost: limits.memoryCost.min - 1}), /invalid memoryCost.+between \d+ and \d+/i) | ||
test('hash with low memory cost', () => { | ||
return argon2.hash(password, {memoryCost: limits.memoryCost.min - 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid memoryCost.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with high memory cost', async t => { | ||
await t.throws(argon2.hash(password, {memoryCost: limits.memoryCost.max + 1}), /invalid memoryCost.+between \d+ and \d+/i) | ||
test('hash with high memory cost', () => { | ||
return argon2.hash(password, {memoryCost: limits.memoryCost.max + 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid memoryCost.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with parallelism', async t => { | ||
t.regex(await argon2.hash(password, {parallelism: 2}), /p=2/) | ||
test('hash with parallelism', () => { | ||
return argon2.hash(password, {parallelism: 2}).then(hash => { | ||
expect(hash).toMatch(/p=2/) | ||
}) | ||
}) | ||
test('hash with low parallelism', async t => { | ||
await t.throws(argon2.hash(password, {parallelism: limits.parallelism.min - 1}), /invalid parallelism.+between \d+ and \d+/i) | ||
test('hash with low parallelism', () => { | ||
return argon2.hash(password, {parallelism: limits.parallelism.min - 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid parallelism.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with high parallelism', async t => { | ||
await t.throws(argon2.hash(password, {parallelism: limits.parallelism.max + 1}), /invalid parallelism.+between \d+ and \d+/i) | ||
test('hash with high parallelism', () => { | ||
return argon2.hash(password, {parallelism: limits.parallelism.max + 1}).catch(err => { | ||
expect(err.message).toMatch(/invalid parallelism.+between \d+ and \d+/i) | ||
}) | ||
}) | ||
test('hash with all options', async t => { | ||
t.regex(await argon2.hash(password, {timeCost: 4, memoryCost: 13, parallelism: 2}), /m=8192,t=4,p=2/) | ||
test('hash with all options', () => { | ||
return argon2.hash(password, {timeCost: 4, memoryCost: 13, parallelism: 2}).then(hash => { | ||
expect(hash).toMatch(/m=8192,t=4,p=2/) | ||
}) | ||
}) | ||
test('verify correct password', async t => { | ||
t.true(await argon2.verify(await argon2.hash(password), password)) | ||
test('verify correct password', () => { | ||
return argon2.hash(password).then(hash => { | ||
return argon2.verify(hash, password).then(matches => { | ||
expect(matches).toBeTruthy() | ||
}) | ||
}) | ||
}) | ||
test('verify wrong password', async t => { | ||
t.false(await argon2.verify(await argon2.hash(password), 'passworld')) | ||
test('verify wrong password', () => { | ||
return argon2.hash(password).then(hash => { | ||
return argon2.verify(hash, 'passworld').then(matches => { | ||
expect(matches).toBeFalsy() | ||
}) | ||
}) | ||
}) | ||
test('verify invalid hash', async t => { | ||
const hash = await argon2.hash(password) | ||
/* Cut just a piece of the hash making it invalid */ | ||
await t.throws(argon2.verify(hash.slice(8), password), /invalid hash.+generated by argon2/i) | ||
test('verify with null in password', () => { | ||
return argon2.hash('pass\0word').then(hash => { | ||
return argon2.verify(hash, 'pass\0word').then(matches => { | ||
expect(matches).toBeTruthy() | ||
}) | ||
}) | ||
}) | ||
test('verify with null in password', async t => { | ||
t.true(await argon2.verify(await argon2.hash('pass\0word'), 'pass\0word')) | ||
test('verify argon2d correct password', () => { | ||
return argon2.hash(password, {type: argon2.argon2d}).then(hash => { | ||
return argon2.verify(hash, password).then(matches => { | ||
expect(matches).toBeTruthy() | ||
}) | ||
}) | ||
}) | ||
test('verify argon2d correct password', async t => { | ||
t.true(await argon2.verify(await argon2.hash(password, {type: argon2.argon2d}), password)) | ||
test('verify argon2d wrong password', () => { | ||
return argon2.hash(password, {type: argon2.argon2d}).then(hash => { | ||
return argon2.verify(hash, 'passworld').then(matches => { | ||
expect(matches).toBeFalsy() | ||
}) | ||
}) | ||
}) | ||
test('verify argon2d wrong password', async t => { | ||
t.false(await argon2.verify(await argon2.hash(password, {type: argon2.argon2d}), 'passworld')) | ||
test('verify argon2id correct password', () => { | ||
return argon2.hash(password, {type: argon2.argon2id}).then(hash => { | ||
return argon2.verify(hash, password).then(matches => { | ||
expect(matches).toBeTruthy() | ||
}) | ||
}) | ||
}) | ||
test('verify argon2id correct password', async t => { | ||
t.true(await argon2.verify(await argon2.hash(password, {type: argon2.argon2id}), password)) | ||
test('verify argon2id wrong password', () => { | ||
return argon2.hash(password, {type: argon2.argon2id}).then(hash => { | ||
return argon2.verify(hash, 'passworld').then(matches => { | ||
expect(matches).toBeFalsy() | ||
}) | ||
}) | ||
}) | ||
test('verify argon2id wrong password', async t => { | ||
t.false(await argon2.verify(await argon2.hash(password, {type: argon2.argon2id}), 'passworld')) | ||
}) | ||
test('js promise + setInterval', async t => { | ||
const timer = setInterval(() => { | ||
/* istanbul ignore next */ | ||
t.fail('Interval expired first') | ||
}, 5e3) | ||
await argon2.hash(password) | ||
clearInterval(timer) | ||
t.pass() | ||
}) | ||
test('js promise + setTimeout', async t => { | ||
const timer = setTimeout(() => { | ||
/* istanbul ignore next */ | ||
t.fail('Timeout expired first') | ||
}, 5e3) | ||
await argon2.hash(password) | ||
clearTimeout(timer) | ||
t.pass() | ||
}) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
207349
6
326
28
231
2