Marcy Lab School Docs
  • Welcome
  • Student Guidelines & Policies
    • Student Handbook
    • AI Policy
    • Academic Calendar
  • Environment Setup
    • Local Environment Setup - Mac
    • Local Environment Setup - Windows
    • GitHub Setup
    • Postgres Setup
  • How-Tos
    • How To Code at Marcy: Code Style Guide
    • How to Do Short Response and Coding Assignments
    • How to Debug
    • How to PEDAC
    • How to Create A GitHub Organization and Scrumboard
    • How to Create Projects with Vite
    • How to Deploy on GitHub Pages
    • How to Deploy on Render
    • How to Test your API with Postman
  • Mod 0 - Command Line Interfaces, Git, and GitHub
    • Overview
    • 1. Command Line Interfaces
    • 2. Git & GitHub
    • 3. Git Pulling & Merging
    • 4. Git Branching & PRs
  • Mod 1 - JavaScript Fundamentals
    • Overview
    • 1. Intro to Programming
    • 2. Errors
    • 3. Node & Node Modules
    • 4. Variables, Functions & String Methods
    • 5. Control Flow, typeof, and Math
    • 6. Loops
    • 7. Arrays
    • 8. Objects
    • 9. Higher Order Functions: Callbacks
    • 10. Higher Order Functions: Array Methods
    • 11. Regex
  • Mod 2 - HTML, CSS & the DOM
    • Overview
    • 1. HTML
    • 2. CSS
    • 3. Accessibility (a11y)
    • 4. The Document Object Model (DOM) API
    • 5. Events
    • 6. Forms
    • 7. The Box Model and Positioning
    • 8. Flexbox
    • 9. Grid & Media Queries
    • 10. ESModules
    • 11. Vite
    • 12. LocalStorage
  • Mod 3 - Async & APIs
    • Overview
    • 1. Promises
    • 2. Fetch
    • 3. Building a Fetching App
    • 4. Async & Await
    • 5. A Generic Fetch Handler
  • Mod 4 - Project Week!
    • Important How Tos and Guides
      • How to Create a GitHub Organization and Scrum Board
      • How To Start a Project with Vite
      • How To Deploy a Project with GitHub Pages
    • Project Week Overview
    • Agile Methodologies
    • Deliverables & Milestones
    • Technical Requirements Checklist
    • Free API List
    • Collaborative GitHub
  • Mod 5 - Object-Oriented Programming
    • Overview
    • 1. Intro to OOP, Encapsulation, Factory Functions, and Closure
    • 2. Classes
    • 3. Private & Static
    • 4. UML Diagrams & Has Many/Belongs To Relationships
    • 5. Challenge: Implementing Has Many/Belongs To
    • 6. Inheritance
    • 7. Polymorphism
    • 8. Review and Practice
    • MDN: Object Prototypes
  • Mod 6 - Data Structures & Algorithms
    • Overview
    • Important How Tos and Guides
      • How to Debug
      • How to PEDAC
    • 1. Nodes & Linked Lists
    • 2. Singly & Doubly Linked Lists
    • 3. Stacks & Queues
    • 4. Recursion
    • 5. Trees
  • Mod 7 - React
    • Overview
    • Important How Tos and Guides
      • How to Create Projects with Vite
      • How to Deploy on GitHub Pages
    • 1. Intro to React
    • 2. Events, State, and Forms
    • 3. Fetching with useEffect
    • 4. React Router
    • 5. Building a Flashcards App
    • 6. React Context
    • 7. Global Context Pattern
  • Mod 8 - Backend
    • Overview
    • Important How Tos and Guides
      • How to Deploy on Render
      • How to Test your API with Postman
      • Postgres Setup
    • 1. Intro to Express
    • 2. Building a Static Web Server with Middleware
    • 3. Securing API Keys and Environment Variables
    • 4. RESTful CRUD API
    • 5. Model-View-Controller Architecture
    • 6. SQL and Databases
    • 7. JOIN (Association) SQL Queries
    • 8. Knex
    • 9. Your First Fullstack App!
    • 10. Migrations & Seeds
    • 11. Schema Design & Normalization
    • 12. Hashing Passwords with Bcrypt
    • 13. React Express Auth Template Overview
  • Mod 9 - Civic Tech Hackathon
    • Overview
    • Rubric
  • Mod 10 - Capstone
    • Overview
