Skip to content

Instantly share code, notes, and snippets.

@ddjerqq
Created October 13, 2022 11:54
Show Gist options
  • Select an option

  • Save ddjerqq/4dd7d8927dc3e900f9ad96d712912f59 to your computer and use it in GitHub Desktop.

Select an option

Save ddjerqq/4dd7d8927dc3e900f9ad96d712912f59 to your computer and use it in GitHub Desktop.
Password hasher. used to prepare the passwords for super secure storage.
const crypto = require("crypto")
/**
* Password hasher
* used to prepare the passwords for super secure storage.
*
* this system is so secure (unless the SALTS and PEPPERS are *stolen*) that its safe to have weak passwords like password123 iloveu and so on.
* as 2 same passwords will (most *likely*) have a different hash
*
* # Notes:
*
* * if the SALTS and PEPPERS are stolen, along this file which has the whole method inside it, it will render the security useless.
* so keep this file on the server, and let users send plain text to the server. use TLS (transport layer security) and HTTP__S__
*
* * 2 identical plain text passwords will have different hashes most likely because of the _SAUCES. but there still is a chance that two users will have a same password.
* that is okay tho, and it is not a big deal, just something to be aware of.
*
* # Payload structure
* `[dice] is plain_text_password split in 2 parts, with the second part reversed`;
*
* one of the hashes is `[salt][dice][pepper][random_sauce]`;
*
* @example
* let hashes = [];
* for each sauce in Password.SAUCES {
* hashes.push(sha256([salt][dice][pepper][sauce]))
* }
* return random choice from the hashes
*
* @example
* const password = "password";
* const hash = await Password.hash(password);
* console.assert(Password.check(password, hash));
*/
class Password {
#_SALT = "SECRET";
#_PEPPER = "SECRET";
#_SAUCES = "SECRET";
static async #_hash(payload) {
const msgBuffer = new TextEncoder().encode(payload);
const hashBuffer = await crypto.subtle.digest("SHA-256", msgBuffer);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map(b => b.toString(16).padStart(2, "0")).join("");
}
static async #_prepare(password) {
// salt and pepper the password
const salted = Password.#_SALT + password + Password.#_PEPPER;
// dice the password
const mid = Math.floor(salted.length / 2);
// split it in two and reverse the second half
const diced = salted.slice(0, mid) + salted.slice(mid).split("").reverse();
// for each kind of sauce, sauce the password and add it to the output list
let parts = [];
for (const sauce of this.#_SAUCES) {
let hash = await Password.#_hash(diced + sauce);
parts.push(hash);
}
return parts;
}
/**
* hash a plain text password and return one of the saucy sha256 hashes.
* @param {String} plain_text_password the password which you want to hash.
* @returns {Promise<String>} one of the sauced password hashes.
* @example
* let one_of_the_hashes = await Password.hash("$up3rSecure");
*/
static async hash(plain_text_password) {
const sauces = await this.#_prepare(plain_text_password);
const idx = Math.floor(Math.random() * sauces.length);
return sauces[idx];
}
/**
* check if the hash belongs to the password.
* @param {String} plain_text_password the password which you want to check.
* @param {String} hash the hash of the password, usually from the database.
* @returns {Promise<boolean>} returns true if the hash belongs to the password.
* @example
* const isPasswordValid = await check("$up3rSecure", "8bb32691631f72e8c39b0834b1f8c956ffbb666df6facef5a951ab9f1b549b17");
* console.assert(isPasswordValid)
*/
static async check(plain_text_password, hash) {
const password = new Password(plain_text_password);
const hashes = await password.#_prepare();
return hashes.includes(hash)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment