ranisalt / node-argon2

Node.js bindings for Argon2 hashing algorithm
https://www.npmjs.com/package/argon2
MIT License
1.86k stars 92 forks source link

How to pass a custom salt? #311

Closed hayr-hotoca closed 3 years ago

hayr-hotoca commented 3 years ago

Welcome to the issues section if it's your first time!

Before creating an issue, please be sure to:

Steps to reproduce

const salt = CryptoJS.lib.WordArray.random(256 / 8);
  const hashSalt = CryptoJS.enc.Hex.stringify(salt);
  const saltBuf = Buffer.from(hashSalt, "hex");

  const hash = await argon2.hash(str, {
    salt: saltBuf,
    type: argon2.argon2id,
    memoryCost: 2**16,
    hashLength: 32,
    parallelism: 1,
    timeCost: 3,
    saltLength: 32,
});

Expected behaviour

Pass a 256bit key as a salt

Actual behaviour

Error: Invalid argument

Environment

Operating system: Windows 10

Node version: 14.16.1

Compiler version:

ranisalt commented 3 years ago

Do you need to pass a specific salt? If you leave it empty, the library will generate one for you, a random, cryptographically secure one using crypto.randomBytes.

By the way, you don't need to pass the parameters if you are using the defaults, and definitely don't pass both salt and saltLength, as salt will take precedence. saltLength is meant to select the size of the generated salt, not to indicate the length of the salt param.

Check the wiki for options, it may help you use them

VityaSchel commented 3 years ago

Do you need to pass a specific salt? If you leave it empty, the library will generate one for you, a random, cryptographically secure one using crypto.randomBytes.

By the way, you don't need to pass the parameters if you are using the defaults, and definitely don't pass both salt and saltLength, as salt will take precedence. saltLength is meant to select the size of the generated salt, not to indicate the length of the salt param.

Check the wiki for options, it may help you use them

Yes I need and I can do this via cli, but cannot do via js module. Wtf??

echo -n "this_is_being_hashed" | argon2-cli "custom_salt" -e
VityaSchel commented 3 years ago

Simplier example which gives Error: Invalid argument:

const argon2 = require('argon2')
argon2.hash('hello', { salt: 'salt' }).then(console.log)
(node:65891) UnhandledPromiseRejectionWarning: Error: Invalid argument
    at internal/util.js:308:30
    at new Promise (<anonymous>)
    at internal/util.js:307:12
    at Object.hash (.../node_modules/argon2/argon2.js:38:22)

Only workaround I can think of is to use cli through nodejs 💩

const { spawn } = require('child_process')
const argon2cli = spawn('argon2-cli', ['custom_salt', '-e'])
argon2cli.stdout.on('data', data => console.log(data.toString().trimEnd()))
argon2cli.stdin.write('this_is_being_hashed')
argon2cli.stdin.end()

I used the following in my app:

import { spawn } from 'child_process'

function hash(text, salt) {
  return new Promise(resolve => {
    const argon2cli = spawn('argon2-cli', [salt, '-e'])
    argon2cli.stdout.on('data', data => resolve(data.toString().trimEnd()))
    argon2cli.stdin.write(text)
    argon2cli.stdin.end()
  })
}

Usage example:

console.log(await hash('text', 'salt'))
ranisalt commented 3 years ago

Simplier example which gives Error: Invalid argument:

In your example, the issue is that it is using a string as the salt, but it must be a Buffer. If you do { salt: Buffer.from('salt') }, it will work just fine 😉

I will update the docs to reflect that the salt is binary, not text.

Yes I need and I can do this via cli, but cannot do via js module. Wtf??

Please note that I always ask this as a disclaimer because virtually everybody asking how to use a custom salt here is misunderstanding how salts work.

aboveyunhai commented 2 years ago

Do you need to pass a specific salt? If you leave it empty, the library will generate one for you, a random, cryptographically secure one using crypto.randomBytes.

By the way, you don't need to pass the parameters if you are using the defaults, and definitely don't pass both salt and saltLength, as salt will take precedence. saltLength is meant to select the size of the generated salt, not to indicate the length of the salt param.

Check the wiki for options, it may help you use them

@ranisalt I just cannot find a right place to ask my naïve question, so I just continue from this issue. I always used the default functions argon2.hash(str) without any params, and save the hash only. Since you mention the lib will generate a random for me, and without actually saving the generated "salt" into database, how can the lib is able to recompute the same hash next time to match the hash in database via argon2.verify(hash, str);

VityaSchel commented 2 years ago

Do you need to pass a specific salt? If you leave it empty, the library will generate one for you, a random, cryptographically secure one using crypto.randomBytes.

By the way, you don't need to pass the parameters if you are using the defaults, and definitely don't pass both salt and saltLength, as salt will take precedence. saltLength is meant to select the size of the generated salt, not to indicate the length of the salt param.

Check the wiki for options, it may help you use them

@ranisalt I just cannot find a right place to ask my naïve question, so I just continue from this issue. I always used the default functions argon2.hash(str) without any params, and save the hash only. Since you mention the lib will generate a random for me, and without actually saving the generated "salt" into database, how can the lib is able to recompute the same hash next time to match the hash in database via argon2.verify(hash, str);

salt is stored withing hash in argon2. pay attention to first half of the resulting string

aboveyunhai commented 2 years ago

@VityaSchel Thx for your clarification.

pubmikeb commented 2 years ago

@VityaSchel & @ranisalt, and what about the pepper?

I want to use both — the salt and the pepper, the salt argon2 will generate for me automatically but in case of a pepper, where it's better to integrate it in the flow?

Does it make sense, the following implementation:

  1. To combine the plain password with the pepper
  2. To calculate a sha3-512 hash of the peppered password
  3. To run argon2id on the hash from the step number 2

Or perhaps this flow:

  1. To run argon2id on a plain password
  2. To combine the argon2id hash with the pepper
  3. To calculate a sha3-512 hash based on the step number 2

Which of these two strategies do you find better? Or, perhaps, something else?

VityaSchel commented 2 years ago

@VityaSchel & @ranisalt, and what about the pepper?

I want to use both — the salt and the pepper, the salt argon2 will generate for me automatically but in case of a pepper, where it's better to integrate it in the flow?

Does it make sense, the following implementation:

  1. To combine the plain password with the pepper
  2. To calculate a sha3-512 hash of the peppered password
  3. To run argon2id on the hash from the step number 2

Or perhaps this flow:

  1. To run argon2id on a plain password
  2. To combine the argon2id hash with the pepper
  3. To calculate a sha3-512 hash based on the step number 2

Which of these two strategies do you find better? Or, perhaps, something else?

just add pepper to plain text that u hashing