diff --git a/crypto_pwhash.js b/crypto_pwhash.js new file mode 100644 index 0000000..b6151c8 --- /dev/null +++ b/crypto_pwhash.js @@ -0,0 +1,264 @@ +/* eslint-disable camelcase */ +const b4a = require('b4a') +const argon2 = require('../../wasm/argon2-wasm') +const { randombytes_buf } = require('./randombytes') + +const crypto_pwhash_argon2i_ALG_ARGON2I13 = 1 +const crypto_pwhash_argon2i_BYTES_MIN = 16 +const crypto_pwhash_argon2i_BYTES_MAX = 4294967295 +const crypto_pwhash_argon2i_PASSWD_MIN = 0 +const crypto_pwhash_argon2i_PASSWD_MAX = 4294967295 +const crypto_pwhash_argon2i_SALTBYTES = 16 +const crypto_pwhash_argon2i_STRBYTES = 128 +const crypto_pwhash_argon2i_STRPREFIX = '$argon2i$' +const crypto_pwhash_argon2i_OPSLIMIT_MIN = 3 +const crypto_pwhash_argon2i_OPSLIMIT_MAX = 4294967295 +const crypto_pwhash_argon2i_MEMLIMIT_MIN = 8192 +const crypto_pwhash_argon2i_MEMLIMIT_MAX = 2147483648 +const crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE = 4 +const crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE = 33554432 +const crypto_pwhash_argon2i_OPSLIMIT_MODERATE = 6 +const crypto_pwhash_argon2i_MEMLIMIT_MODERATE = 134217728 +const crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE = 8 +const crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE = 536870912 + +const crypto_pwhash_argon2id_ALG_ARGON2ID13 = 2 +const crypto_pwhash_argon2id_BYTES_MIN = 16 +const crypto_pwhash_argon2id_BYTES_MAX = 4294967295 +const crypto_pwhash_argon2id_PASSWD_MIN = 0 +const crypto_pwhash_argon2id_PASSWD_MAX = 4294967295 +const crypto_pwhash_argon2id_SALTBYTES = 16 +const crypto_pwhash_argon2id_STRBYTES = 128 +const crypto_pwhash_argon2id_STRPREFIX = '$argon2id$' +const crypto_pwhash_argon2id_OPSLIMIT_MIN = 1 +const crypto_pwhash_argon2id_OPSLIMIT_MAX = 4294967295 +const crypto_pwhash_argon2id_MEMLIMIT_MIN = 8192 +const crypto_pwhash_argon2id_MEMLIMIT_MAX = 2147483648 +const crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE = 2 +const crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE = 67108864 +const crypto_pwhash_argon2id_OPSLIMIT_MODERATE = 3 +const crypto_pwhash_argon2id_MEMLIMIT_MODERATE = 268435456 +const crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE = 4 +const crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE = 1073741824 + +const crypto_pwhash_ALG_ARGON2I13 = crypto_pwhash_argon2i_ALG_ARGON2I13 +const crypto_pwhash_ALG_ARGON2ID13 = crypto_pwhash_argon2id_ALG_ARGON2ID13 +const crypto_pwhash_ALG_DEFAULT = crypto_pwhash_ALG_ARGON2ID13 +const crypto_pwhash_BYTES_MIN = crypto_pwhash_argon2id_BYTES_MIN +const crypto_pwhash_BYTES_MAX = crypto_pwhash_argon2id_BYTES_MAX +const crypto_pwhash_PASSWD_MIN = crypto_pwhash_argon2id_PASSWD_MIN +const crypto_pwhash_PASSWD_MAX = crypto_pwhash_argon2id_PASSWD_MAX +const crypto_pwhash_SALTBYTES = crypto_pwhash_argon2id_SALTBYTES +const crypto_pwhash_STRBYTES = crypto_pwhash_argon2id_STRBYTES +const crypto_pwhash_STRPREFIX = crypto_pwhash_argon2id_STRPREFIX +const crypto_pwhash_OPSLIMIT_MIN = crypto_pwhash_argon2id_OPSLIMIT_MIN +const crypto_pwhash_OPSLIMIT_MAX = crypto_pwhash_argon2id_OPSLIMIT_MAX +const crypto_pwhash_MEMLIMIT_MIN = crypto_pwhash_argon2id_MEMLIMIT_MIN +const crypto_pwhash_MEMLIMIT_MAX = crypto_pwhash_argon2id_MEMLIMIT_MAX +const crypto_pwhash_OPSLIMIT_INTERACTIVE = crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE +const crypto_pwhash_MEMLIMIT_INTERACTIVE = crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE +const crypto_pwhash_OPSLIMIT_MODERATE = crypto_pwhash_argon2id_OPSLIMIT_MODERATE +const crypto_pwhash_MEMLIMIT_MODERATE = crypto_pwhash_argon2id_MEMLIMIT_MODERATE +const crypto_pwhash_OPSLIMIT_SENSITIVE = crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE +const crypto_pwhash_MEMLIMIT_SENSITIVE = crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE + +module.exports = { + crypto_pwhash_ALG_ARGON2I13, + crypto_pwhash_ALG_ARGON2ID13, + crypto_pwhash_ALG_DEFAULT, + crypto_pwhash_BYTES_MIN, + crypto_pwhash_BYTES_MAX, + crypto_pwhash_PASSWD_MIN, + crypto_pwhash_PASSWD_MAX, + crypto_pwhash_SALTBYTES, + crypto_pwhash_STRBYTES, + crypto_pwhash_STRPREFIX, + crypto_pwhash_OPSLIMIT_MIN, + crypto_pwhash_OPSLIMIT_MAX, + crypto_pwhash_MEMLIMIT_MIN, + crypto_pwhash_MEMLIMIT_MAX, + crypto_pwhash_OPSLIMIT_INTERACTIVE, + crypto_pwhash_MEMLIMIT_INTERACTIVE, + crypto_pwhash_OPSLIMIT_MODERATE, + crypto_pwhash_MEMLIMIT_MODERATE, + crypto_pwhash_OPSLIMIT_SENSITIVE, + crypto_pwhash_MEMLIMIT_SENSITIVE, + crypto_pwhash_argon2i, + crypto_pwhash_argon2id, + crypto_pwhash_argon2i_str, + crypto_pwhash_argon2id_str, + crypto_pwhash, + crypto_pwhash_str, + crypto_pwhash_str_verify, + crypto_pwhash_str_needs_rehash +} + +function crypto_pwhash_argon2i (out, passwd, salt, passes, memory, alg) { + const outlen = out.byteLength + const passwdlen = passwd.byteLength + out.fill(0) + + if (outlen > crypto_pwhash_argon2i_BYTES_MAX) { + throw new Error('Too large') + } + + if (outlen < crypto_pwhash_argon2i_BYTES_MIN) { + throw new Error('Invalid opts') + } + + if (passwdlen > crypto_pwhash_argon2i_PASSWD_MAX || + passes > crypto_pwhash_argon2i_OPSLIMIT_MAX || + memory > crypto_pwhash_argon2i_MEMLIMIT_MAX) { + throw new Error('Too large') + } + + if (passwdlen < crypto_pwhash_argon2i_PASSWD_MIN || + (passes && passes < crypto_pwhash_argon2i_OPSLIMIT_MIN) || + (memory && memory < crypto_pwhash_argon2i_MEMLIMIT_MIN)) { + throw new Error('Invalid opts') + } + + switch (alg) { + case crypto_pwhash_argon2i_ALG_ARGON2I13: { + const buf = argon2(passwd, salt, null, null, 'binary', { memory: memory >> 10, passes, outlen, type: argon2.ARGON2I }) + out.set(buf) + return 0 + } + + default: + throw new Error('Invalid input') + } +} + +function crypto_pwhash_argon2id (out, passwd, salt, passes, memory, alg) { + const outlen = out.byteLength + const passwdlen = passwd.byteLength + out.fill(0) + + if (outlen > crypto_pwhash_argon2id_BYTES_MAX) { + throw new Error('Too large') + } + + if (outlen < crypto_pwhash_argon2id_BYTES_MIN) { + throw new Error('Invalid opts') + } + + if (passwdlen > crypto_pwhash_argon2id_PASSWD_MAX || + passes > crypto_pwhash_argon2id_OPSLIMIT_MAX || + memory > crypto_pwhash_argon2id_MEMLIMIT_MAX) { + throw new Error('Too large') + } + + if (passwdlen < crypto_pwhash_argon2id_PASSWD_MIN || + (passes && passes < crypto_pwhash_argon2id_OPSLIMIT_MIN) || + (memory && memory < crypto_pwhash_argon2id_MEMLIMIT_MIN)) { + throw new Error('Invalid opts') + } + + switch (alg) { + case crypto_pwhash_argon2id_ALG_ARGON2ID13: { + const buf = argon2(passwd, salt, null, null, 'binary', { memory: memory >> 10, passes, outlen, type: argon2.ARGON2ID }) + out.set(buf) + return 0 + } + + default: + throw new Error('Invalid input') + } +} + +function crypto_pwhash_argon2i_str (out, passwd, salt, passes, memory, alg) { + const outlen = 32 + const passwdlen = passwd.byteLength + + if (passwdlen > crypto_pwhash_argon2i_PASSWD_MAX || + passes > crypto_pwhash_argon2i_OPSLIMIT_MAX || + memory > crypto_pwhash_argon2i_MEMLIMIT_MAX) { + throw new Error('Too large') + } + + if (passwdlen < crypto_pwhash_argon2i_PASSWD_MIN || + (passes && passes < crypto_pwhash_argon2i_OPSLIMIT_MIN) || + (memory && memory < crypto_pwhash_argon2i_MEMLIMIT_MIN)) { + throw new Error('Invalid opts') + } + + switch (alg) { + case crypto_pwhash_argon2i_ALG_ARGON2I13: + return argon2(passwd, salt, null, null, { memory: memory >> 10, passes, outlen, type: argon2.ARGON2I }) + + default: + throw new Error('Invalid input') + } +} + +function crypto_pwhash_argon2id_str (out, passwd, salt, passes, memory, alg) { + const outlen = 32 + const passwdlen = passwd.byteLength + + if (passwdlen > crypto_pwhash_argon2id_PASSWD_MAX || + passes > crypto_pwhash_argon2id_OPSLIMIT_MAX || + memory > crypto_pwhash_argon2id_MEMLIMIT_MAX) { + throw new Error('Too large') + } + + if (passwdlen < crypto_pwhash_argon2id_PASSWD_MIN || + (passes && passes < crypto_pwhash_argon2id_OPSLIMIT_MIN) || + (memory && memory < crypto_pwhash_argon2id_MEMLIMIT_MIN)) { + throw new Error('Invalid opts') + } + + switch (alg) { + case crypto_pwhash_argon2id_ALG_ARGON2ID13: + out.write(argon2(passwd, salt, null, null, { memory: memory >> 10, passes, outlen, type: argon2.ARGON2ID })) + return + + default: + throw new Error('Invalid input') + } +} + +function crypto_pwhash (out, passwd, salt, opslimit, memlimit, alg) { + switch (alg) { + case crypto_pwhash_argon2i_ALG_ARGON2I13: + return crypto_pwhash_argon2i(out, passwd, salt, opslimit, memlimit, alg) + + case crypto_pwhash_argon2id_ALG_ARGON2ID13: + return crypto_pwhash_argon2id(out, passwd, salt, opslimit, memlimit, alg) + } +} + +function crypto_pwhash_str (out, passwd, opslimit, memlimit) { + const salt = b4a.alloc(crypto_pwhash_SALTBYTES) + randombytes_buf(salt) + + return crypto_pwhash_argon2id_str(out, passwd, salt, opslimit, memlimit, crypto_pwhash_ALG_DEFAULT) +} + +function crypto_pwhash_str_verify (str, passwd) { + if (b4a.isBuffer(str)) return crypto_pwhash_str_verify(str.toString(), passwd) + + if (str.slice(0, crypto_pwhash_argon2id_STRPREFIX.length) === crypto_pwhash_argon2id_STRPREFIX) { + return argon2.verify(str, passwd, null) + } + + if (str.slice(0, crypto_pwhash_argon2i_STRPREFIX.length) === crypto_pwhash_argon2i_STRPREFIX) { + return argon2.verify(str, passwd, null) + } + + return false +} + +function crypto_pwhash_str_needs_rehash (str, opslimit, memlimit, type) { + memlimit >>= 10 + + if ((opslimit | memlimit) > 0xffffffff || (str.byteLength > crypto_pwhash_STRBYTES)) { + throw new Error('Invalid opts.') + } + + try { + return argon2.needsRehash(str, opslimit, memlimit, type) + } catch { + return true + } +} diff --git a/index.js b/index.js index 2454419..9c51f7c 100644 --- a/index.js +++ b/index.js @@ -12,6 +12,7 @@ forward(require('./randombytes')) forward(require('./memory')) forward(require('./helpers')) forward(require('./utils')) +forward(require('./crypto_aead')) forward(require('./crypto_auth')) forward(require('./crypto_box')) forward(require('./crypto_core')) @@ -21,7 +22,7 @@ forward(require('./crypto_hash')) forward(require('./crypto_hash_sha256')) forward(require('./crypto_kdf')) forward(require('./crypto_kx')) -forward(require('./crypto_aead')) +forward(require('./crypto_pwhash')) forward(require('./crypto_onetimeauth')) forward(require('./crypto_scalarmult_ed25519')) // forward(require('./crypto_scalarmult')) diff --git a/package.json b/package.json index 1bd9fb5..ef1d921 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "WIP - a pure javascript version of sodium-native", "main": "index.js", "dependencies": { + "argon2-wasm": "^0.9.0", "blake2b": "^2.1.1", "chacha20-universal": "^1.0.4", "nanoassert": "^2.0.0",