udibo / oauth2_server

A standards compliant implementation of an OAuth 2.0 authorization server with PKCE support.
MIT License
21 stars 4 forks source link

Use PBKDF2 for password hashing instead of SHA-256 #28

Closed KyleJune closed 3 years ago

KyleJune commented 3 years ago

Update hashPassword function on the user service to use PBKDF2 for password hashing.

https://github.com/udibo/oauth2_server/blob/main/services/user.ts

I wrote a function for generating salt in the example. It should be moved to the user service so that it's more reusable like the hashPassword function is.

https://github.com/udibo/oauth2_server/blob/main/examples/oak-localstorage/services/user.ts#L4

To be determined, what options should hashPassword have configurable.

Notes:

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveBits#pbkdf2

import { encode as encodeHex } from "https://deno.land/std@0.114.0/encoding/hex.ts";

function generateSalt(): Uint8Array {
  return window.crypto.getRandomValues(new Uint8Array(16));
}

/*
Get some key material to use as input to the deriveBits method.
The key material is a password supplied by the user.
*/
function getKeyMaterial(password: string) {
  const enc = new TextEncoder();
  return window.crypto.subtle.importKey(
    "raw",
    enc.encode(password),
    "PBKDF2",
    false,
    ["deriveBits", "deriveKey"],
  );
}

/*
Derive some bits from a password supplied by the user.
*/
async function getDerivedBits(
  password: string,
  salt: Uint8Array,
): Promise<[Uint8Array, number]> {
  const keyMaterial = await getKeyMaterial(password);
  const derivedBits = await window.crypto.subtle.deriveBits(
    {
      "name": "PBKDF2",
      salt,
      "iterations": 100000,
      "hash": "SHA-256",
    },
    keyMaterial,
    256,
  );

  const buffer = new Uint8Array(derivedBits, 0, 32);
  return [buffer, derivedBits.byteLength];
}

const salt = generateSalt();
const saltText = (new TextDecoder()).decode(encodeHex(salt));
console.log("salt: ", saltText);

const [buffer, length] = await getDerivedBits("hunter2", salt);
console.log(buffer);
console.log(length);
const hashed = (new TextDecoder()).decode(encodeHex(new Uint8Array(buffer)));
console.log(hashed);

// current SHA-256 hashPassword function
async function hashPassword(password: string, salt?: string): Promise<string> {
  const data = (new TextEncoder()).encode(
    password + (salt ? `:${salt}` : ""),
  );
  const buffer = await crypto.subtle.digest("SHA-256", data);
  return (new TextDecoder()).decode(encodeHex(new Uint8Array(buffer)));
}
console.log(await hashPassword("hunter2", saltText));
KyleJune commented 3 years ago

Here is an example using deriveKey instead of deriveBits.

https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/deriveKey#pbkdf2_2

Here is an issue on deno tracking supported algorithms.

https://github.com/denoland/deno/issues/11690