Search code examples
angularauthenticationhashcryptography

Implementing crypto hash - Node.js 22 - Angular 18


I already searched within Stack Overflow and in Google for solutions, but none of the solutions work, I always get following error :

Error: Module not found: Error: Can't resolve 'crypto' in ....

I did already:

  • npm update
  • ng update

auth.service.ts : ( not the html component - not in the browser - hidden authentication service )

import { Injectable } from '@angular/core';
import { scrypt, randomBytes, timingSafeEqual } from "crypto";

const keyLength = 32;

@Injectable({
  providedIn: 'root'
})
export class AuthService {

constructor() {};

async login( username: string, password: string ) {
  if ( await AuthService.compare(username, await AuthService.hash(password))) {
    localStorage.setItem('STATE', 'true');
    return true;
  }

  return false;
}
  
async logout() {
    localStorage.setItem('STATE', 'false');

    return true;
}
  
isLoggedIn() {
  const loggedIn = localStorage.getItem('STATE');
  if (loggedIn == 'true')
    return true;
  else
    return false;
}

/**
 * Has a password or a secret with a password hashing algorithm (scrypt)
 * @param {string} password
 * @returns {string} The salt+hash
 */
static async hash( password: string ): Promise<string> {
  return new Promise((resolve, reject) => {
      // generate random 16 bytes long salt - recommended by NodeJS Docs
      const salt = randomBytes(16).toString("hex");

      scrypt(password, salt, keyLength, (err, derivedKey) => {
          if (err) reject(err);
          // derivedKey is of type Buffer
          resolve(`${salt}.${derivedKey.toString("hex")}`);
      });
    });
  };

/**
* Compare a plain text password with a salt+hash password
* @param {string} password The plain text password
* @param {string} hash The hash+salt to check against
* @returns {boolean}
*/
static async compare( password: string, hash: string ): Promise<boolean> {
  return new Promise((resolve, reject) => {
      const [salt, hashKey] = hash.split(".");
      // we need to pass buffer values to timingSafeEqual
      const hashKeyBuff = Buffer.from(hashKey, "hex");
      scrypt(password, salt, keyLength, (err, derivedKey) => {
          if (err) reject(err);
          // compare the new supplied password with the hashed password using timeSafeEqual
          resolve(timingSafeEqual(hashKeyBuff, derivedKey));
      });
    });
  };

}

Crypto module is built-in in Node.js 22.4.1 which I use for my Angular 18: https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback

The solution is not:

  • npm install crypto (old way)
  • not using Crypto-JS
  • No errors shown in Visual Studio Code (but errors show up when doing "Start debugging")

Solution

  • You'll need to use something available in browsers like the Web Crypto API.

    Hashing could look something like:

    static async hash(password: string): Promise<string> {
      const salt = crypto.getRandomValues(new Uint8Array(16))
      const key = await crypto.subtle.importKey(
        "raw",
        new TextEncoder().encode(password),
        { name: "PBKDF2" },
        false,
        ["deriveBits"])
      const derivedBits = await crypto.subtle.deriveBits({
        name: "PBKDF2",
        salt,
        iterations: 100000,
        hash: "SHA-256"
      }, key, 256)
      return `${this.bufferToHex(salt)}.${this.bufferToHex(derivedBits)}`
    }
    

    Comparing could look something like:

    static async compare(password: string, hash: string): Promise<boolean> {
      const [saltHex, storedHashHex] = hash.split('.')
      const key = await crypto.subtle.importKey(
        "raw",
        new TextEncoder().encode(password),
        { name: "PBKDF2" },
        false,
        ["deriveBits"]
      )
      const derivedBits = await crypto.subtle.deriveBits({
        name: "PBKDF2",
        salt: this.hexToBuffer(saltHex),
        iterations: 100000,
        hash: "SHA-256"
      }, key, 256)
      return storedHashHex === this.bufferToHex(derivedBits)
    }
    

    Shouldn't need to import anything for this to work. I'm not a security expert so can't guarantee the security of these algorithms, but PBKDF2 is generally considered acceptable for password hashing.