Deno - Hash and Validate Password with bcrypt Examples

This tutorial gives you examples of how to hash a password using bcrypt in Deno as well as how to compare a password against a stored hash.

bcrypt is a password-hashing algorithm that uses Eksblowfish algorithm. The process of generating a bcrypt hash includes expensive key setup using Eksblowfish algorithm and key expansion.

Below is an example of a bcrypt hash string. Each part is differentiated to other parts with different colors.

$2a$08$lT24ClsQAkEe9AB1odZRN.sJRNJGN1OFHRhMSpB7JcL7lMQFVazJ6

The explanation of the parts is as follows.

  • $2a$: The hash algorithm identifier for bcrypt.
  • 10: Cost factor. 10 means 210 = 1,024 rounds.
  • lT24ClsQAkEe9AB1odZRN.: 16-byte salt which is encoded to 22 characters using base64.
  • sJRNJGN1OFHRhMSpB7JcL7lMQFVazJ6: 24-byte hash which is encoded to 31 characters using base64..

If you use Deno, there is a third-party module named bcrypt that allows you to easily hash a password using bcrypt algorithm. The module also supports comparing plaintext against a hashed value which is used for password validation.

Using bcrypt Module

First, you need to import and re-export bcrypt module in deps.ts file. Placing the dependency to remote modules in deps.ts makes it easier to change the version later.

deps.ts

  import * as bcrypt from 'https://deno.land/x/bcrypt@v0.2.4/mod.ts';

  export { bcrypt };

Generate Hash

bcrypt computation is usually quite expensive. Therefore, it would be better if we can run the computation asynchronously. Currently the async implementation requires to use Deno WebWorkers. To import Deno standard modules from the WebWorkers, you need to add --allow-net flag while running deno run command. Since Worker.deno is not a stable API at the time this post was written, you may also need to add --unstable flag.

Below is the function that can be used to generate the hash asynchronously.

  async function hash(
    plaintext: string,
    salt: string | undefined = undefined,
  ): Promise<string>

Example:

  import { bcrypt } from './deps.ts';

  const hash = await bcrypt.hash('password1234');
  console.log(`hash: ${hash}`);

Below is the command example that uses those two flags.

   deno run --allow-net --unstable file_name.ts

Output:

  hash: $2a$10$yq3gbV3iGKftf4Nx18arMuAHchj/pomygYXv9OSqJnVyUQqBdiqlu

Using the above code, the salt is automatically generated. Alternatively, you can also generate the salt manually by using genSalt function, which allows you to specify the number of log rounds.

  async function genSalt(
    log_rounds: number | undefined = undefined,
  ): Promise<string>

Example:

  import { bcrypt } from './deps.ts';

  const salt = await bcrypt.genSalt(8);
  const hash = await bcrypt.hash('password1234', salt);
  console.log(`hash: ${hash}`);

Output:

  hash: $2a$08$lT24ClsQAkEe9AB1odZRN.sJRNJGN1OFHRhMSpB7JcL7lMQFVazJ6

You can also choose to generate the salt and the hash synchronously. To do so, you need to use genSaltSync and hashSync. Changing to use the synchronous functions means you don't need to pass --allow-net and --unstable flags anymore. However, it's not recommended because the bcrypt computation is quite expensive and it can block multiple incoming requests to the server.

  function genSaltSync(
    log_rounds: number | undefined = undefined,
  ): string

  function hashSync(
    plaintext: string,
    salt: string | undefined = undefined,
  ): string

Example:

  import { bcrypt } from './deps.ts';

  const salt = bcrypt.genSaltSync(8);
  const hash = bcrypt.hashSync('password1234', salt);
  console.log(`hash: ${hash}`);

Validate Password

If you want to compare a hashed password against a value in order to check whether the given value is a valid password, you can use compare function.

  function compare(
    plaintext: string,
    hash: string,
  ): Promise<boolean>

The above function is asynchronous. You need to pass the plain password as the first argument and the stored hash as the second argument. It will return true if the plain password matches the stored hash or false otherwise. To use this function, you also need to pass --allow-net and --unstable flags.

Example:

  import { bcrypt } from './deps.ts';

  const isValid = await bcrypt.compare('password1234', hash);
  console.log(`isValid: ${isValid}`);

Output:

  isValid: true

The function also has a synchronous version.

  function compareSync(plaintext: string, hash: string): boolean

Example:

  import { bcrypt } from './deps.ts';

  const result = bcrypt.compareSync('password1234', hash);
  console.log(`result: ${result}`);

Summary

That's how to use the bcrypt algorithm to hash a password in Deno and also how to compare a password with a hashed value. It's recommended to use the asynchronous version for better performance. The asynchronous version requires the operations to run in WebWorkers. At the moment this post was written, it's still necessary to use --unstable flag since running the hash asynchronously requires Worker.deno API which is still unstable.