Powered by GitBook
On this page
  • Terms
  • Pre-Learning
  • Hashing
  • Authenticating Users
  • A Simple Example
  • The Importance of Secure Hash Functions
  • Hashing with Bcrypt
  • Salting
  • Bcrypt Helpers
  • Summary — The Password Validation Workflow
  1. Mod 8 - Backend

12. Hashing Passwords with Bcrypt

Previous11. Schema Design & NormalizationNext13. React Express Auth Template Overview

Last updated 1 month ago

Follow along with code examples !

Table of Contents:

Terms

  • Hashing - a mathematical algorithm that transforms a string of characters into a fixed-length string of characters.

  • Password Salt - A salt is a random string of data that is added to the input data before the hash function is applied. This changes the hash value that is produced, even for the same input data.

  • Salt Rounds - the number of times a password has been salted before being hashed

  • Plaintext password - the password as it was entered by the user, before it is hashed.

  • Bcrypt - a Node module that provides functions for hashing strings and verifying hashed strings.

Pre-Learning

Check out this video to learn about hashing, salting, and various attacks used by hackers to get accessed to your password!

Hashing

Hashing is a process of transforming a string of characters into a fixed-length string of characters called a hash.

A hashing function is a function that performs a hashing algorithm.

Hashing functions have two key properties:

  1. They must be pure — they must produce the same output when given the same input!

  2. They should be "one way" — it is easy to generate a hash from a given string, but relatively impossible to determine the original string from a given hash.

As a result, hashing is commonly used for password storage: when a user creates an account, the username will be stored in the database alongside the hashed password.

Q: Why is it important that hashing functions are "one way" when it comes to password storage?

Without password hashing, a hacker who obtains a user database can simply read the passwords in plain text.

With password hashing, the passwords are stored as hash values, and an attacker would need to spend significant time and resources attempting to convert the hash values back into the original passwords.

Authenticating Users

By storing the hashed password, the database itself never "knows" your actual password. Despite this, when a user returns to sign in, the server can still validate a given username and password combination through some clever logic.

This process is called authentication:

  • When the user returns to log in to their account, they provide their username and password.

  • The server uses the provided username to find the associated hashed password in the database.

  • The server then hashes the provided password. Since hashing algorithms are pure, the provided password's hash should match the hash stored in the database.

  • If the hashes match, the user is authenticated!

A Simple Example

Below is a very simple hashing function that can help demonstrate the authentication process.

This function converts each character in the given string into its ASCII character code ("a" → 97, "b" → 98, etc...)

// Convert a given string to its ASCII codes
const simpleHash = (str) => {
  let hash = '';
  for (let i = 0; i < str.length; i++) {
    hash += str.charCodeAt(i);
  }
  return hash;
}

// the passwordToTest should produce the same hash as the storedHash
const validatePassword = (passwordToTest, storedHash) => {
  return simpleHash(passwordToTest) === storedHash;
}

const hashedPassword = simpleHash("abc");  // 979899

console.log(validatePassword("xyz", hashedPassword));
// false, the given password produces a different hash!

console.log(validatePassword("abc", hashedPassword));
// true, the given password produces the same hash!

The Importance of Secure Hash Functions

Remember, a hashing function should have the following properties:

  1. They must be pure — they must produce the same output when given the same input!

  2. They should be "one way" — it is easy to generate a hash from a given string, but relatively impossible to determine the original string from a given hash.

Q: For the algorithm above, given the hashed string 999897, what is the plain-text string that would generate that hashed string?
simpleHash("cba") //-> "999897"

This algorithm is clearly not "one way"!

If an attacker had a database full of passwords hashed using this algorithm, they would be able to crack the passwords in no time!

This is where the secure hashing module bcrypt come in!

Hashing with Bcrypt

The bcrypt node module gives us the bcrypt.hash and bcrypt.compare methods for hashing and authenticating strings.

They are both asynchronous methods that return promises

  • bcrypt.hash resolves to the hash string

  • bcrypt.compare resolves to a boolean

The strings produced by bcrypt.hash are much more complex and are nearly impossible to reverse-engineer!

const bcrypt = require('bcrypt');

