Comparing version 0.15.0 to 0.16.0
@@ -57,3 +57,3 @@ # Argon2 | ||
``` | ||
Usage: ./argon2 [-h] salt [-i|-d|-id] [-t iterations] [-m memory] [-p parallelism] [-l hash length] [-e|-r] [-v (10|13)] | ||
Usage: ./argon2 [-h] salt [-i|-d|-id] [-t iterations] [-m memory] [-p parallelism] [-l hash length] [-e|-r] | ||
Password is read from stdin | ||
@@ -71,3 +71,2 @@ Parameters: | ||
-r Output only the raw bytes of the hash | ||
-v (10|13) Argon2 version (defaults to the most recent version, currently 13) | ||
-h Print argon2 usage | ||
@@ -267,3 +266,3 @@ ``` | ||
## Test suite | ||
## Test Suite | ||
@@ -270,0 +269,0 @@ There are two sets of test suites. One is a low level test for the hash |
@@ -32,4 +32,3 @@ // Type definitions for argon2 v0.14.0 | ||
export const limits: OptionLimits; | ||
export function hash(plain: Buffer | string, salt: Buffer, options?: Options): Promise<string>; | ||
export function generateSalt(length?: number): Promise<Buffer>; | ||
export function hash(plain: Buffer | string, options?: Options): Promise<string>; | ||
export function verify(hash: string, plain: Buffer | string): Promise<boolean>; |
44
index.js
@@ -21,19 +21,2 @@ 'use strict' | ||
const validate = (salt, options) => new Promise((resolve, reject) => { | ||
if (salt.length < 8) { | ||
reject(new Error('Invalid salt, must be a buffer with 8 or more bytes.')) | ||
} | ||
for (let key of Object.keys(limits)) { | ||
const max = limits[key].max | ||
const min = limits[key].min | ||
const value = options[key] | ||
if (value > max || value < min) { | ||
reject(new Error(`Invalid ${key}, must be between ${min} and ${max}.`)) | ||
} | ||
} | ||
resolve() | ||
}) | ||
module.exports = { | ||
@@ -46,19 +29,20 @@ defaults, | ||
hash(plain, salt, options) { | ||
salt = new Buffer(salt) | ||
hash (plain, options) { | ||
options = Object.assign({}, defaults, options) | ||
return validate(salt, options).then(() => new Promise((resolve, reject) => { | ||
bindings.hash(new Buffer(plain), salt, options, resolve, reject) | ||
})) | ||
}, | ||
return new Promise((resolve, reject) => { | ||
for (let key of Object.keys(limits)) { | ||
const max = limits[key].max | ||
const min = limits[key].min | ||
const value = options[key] | ||
if (value > max || value < min) { | ||
reject(new Error(`Invalid ${key}, must be between ${min} and ${max}.`)) | ||
} | ||
} | ||
generateSalt(length) { | ||
return new Promise((resolve, reject) => { | ||
crypto.randomBytes(length || 16, (err, salt) => { | ||
/* istanbul ignore if */ | ||
crypto.randomBytes(16, (err, salt) => { | ||
if (err) { | ||
reject(err) | ||
} | ||
resolve(salt) | ||
bindings.hash(Buffer.from(plain), salt, options, resolve, reject) | ||
}) | ||
@@ -68,3 +52,3 @@ }) | ||
verify(hash, plain) { | ||
verify (hash, plain) { | ||
if (!/^\$argon2(i|d|id)(\$v=\d+)?\$m=\d+,t=\d+,p=\d+(?:\$[\w+/]+){2}$/.test(hash)) { | ||
@@ -75,5 +59,5 @@ return Promise.reject(new Error('Invalid hash, must be in MCF, generated by Argon2.')) | ||
return new Promise((resolve, reject) => { | ||
bindings.verify(new Buffer(hash), new Buffer(plain), resolve, reject) | ||
bindings.verify(Buffer.from(hash), Buffer.from(plain), resolve, reject) | ||
}) | ||
} | ||
} |
{ | ||
"name": "argon2", | ||
"version": "0.15.0", | ||
"version": "0.16.0", | ||
"description": "An Argon2 library for Node", | ||
@@ -8,5 +8,5 @@ "main": "index.js", | ||
"scripts": { | ||
"benchmark": "babel-node benchmark.js", | ||
"benchmark": "node benchmark.js", | ||
"clean": "node-gyp clean", | ||
"test": "tsc -p . && node test-d.js && xo && nyc --reporter=lcov ava" | ||
"test": "tsc -p . && node test-d.js && standard && nyc --reporter=lcov ava" | ||
}, | ||
@@ -36,13 +36,10 @@ "repository": { | ||
"devDependencies": { | ||
"@types/node": "^6.0.42", | ||
"ava": "^0.17.0", | ||
"babel-cli": "^6.18.0", | ||
"@types/node": "^7.0.5", | ||
"ava": "^0.19.1", | ||
"babel-plugin-transform-async-to-generator": "^6.16.0", | ||
"babel-preset-es2015": "^6.18.0", | ||
"babel-register": "^6.18.0", | ||
"nyc": "^10.0.0", | ||
"object-assign": "^4.1.0", | ||
"sandra": "^0.1.0", | ||
"typescript": "^2.0.3", | ||
"xo": "^0.17.1" | ||
"mockery": "^2.0.0", | ||
"nyc": "^11.0.1", | ||
"sandra": "^0.2.1", | ||
"standard": "^10.0.2", | ||
"typescript": "^2.1.5" | ||
}, | ||
@@ -56,16 +53,4 @@ "engines": { | ||
], | ||
"babel": "inherit", | ||
"require": [ | ||
"babel-register" | ||
] | ||
}, | ||
"xo": { | ||
"esnext": true, | ||
"rules": { | ||
"no-div-regex": "off", | ||
"prefer-const": "off" | ||
}, | ||
"semicolon": false, | ||
"space": 2 | ||
"babel": "inherit" | ||
} | ||
} |
# 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] | ||
@@ -3,0 +5,0 @@ - Linux: [![Linux build status][travis-image]][travis-url] |
130
test.js
@@ -1,23 +0,40 @@ | ||
import test from 'ava' | ||
import argon2, {defaults, limits} from './' | ||
'use strict' | ||
const test = require('ava') | ||
const mockery = require('mockery') | ||
let argon2, defaults, limits | ||
const password = 'password' | ||
const salt = new Buffer('somesalt') | ||
const saltWithNull = new Buffer('\0abcdefghijklmno') | ||
// Like argon2's modified base64 implementation, this function truncates any | ||
// trailing '=' characters for a more compact representation. | ||
const truncatedBase64 = buffer => buffer.toString('base64').replace(/=*$/, '') | ||
// hashes for argon2i and argon2d with default options | ||
const hashes = Object.freeze({ | ||
argon2i: '$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$iWh06vD8Fy27wf9npn6FXWiCX4K6pW6Ue1Bnzz07Z8A', | ||
argon2d: '$argon2d$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$2+JCoQtY/2x5F0VB9pEVP3xBNguWP1T25Ui0PtZuk8o', | ||
argon2id: '$argon2id$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$qLml5cbqFAO6YxVHhrSBHP0UWdxrIxkNcM8aMX3blzU', | ||
withNull: '$argon2i$v=19$m=4096,t=3,p=1$c29tZXNhbHQ$gk27gZBfGSSQTGxrg0xP9BjOw1pY1QMEdLcNe+t6N8Q', | ||
raw: Buffer.from('iWh06vD8Fy27wf9npn6FXWiCX4K6pW6Ue1Bnzz07Z8A', 'base64'), | ||
rawWithNull: Buffer.from('gk27gZBfGSSQTGxrg0xP9BjOw1pY1QMEdLcNe+t6N8Q', 'base64'), | ||
rawArgon2d: Buffer.from('2+JCoQtY/2x5F0VB9pEVP3xBNguWP1T25Ui0PtZuk8o', 'base64') | ||
argon2i: '$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0c2FsdA$Iv3dSMJ431p24TEj68Kxokm/ilAC9HfwREDIVPM/1/0', | ||
withNull: '$argon2i$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0c2FsdA$Z3fEValT7xBg6b585WOlY2gufWl95ZfkFA8mPtWJ3UM', | ||
argon2d: '$argon2d$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0c2FsdA$3CYaDoobFaprD02HTMVVRLsrSgJjZK5QmqYWnWDEAlw', | ||
argon2id: '$argon2id$v=19$m=4096,t=3,p=1$c2FsdHNhbHRzYWx0c2FsdA$fxbFVdPGPQ1NJoy87CaTabyrXOKZepZ9SGBFwPkPJ28', | ||
rawArgon2i: Buffer.from('22fddd48c278df5a76e13123ebc2b1a249bf8a5002f477f04440c854f33fd7fd', 'hex'), | ||
rawWithNull: Buffer.from('6777c455a953ef1060e9be7ce563a563682e7d697de597e4140f263ed589dd43', 'hex'), | ||
rawArgon2d: Buffer.from('dc261a0e8a1b15aa6b0f4d874cc55544bb2b4a026364ae509aa6169d60c4025c', 'hex'), | ||
rawArgon2id: Buffer.from('7f16c555d3c63d0d4d268cbcec269369bcab5ce2997a967d486045c0f90f276f', 'hex') | ||
}) | ||
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 => { | ||
@@ -35,56 +52,43 @@ t.deepEqual(defaults, { | ||
test('basic hash', async t => { | ||
t.is(await argon2.hash(password, salt), hashes.argon2i) | ||
t.is(await argon2.hash(password), hashes.argon2i) | ||
}) | ||
test('hash with null in password', async t => { | ||
t.is(await argon2.hash('pass\0word', salt), hashes.withNull) | ||
t.is(await argon2.hash('pass\0word'), hashes.withNull) | ||
}) | ||
test('hash with null in salt', async t => { | ||
const hash = await argon2.hash(password, saltWithNull) | ||
const paramsLen = '$argon2i$v=19$m=4096,t=3,p=1$'.length | ||
t.is(hash.substring(paramsLen, paramsLen + 22), truncatedBase64(saltWithNull)) | ||
}) | ||
test('with raw hash', async t => { | ||
t.is((await argon2.hash(password, salt, {raw: true})).equals(hashes.raw), true) | ||
t.is((await argon2.hash(password, {raw: true})).equals(hashes.rawArgon2i), true) | ||
}) | ||
test('with raw hash, null in password', async t => { | ||
t.is((await argon2.hash('pass\0word', salt, {raw: true})).equals(hashes.rawWithNull), true) | ||
t.is((await argon2.hash('pass\0word', {raw: true})).equals(hashes.rawWithNull), true) | ||
}) | ||
test('hash with longer salt', async t => { | ||
/* Intentionally using a length that is not multiple of 3 */ | ||
const hash = await argon2.hash('password', await argon2.generateSalt(500)) | ||
t.regex(hash, /.*\$.{667}\$/) | ||
t.true(await argon2.verify(hash, 'password')) | ||
}) | ||
test('hash with argon2d', async t => { | ||
t.is(await argon2.hash(password, salt, {type: argon2.argon2d}), hashes.argon2d) | ||
t.is(await argon2.hash(password, {type: argon2.argon2d}), hashes.argon2d) | ||
}) | ||
test('argon2d with raw hash', async t => { | ||
t.is((await argon2.hash(password, salt, {type: argon2.argon2d, raw: true})).equals(hashes.rawArgon2d), true) | ||
t.is((await argon2.hash(password, {type: argon2.argon2d, raw: true})).equals(hashes.rawArgon2d), true) | ||
}) | ||
test('hash with argon2id', async t => { | ||
t.is(await argon2.hash(password, salt, {type: argon2.argon2id}), hashes.argon2id) | ||
t.is(await argon2.hash(password, {type: argon2.argon2id}), hashes.argon2id) | ||
}) | ||
test('hash with short salt', async t => { | ||
t.throws(argon2.hash(password, salt.slice(0, 7)), /invalid salt.+with 8 or more bytes/i) | ||
test('argon2id with raw hash', async t => { | ||
t.is((await argon2.hash(password, {type: argon2.argon2id, raw: true})).equals(hashes.rawArgon2id), true) | ||
}) | ||
test('hash with time cost', async t => { | ||
t.regex(await argon2.hash(password, salt, {timeCost: 4}), /t=4/) | ||
t.regex(await argon2.hash(password, {timeCost: 4}), /t=4/) | ||
}) | ||
test('hash with low time cost', async t => { | ||
t.throws(argon2.hash(password, salt, {timeCost: limits.timeCost.min - 1}), /invalid timeCost.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {timeCost: limits.timeCost.min - 1}), /invalid timeCost.+between \d+ and \d+/i) | ||
}) | ||
test('hash with high time cost', async t => { | ||
t.throws(argon2.hash(password, salt, {timeCost: limits.timeCost.max + 1}), /invalid timeCost.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {timeCost: limits.timeCost.max + 1}), /invalid timeCost.+between \d+ and \d+/i) | ||
}) | ||
@@ -94,81 +98,73 @@ | ||
// 4 bytes ascii == 6 bytes base64 | ||
t.regex(await argon2.hash(password, salt, {hashLength: 4}), /\$\w{6}$/) | ||
t.regex(await argon2.hash(password, {hashLength: 4}), /\$\w{6}$/) | ||
}) | ||
test('hash with low hash length', async t => { | ||
t.throws(argon2.hash(password, salt, {hashLength: limits.hashLength.min - 1}), /invalid hashLength.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {hashLength: limits.hashLength.min - 1}), /invalid hashLength.+between \d+ and \d+/i) | ||
}) | ||
test('hash with high hash length', async t => { | ||
t.throws(argon2.hash(password, salt, {hashLength: limits.hashLength.max + 1}), /invalid hashLength.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {hashLength: limits.hashLength.max + 1}), /invalid hashLength.+between \d+ and \d+/i) | ||
}) | ||
test('hash with memory cost', async t => { | ||
t.regex(await argon2.hash(password, salt, {memoryCost: 13}), /m=8192/) | ||
t.regex(await argon2.hash(password, {memoryCost: 13}), /m=8192/) | ||
}) | ||
test('hash with low memory cost', async t => { | ||
t.throws(argon2.hash(password, salt, {memoryCost: limits.memoryCost.min - 1}), /invalid memoryCost.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {memoryCost: limits.memoryCost.min - 1}), /invalid memoryCost.+between \d+ and \d+/i) | ||
}) | ||
test('hash with high memory cost', async t => { | ||
t.throws(argon2.hash(password, salt, {memoryCost: limits.memoryCost.max + 1}), /invalid memoryCost.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {memoryCost: limits.memoryCost.max + 1}), /invalid memoryCost.+between \d+ and \d+/i) | ||
}) | ||
test('hash with parallelism', async t => { | ||
t.regex(await argon2.hash(password, salt, {parallelism: 2}), /p=2/) | ||
t.regex(await argon2.hash(password, {parallelism: 2}), /p=2/) | ||
}) | ||
test('hash with low parallelism', async t => { | ||
t.throws(argon2.hash(password, salt, {parallelism: limits.parallelism.min - 1}), /invalid parallelism.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {parallelism: limits.parallelism.min - 1}), /invalid parallelism.+between \d+ and \d+/i) | ||
}) | ||
test('hash with high parallelism', async t => { | ||
t.throws(argon2.hash(password, salt, {parallelism: limits.parallelism.max + 1}), /invalid parallelism.+between \d+ and \d+/i) | ||
await t.throws(argon2.hash(password, {parallelism: limits.parallelism.max + 1}), /invalid parallelism.+between \d+ and \d+/i) | ||
}) | ||
test('hash with all options', async t => { | ||
t.regex(await argon2.hash(password, salt, {timeCost: 4, memoryCost: 13, parallelism: 2}), /m=8192,t=4,p=2/) | ||
t.regex(await argon2.hash(password, {timeCost: 4, memoryCost: 13, parallelism: 2}), /m=8192,t=4,p=2/) | ||
}) | ||
test('async generate salt with default length', async t => { | ||
t.is((await argon2.generateSalt()).length, 16) | ||
}) | ||
test('async generate salt with specified length', async t => { | ||
t.is((await argon2.generateSalt(32)).length, 32) | ||
}) | ||
test('verify correct password', async t => { | ||
t.true(await argon2.verify(await argon2.hash(password, await argon2.generateSalt()), password)) | ||
t.true(await argon2.verify(await argon2.hash(password), password)) | ||
}) | ||
test('verify wrong password', async t => { | ||
t.false(await argon2.verify(await argon2.hash(password, await argon2.generateSalt()), 'passworld')) | ||
t.false(await argon2.verify(await argon2.hash(password), 'passworld')) | ||
}) | ||
test('verify invalid hash', async t => { | ||
const hash = await argon2.hash(password, await argon2.generateSalt()) | ||
const hash = await argon2.hash(password) | ||
/* Cut just a piece of the hash making it invalid */ | ||
t.throws(argon2.verify(hash.slice(8), password), /invalid hash.+generated by argon2/i) | ||
await t.throws(argon2.verify(hash.slice(8), password), /invalid hash.+generated by argon2/i) | ||
}) | ||
test('verify with null in password', async t => { | ||
t.true(await argon2.verify(await argon2.hash('pass\0word', await argon2.generateSalt()), 'pass\0word')) | ||
t.true(await argon2.verify(await argon2.hash('pass\0word'), 'pass\0word')) | ||
}) | ||
test('verify argon2d correct password', async t => { | ||
t.true(await argon2.verify(await argon2.hash(password, await argon2.generateSalt(), {type: argon2.argon2d}), password)) | ||
t.true(await argon2.verify(await argon2.hash(password, {type: argon2.argon2d}), password)) | ||
}) | ||
test('verify argon2d wrong password', async t => { | ||
t.false(await argon2.verify(await argon2.hash(password, await argon2.generateSalt(), {type: argon2.argon2d}), 'passworld')) | ||
t.false(await argon2.verify(await argon2.hash(password, {type: argon2.argon2d}), 'passworld')) | ||
}) | ||
test('verify argon2id correct password', async t => { | ||
t.true(await argon2.verify(await argon2.hash(password, await argon2.generateSalt(), {type: argon2.argon2id}), password)) | ||
t.true(await argon2.verify(await argon2.hash(password, {type: argon2.argon2id}), password)) | ||
}) | ||
test('verify argon2id wrong password', async t => { | ||
t.false(await argon2.verify(await argon2.hash(password, await argon2.generateSalt(), {type: argon2.argon2id}), 'passworld')) | ||
t.false(await argon2.verify(await argon2.hash(password, {type: argon2.argon2id}), 'passworld')) | ||
}) | ||
@@ -182,4 +178,5 @@ | ||
await argon2.hash(password, salt) | ||
await argon2.hash(password) | ||
clearInterval(timer) | ||
t.pass() | ||
}) | ||
@@ -193,4 +190,5 @@ | ||
await argon2.hash(password, salt) | ||
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
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
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
8
262
189241
227