// Make an async wrapper function so we can use await
const testHashing = async () => {
  const saltRounds = 8; 

  const hashedPassword = await bcrypt.hash('secret', saltRounds);

  console.log(hashedPassword); // a complex string!
  
  const isValid = await bcrypt.compare('secret', hashedPassword);

  console.log(isValid); // true!
}

testHashing();

Salting

You may have noticed the saltRounds argument provided to bcrypt.hash:

const saltRounds = 8; // a.k.a. cost = 8

const hashedPassword = await bcrypt.hash('secret', saltRounds);

To understand what this does, we should break down the structure of a bcrypt hash string:

A salt is a random string of data that is added to the input data before the hash function is applied. This ensures that even if two users have the same password, they will have unique password hashes.

When you invoke bcrypt.hash, it will automatically generate a salt value and add it to your input string before hashing. You can test this by invoking bcrypt.hash twice with the same input:

// bcrypt.hash automatically adds a salt for you.
// So, the hashes will be different!
console.log(await bcrypt.hash('secret', 8));
console.log(await bcrypt.hash('secret', 8));

However, instead of salting only once, the saltRounds argument determines the number of times that it re-salts and re-hashes your string before arriving at the final hash.

hash(password + salt) => hashedPassword1
hash(hashedPassword1 + salt) => hashedPassword2
hash(hashedPassword2 + salt) => hashedPassword3
...
hash(hashedPassword6 + salt) => hashedPassword7
hash(hashedPassword7 + salt) => hashedPassword8 

=> hashedPassword8 is stored in the database

This repeated process of salting and hashing increases the computational cost of generating a hash, further protecting against brute force attempts to crack a password.

The hash string has all of the information needed to re-compute the stored hash value as long as the matching password is given.

When authenticating a password, the bcrypt.compare function will extract the cost and the salt value from the stored hash value and apply them to the given password.

Again, since this process is pure, the resulting hash function should match the stored hash function!

The Salt Rounds is also called the cost factor since there is a tradeoff to be considered.

However, it also means that your server needs to take more time to generate secure passwords. So, the recommended number of rounds is 12 as it strikes a nice balance between security and performance (though 8 is satisfactory for a learning project).

Bcrypt Helpers

Both hashing and comparing can throw errors if the functions are not used properly. So we've created these helpers to handle those errors for us.

const hashPassword = async (password, saltRounds = 8) => {
  try {
    const hashedPassword = await bcrypt.hash(password, saltRounds);
    return hashedPassword;
  } catch (err) {
    return console.error(err.message);
  }
};
const isValidPassword = async (password, hash) => {
  try {
    const isValid = await bcrypt.compare(password, hash);
    return isValid;
  } catch (err) {
    return console.error(err.message);
  }
};

Summary — The Password Validation Workflow

So, to recap:

Hashing is a process of transforming a string of characters into a fixed-length string of characters called a hash.

Hashing functions have two key properties:

  1. They must be pure — they must produce the same output when given the same input!

  2. They should be "one way" — it is easy to generate a hash from a given string, but relatively impossible to determine the original string from a given hash.

We should always hash passwords before storing them in a database

Authentication is possible by hashing a given password and comparing it against the stored hash:

A higher number of salt rounds means attackers need to spend equally more time and resources to crack passwords through brute force methods (see and ).

Video: Password Hashing, Salts, Peppers | Explained!
Rainbow Table Attacks
Dictionary Attacks
here
Terms
Pre-Learning
Hashing
Authenticating Users
A Simple Example
The Importance of Secure Hash Functions
Hashing with Bcrypt
Salting
Bcrypt Helpers
Summary — The Password Validation Workflow
Hashing is a process of transforming a string of characters into a fixed-length string of characters called a hash.
Usernames are stored in a database alongside the hashed password.
The server uses the given username to find the associated hashed password in the database. If the given password produces the same hash, then the user is authenticated!
The salt and number of salt rounds (a.k.a. "cost") is included in the hash string.
A salt is added to a string before hashing to produce different results.
The salt and number of salt rounds (a.k.a. "cost") is included in the hash string.
Usernames are stored in a database alongside the hashed password.
The server uses the given username to find the associated hashed password in the database. If the given password produces the same hash, then the user is authenticated!