diff --git a/.releaserc.json b/.releaserc.json index e9e134e..cd47a2a 100644 --- a/.releaserc.json +++ b/.releaserc.json @@ -16,4 +16,4 @@ "@semantic-release/github", "@semantic-release/npm" ] -} \ No newline at end of file +} diff --git a/.vscode/settings.json b/.vscode/settings.json index d2d6827..e5dd828 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,8 @@ { - "deno.enable": true, - "deno.suggest.imports.hosts": { - "https://deno.land": false - }, - "deno.lint": true, - "deno.unstable": false -} \ No newline at end of file + "deno.enable": true, + "deno.suggest.imports.hosts": { + "https://deno.land": false + }, + "deno.lint": true, + "deno.unstable": false +} diff --git a/README.md b/README.md index bd9df18..cbb3177 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,6 @@ This is a wasm-based (using rust-crypto) implementation of scrypt key derivation function that doesn't require any privileges. - [![Deno CI](https://github.com/denorg/scrypt/workflows/Deno%20CI/badge.svg)](https://github.com/denorg/scrypt/actions) [![GitHub](https://img.shields.io/github/license/denorg/scrypt)](https://github.com/denorg/scrypt/blob/master/LICENSE) [![Contributors](https://img.shields.io/github/contributors/denorg/scrypt)](https://github.com/denorg/scrypt/graphs/contributors) @@ -11,7 +10,6 @@ This is a wasm-based (using rust-crypto) implementation of scrypt key derivation [![TypeScript](https://img.shields.io/badge/types-TypeScript-blue)](https://github.com/denorg/scrypt) [![semantic-release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg)](https://github.com/semantic-release/semantic-release) - ## ⭐ Getting started Import the `hash` and/or `verify` functions and use them: diff --git a/cli.ts b/cli.ts index 3bb055d..6fb65ab 100644 --- a/cli.ts +++ b/cli.ts @@ -1,4 +1,4 @@ -import { hash, verify, genSalt } from "./mod.ts"; +import { genSalt, hash, verify } from "./mod.ts"; /** * @todo add a proper argument parser ([args](https://deno.land/x/args) perhaps?) */ diff --git a/lib/helpers.ts b/lib/helpers.ts index ff14921..1e908f7 100644 --- a/lib/helpers.ts +++ b/lib/helpers.ts @@ -1,17 +1,73 @@ /** * @todo document this module */ -import { Sha256, HmacSha256 } from "https://deno.land/std@0.127.0/hash/sha256.ts"; -import { - encode, - decode, -} from "https://deno.land/std@0.127.0/encoding/base64.ts"; +import { decode, encode } from "https://deno.land/std@0.143.0/encoding/base64.ts"; +import { HmacSha256, Sha256 } from "https://deno.land/std@0.143.0/hash/sha256.ts"; // deno-fmt-ignore -export type logN = - 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | - 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | - 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | - 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63; +export type logN = + | 1 + | 2 + | 3 + | 4 + | 5 + | 6 + | 7 + | 8 + | 9 + | 10 + | 11 + | 12 + | 13 + | 14 + | 15 + | 16 + | 17 + | 18 + | 19 + | 20 + | 21 + | 22 + | 23 + | 24 + | 25 + | 26 + | 27 + | 28 + | 29 + | 30 + | 31 + | 32 + | 33 + | 34 + | 35 + | 36 + | 37 + | 38 + | 39 + | 40 + | 41 + | 42 + | 43 + | 44 + | 45 + | 46 + | 47 + | 48 + | 49 + | 50 + | 51 + | 52 + | 53 + | 54 + | 55 + | 56 + | 57 + | 58 + | 59 + | 60 + | 61 + | 62 + | 63; export interface ScryptParameters { logN?: logN; r?: number; @@ -20,7 +76,7 @@ export interface ScryptParameters { dklen?: number; N?: number; } -export type scryptFormat = ("scrypt" | "phc" | "raw"); +export type scryptFormat = "scrypt" | "phc" | "raw"; export async function formatScrypt( rawHash: string, @@ -71,7 +127,7 @@ async function decomposeScrypt( * @param {logN} logN - log2 of cost factor * @param {number} r - block size * @param {number} p - parallelism factor - * @param {string|Uint8Array} salt - salt used when hashing + * @param {string|Uint8Array} salt - salt used when hashing */ export async function formatPHC( rawHash: string, @@ -86,8 +142,7 @@ export async function formatPHC( return `\$scrypt\$ln=${logN},r=${r},p=${p}\$${salt}\$${rawHash}`; } async function decomposePHC(formattedHash: string): Promise { - const regex = - /\$scrypt\$ln=(?\d+),r=(?\d+),p=(?

\d+)\$(?[a-zA-Z0-9\-\_\+\/\=]*)\$/; + const regex = /\$scrypt\$ln=(?\d+),r=(?\d+),p=(?

\d+)\$(?[a-zA-Z0-9\-\_\+\/\=]*)\$/; const parameters: ScryptParameters = formattedHash.match(regex) ?.groups as ScryptParameters; parameters.salt = new Uint8Array(decode(parameters.salt as string)); diff --git a/lib/helpers_test.ts b/lib/helpers_test.ts index 8a6119f..a9bb743 100644 --- a/lib/helpers_test.ts +++ b/lib/helpers_test.ts @@ -1,6 +1,4 @@ -import { - assertEquals, -} from "https://deno.land/std@0.127.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.143.0/testing/asserts.ts"; import { decomposeFormat } from "./helpers.ts"; @@ -9,11 +7,44 @@ Deno.test("decompose scrypt with format detection", async (): Promise => { "c2NyeXB0AAwAAAAIAAAAAcQ0zwp7QNLklxCn14vB75AYWDIrrT9I/7F9+lVGBfKN/1TH2hs/HboSy1ptzN0YzMmobAXD3CqJJLRLaTK7nOHbjNTWA20LuUmGwEoJtonW", ); // deno-fmt-ignore - const expectedParams = {logN:12, r:8, p:1, salt:new Uint8Array([ - 196, 52, 207, 10, 123, 64, 210, 228, - 151, 16, 167, 215, 139, 193, 239, 144, - 24, 88, 50, 43, 173, 63, 72, 255, - 177, 125, 250, 85, 70, 5, 242, 141 - ])} + const expectedParams = { + logN: 12, + r: 8, + p: 1, + salt: new Uint8Array([ + 196, + 52, + 207, + 10, + 123, + 64, + 210, + 228, + 151, + 16, + 167, + 215, + 139, + 193, + 239, + 144, + 24, + 88, + 50, + 43, + 173, + 63, + 72, + 255, + 177, + 125, + 250, + 85, + 70, + 5, + 242, + 141, + ]), + }; assertEquals(params, expectedParams); }); diff --git a/lib/scrypt.ts b/lib/scrypt.ts index 716348a..1719de0 100644 --- a/lib/scrypt.ts +++ b/lib/scrypt.ts @@ -1,6 +1,6 @@ -import init, { source, scrypt as scryptWASM } from "./_wasm/wasm.js"; -import { encode as base64encode } from "https://deno.land/std@0.127.0/encoding/base64.ts"; -import { encode as hexencode } from "https://deno.land/std@0.127.0/encoding/hex.ts"; +import { encode as base64encode } from "https://deno.land/std@0.143.0/encoding/base64.ts"; +import { encode as hexencode } from "https://deno.land/std@0.143.0/encoding/hex.ts"; +import init, { scrypt as scryptWASM, source } from "./_wasm/wasm.js"; const encoder: TextEncoder = new TextEncoder(); const decoder: TextDecoder = new TextDecoder("utf-8"); await init(source); @@ -17,27 +17,26 @@ export type encoding = "utf-8" | "base64" | "hex"; * @returns {Promise} - the resulting hash encoded according to outputEncoding */ export async function scrypt( - password: string | Uint8Array, - salt: string | Uint8Array, - N: number, - r: number, - p: number, - dklen?: number, - outputEncoding?: encoding + password: string | Uint8Array, + salt: string | Uint8Array, + N: number, + r: number, + p: number, + dklen?: number, + outputEncoding?: encoding, ): Promise { - dklen = dklen ?? 64; - password = - typeof password === "string" ? encoder.encode(password) : password; - salt = typeof salt === "string" ? encoder.encode(salt) : salt; - const result: Uint8Array = scryptWASM(password, salt, N, r, p, dklen); - switch (outputEncoding) { - case "base64": - return base64encode(result); - case "hex": - return hexencode(result); - case "utf-8": - return decoder.decode(result); - default: - return result; - } + dklen = dklen ?? 64; + password = typeof password === "string" ? encoder.encode(password) : password; + salt = typeof salt === "string" ? encoder.encode(salt) : salt; + const result: Uint8Array = scryptWASM(password, salt, N, r, p, dklen); + switch (outputEncoding) { + case "base64": + return base64encode(result); + case "hex": + return hexencode(result); + case "utf-8": + return decoder.decode(result); + default: + return result; + } } diff --git a/lib/scrypt_bench.ts b/lib/scrypt_bench.ts index 3d07ebf..bb8a77a 100644 --- a/lib/scrypt_bench.ts +++ b/lib/scrypt_bench.ts @@ -2,96 +2,95 @@ * Just a few simple benchmarks * @todo document the benchmarks better */ -import { - runBenchmarks, - bench, -} from "https://deno.land/std@0.127.0/testing/bench.ts"; +import { bench, runBenchmarks } from "https://deno.land/std@0.143.0/testing/bench.ts"; import { scrypt } from "./scrypt.ts"; -const extremeSalt: string = `IkjpewCbNm4A7cuY1VL9KC3y92blEthtLf9Cet6uEoTxz5cyF660da2zeci42fYwVCVwsKogETMCXupSZZAUh6a80ZnnBTk17B3UTCSVQPpYfL8GktJ2BDokE7ox2hV8OwwUT1hFvCuJqwHZpRvZw1RNCO6HfukPdgMHhq9rWLTXXUNwrIjlmkeydKGFJz2mS1xFcvLQtle4olJVK0SXXXYHAigBfpYxxSC2acvoxuacWcXhzSSRZAMysU2J7zDfXdxnYoqz50rvmvi36g7t2WDSAdzZ44JpxVcc3bYD7xYI3UgfVMPOfeblzwJi455QIurHzDuXEUNS0tZX1kWwZ0XcNSCwGzPs7WSVHxHc0KVUNhwSz16wDYFK4pYeA29ThXgFiFICSLVshiRrCfuzRthW7IZtRa9efcf4nFJsVBk51jpHY0b8CLhARrQU92mlBULwmJKe8DgST3Vn9rva98E9jk4y7NfSb4i9g74OjuFQ8yRO3BHksBZoVtBl4wUppM2hsLt72LZKA0ZsaWW7dG9a1bgWUkBBRG5OwzARenDqQIA2Gp5V4JsXuUUYNDylCelkLUVfS7hB1AZHtnIgwVqTaGDxl7nNZGKpAx6MrOd40laTUhrtZo4prwFZROHPNVJGQk2PQDgwqxX5SWoBTK8cCCzrbGBfHq9r8BwBvSVdeQ7bgjUW2j7NCapHHZ6filzxZaVsLsEITGZNcK0t5DdSnaDLRxyOn21ncKVIyZfOdlvpytvqpQaH5RWu4G50IPkEevue8KenXpGLP0pmEseBf6eX02rlN9arqZ4HJWmD7RbAChs7OJwfKlNIawb0V3G3N0eJeXiRsYOk10GIb91pyZRLSr2AJDtiWCcMuOWZfgLVHIrUVftfh9iXmRk2RAT1sigivbNtdqcF2cVvbTVMUCe7MIPRt4dGqwOQqdReGjPy9p1CNfKfJBIgW0xhYsOGMkcUqSurHxPl4wTOnMBx8vEZQsqJCZomENA1`; -const extremePassword: string = `TFImeWrtF2kOIvDjG4P0ybmMrNOq0bQ0aERcC69iHflECWrwuSMO4JPD3Ng5HwNXZrCpHyEwviW8zly3WLsQ6zJ60lnfwhVRdkEQCsFiH4NvGl0tCAuty9Rruf47WHeE3GK7qAJwhcXHx3FCJgWN8KHdoy3vn2zUKJlhhjSFGANJdVYQGSaQTmtoJdhcemmYT5hprkALp7Q9vMwCk9hDvV5vB0evXfxqG0dFV3MPJmywwWAUJEi5MyM2Pio7fL50M5ohPWFmUllpa6G5pVBhi26GtOy6sM3GDGHmnohavtsMvTeRcMX1ds4HWA9U3vH7urQ3XGkCUzulB6WxuxHn8Z3fRz3BL6MZI0EReep2qUVaqJn8onzsI6da6pU6iDtRbufWxi0q8XN1S3BCtFGjzaTU12nvfg5js53PiSw1KUnZj2thKxWtnKcpwzbXdTuuZ9GVhZHIMcOXXrDR0rj539ZLAVyJmqwDOMjTsqPN7BY522PcJHoTElSRNRAsAsFx2m7h9brhcZXOgV1PZohJsdQS7RWhAl9EYBkgF8WCgGw9DXidVduIIHDlEd7mAVJfo9HYX85kFcwrLEpuPiFxfNhubeDpeBu2FAbAo6DNHFlqXUUnyKvMbzptcgisSr2V1pwykB6uLVrwx3AceRnyqg5flldmfsSKw0AFZ4PagGMJuFDMGrV29Vmqhv61SRL9in0ngZx0gJ2vKv26qS3jGN72UUsbkysuGNz6ul0D5jIapvIcCTncIiXSY24pPctxFsawcXvSNw4jEKccsHCTZF0gri6iFS7JqqQd87FNowbrug6sIWSwiWHYGN1VfSwuE5plQHVvNCHNZnMBBIoaMWh45lhtlfCWdUwVpjjK5dAUcOtKftJ2hcl4mIlxs7Fy8ASWhYvWAbpp3fRgmAeTRYAFEwMohN9b03iXyDSNFIeZtQoaL7HYFVWoXV4BfBVlR3CvNIwp6OPBAFFSDlSn9CZU06UziY1tSwqBzkCD`; +const extremeSalt: string = + `IkjpewCbNm4A7cuY1VL9KC3y92blEthtLf9Cet6uEoTxz5cyF660da2zeci42fYwVCVwsKogETMCXupSZZAUh6a80ZnnBTk17B3UTCSVQPpYfL8GktJ2BDokE7ox2hV8OwwUT1hFvCuJqwHZpRvZw1RNCO6HfukPdgMHhq9rWLTXXUNwrIjlmkeydKGFJz2mS1xFcvLQtle4olJVK0SXXXYHAigBfpYxxSC2acvoxuacWcXhzSSRZAMysU2J7zDfXdxnYoqz50rvmvi36g7t2WDSAdzZ44JpxVcc3bYD7xYI3UgfVMPOfeblzwJi455QIurHzDuXEUNS0tZX1kWwZ0XcNSCwGzPs7WSVHxHc0KVUNhwSz16wDYFK4pYeA29ThXgFiFICSLVshiRrCfuzRthW7IZtRa9efcf4nFJsVBk51jpHY0b8CLhARrQU92mlBULwmJKe8DgST3Vn9rva98E9jk4y7NfSb4i9g74OjuFQ8yRO3BHksBZoVtBl4wUppM2hsLt72LZKA0ZsaWW7dG9a1bgWUkBBRG5OwzARenDqQIA2Gp5V4JsXuUUYNDylCelkLUVfS7hB1AZHtnIgwVqTaGDxl7nNZGKpAx6MrOd40laTUhrtZo4prwFZROHPNVJGQk2PQDgwqxX5SWoBTK8cCCzrbGBfHq9r8BwBvSVdeQ7bgjUW2j7NCapHHZ6filzxZaVsLsEITGZNcK0t5DdSnaDLRxyOn21ncKVIyZfOdlvpytvqpQaH5RWu4G50IPkEevue8KenXpGLP0pmEseBf6eX02rlN9arqZ4HJWmD7RbAChs7OJwfKlNIawb0V3G3N0eJeXiRsYOk10GIb91pyZRLSr2AJDtiWCcMuOWZfgLVHIrUVftfh9iXmRk2RAT1sigivbNtdqcF2cVvbTVMUCe7MIPRt4dGqwOQqdReGjPy9p1CNfKfJBIgW0xhYsOGMkcUqSurHxPl4wTOnMBx8vEZQsqJCZomENA1`; +const extremePassword: string = + `TFImeWrtF2kOIvDjG4P0ybmMrNOq0bQ0aERcC69iHflECWrwuSMO4JPD3Ng5HwNXZrCpHyEwviW8zly3WLsQ6zJ60lnfwhVRdkEQCsFiH4NvGl0tCAuty9Rruf47WHeE3GK7qAJwhcXHx3FCJgWN8KHdoy3vn2zUKJlhhjSFGANJdVYQGSaQTmtoJdhcemmYT5hprkALp7Q9vMwCk9hDvV5vB0evXfxqG0dFV3MPJmywwWAUJEi5MyM2Pio7fL50M5ohPWFmUllpa6G5pVBhi26GtOy6sM3GDGHmnohavtsMvTeRcMX1ds4HWA9U3vH7urQ3XGkCUzulB6WxuxHn8Z3fRz3BL6MZI0EReep2qUVaqJn8onzsI6da6pU6iDtRbufWxi0q8XN1S3BCtFGjzaTU12nvfg5js53PiSw1KUnZj2thKxWtnKcpwzbXdTuuZ9GVhZHIMcOXXrDR0rj539ZLAVyJmqwDOMjTsqPN7BY522PcJHoTElSRNRAsAsFx2m7h9brhcZXOgV1PZohJsdQS7RWhAl9EYBkgF8WCgGw9DXidVduIIHDlEd7mAVJfo9HYX85kFcwrLEpuPiFxfNhubeDpeBu2FAbAo6DNHFlqXUUnyKvMbzptcgisSr2V1pwykB6uLVrwx3AceRnyqg5flldmfsSKw0AFZ4PagGMJuFDMGrV29Vmqhv61SRL9in0ngZx0gJ2vKv26qS3jGN72UUsbkysuGNz6ul0D5jIapvIcCTncIiXSY24pPctxFsawcXvSNw4jEKccsHCTZF0gri6iFS7JqqQd87FNowbrug6sIWSwiWHYGN1VfSwuE5plQHVvNCHNZnMBBIoaMWh45lhtlfCWdUwVpjjK5dAUcOtKftJ2hcl4mIlxs7Fy8ASWhYvWAbpp3fRgmAeTRYAFEwMohN9b03iXyDSNFIeZtQoaL7HYFVWoXV4BfBVlR3CvNIwp6OPBAFFSDlSn9CZU06UziY1tSwqBzkCD`; bench({ - name: "small scrypt", - runs: 10, - async func(b): Promise { - b.start(); - await scrypt("password", "salt", 1024, 8, 1, 64); - b.stop(); - }, + name: "small scrypt", + runs: 10, + async func(b): Promise { + b.start(); + await scrypt("password", "salt", 1024, 8, 1, 64); + b.stop(); + }, }); bench({ - name: "small scrypt (longer password)", - runs: 10, - async func(b): Promise { - b.start(); - await scrypt("long password to test that", "salt", 1024, 8, 1, 64); - b.stop(); - }, + name: "small scrypt (longer password)", + runs: 10, + async func(b): Promise { + b.start(); + await scrypt("long password to test that", "salt", 1024, 8, 1, 64); + b.stop(); + }, }); bench({ - name: "small scrypt (longer salt)", - runs: 100, - async func(b): Promise { - b.start(); - await scrypt("password", "long salt to test that", 1024, 8, 1, 64); - b.stop(); - }, + name: "small scrypt (longer salt)", + runs: 100, + async func(b): Promise { + b.start(); + await scrypt("password", "long salt to test that", 1024, 8, 1, 64); + b.stop(); + }, }); bench({ - name: "small scrypt (longer password and salt)", - runs: 100, - async func(b): Promise { - b.start(); - await scrypt( - "long password to test that", - "long salt to test that", - 1024, - 8, - 1, - 64 - ); - b.stop(); - }, + name: "small scrypt (longer password and salt)", + runs: 100, + async func(b): Promise { + b.start(); + await scrypt( + "long password to test that", + "long salt to test that", + 1024, + 8, + 1, + 64, + ); + b.stop(); + }, }); bench({ - name: "small scrypt (extremely long salt)", - runs: 100, - async func(b): Promise { - b.start(); - await scrypt("password", extremeSalt, 1024, 8, 1, 64); - b.stop(); - }, + name: "small scrypt (extremely long salt)", + runs: 100, + async func(b): Promise { + b.start(); + await scrypt("password", extremeSalt, 1024, 8, 1, 64); + b.stop(); + }, }); bench({ - name: "small scrypt (extremely long password)", - runs: 100, - async func(b): Promise { - b.start(); - await scrypt(extremePassword, "NaCl", 1024, 8, 1, 64); - b.stop(); - }, + name: "small scrypt (extremely long password)", + runs: 100, + async func(b): Promise { + b.start(); + await scrypt(extremePassword, "NaCl", 1024, 8, 1, 64); + b.stop(); + }, }); bench({ - name: "small scrypt (extremely long password and salt)", - runs: 100, - async func(b): Promise { - b.start(); - await scrypt(extremePassword, extremeSalt, 1024, 8, 1, 64); - b.stop(); - }, + name: "small scrypt (extremely long password and salt)", + runs: 100, + async func(b): Promise { + b.start(); + await scrypt(extremePassword, extremeSalt, 1024, 8, 1, 64); + b.stop(); + }, }); bench({ - name: "standard scrypt", - runs: 20, - async func(b): Promise { - b.start(); - await scrypt("password", "NaCl", 16384, 8, 1, 64); - b.stop(); - }, + name: "standard scrypt", + runs: 20, + async func(b): Promise { + b.start(); + await scrypt("password", "NaCl", 16384, 8, 1, 64); + b.stop(); + }, }); runBenchmarks(); diff --git a/lib/scrypt_test.ts b/lib/scrypt_test.ts index 9b45085..5216093 100644 --- a/lib/scrypt_test.ts +++ b/lib/scrypt_test.ts @@ -1,80 +1,312 @@ -import { assertEquals } from "https://deno.land/std@0.127.0/testing/asserts.ts"; +import { assertEquals } from "https://deno.land/std@0.143.0/testing/asserts.ts"; import { scrypt } from "./scrypt.ts"; Deno.test("scrypt #1", async (): Promise => { - // deno-fmt-ignore - const expectedOutput: Uint8Array = new Uint8Array([ - 0x77, 0xd6, 0x57, 0x62, 0x38, 0x65, 0x7b, 0x20, 0x3b, 0x19, 0xca, 0x42, - 0xc1, 0x8a, 0x04, 0x97, 0xf1, 0x6b, 0x48, 0x44, 0xe3, 0x07, 0x4a, 0xe8, - 0xdf, 0xdf, 0xfa, 0x3f, 0xed, 0xe2, 0x14, 0x42, 0xfc, 0xd0, 0x06, 0x9d, - 0xed, 0x09, 0x48, 0xf8, 0x32, 0x6a, 0x75, 0x3a, 0x0f, 0xc8, 0x1f, 0x17, - 0xe8, 0xd3, 0xe0, 0xfb, 0x2e, 0x0d, 0x36, 0x28, 0xcf, 0x35, 0xe2, 0x0c, - 0x38, 0xd1, 0x89, 0x06, - ]); - assertEquals( - (await scrypt("", "", 16, 1, 1, 64)) as Uint8Array, - expectedOutput - ); + // deno-fmt-ignore + const expectedOutput: Uint8Array = new Uint8Array([ + 0x77, + 0xd6, + 0x57, + 0x62, + 0x38, + 0x65, + 0x7b, + 0x20, + 0x3b, + 0x19, + 0xca, + 0x42, + 0xc1, + 0x8a, + 0x04, + 0x97, + 0xf1, + 0x6b, + 0x48, + 0x44, + 0xe3, + 0x07, + 0x4a, + 0xe8, + 0xdf, + 0xdf, + 0xfa, + 0x3f, + 0xed, + 0xe2, + 0x14, + 0x42, + 0xfc, + 0xd0, + 0x06, + 0x9d, + 0xed, + 0x09, + 0x48, + 0xf8, + 0x32, + 0x6a, + 0x75, + 0x3a, + 0x0f, + 0xc8, + 0x1f, + 0x17, + 0xe8, + 0xd3, + 0xe0, + 0xfb, + 0x2e, + 0x0d, + 0x36, + 0x28, + 0xcf, + 0x35, + 0xe2, + 0x0c, + 0x38, + 0xd1, + 0x89, + 0x06, + ]); + assertEquals( + (await scrypt("", "", 16, 1, 1, 64)) as Uint8Array, + expectedOutput, + ); }); // deno-fmt-ignore Deno.test("scrypt #2", async (): Promise => { - // deno-fmt-ignore - const expectedOutput: Uint8Array = new Uint8Array([ - 0xfd, 0xba, 0xbe, 0x1c, 0x9d, 0x34, 0x72, 0x00, 0x78, 0x56, 0xe7, 0x19, - 0x0d, 0x01, 0xe9, 0xfe, 0x7c, 0x6a, 0xd7, 0xcb, 0xc8, 0x23, 0x78, 0x30, - 0xe7, 0x73, 0x76, 0x63, 0x4b, 0x37, 0x31, 0x62, 0x2e, 0xaf, 0x30, 0xd9, - 0x2e, 0x22, 0xa3, 0x88, 0x6f, 0xf1, 0x09, 0x27, 0x9d, 0x98, 0x30, 0xda, - 0xc7, 0x27, 0xaf, 0xb9, 0x4a, 0x83, 0xee, 0x6d, 0x83, 0x60, 0xcb, 0xdf, - 0xa2, 0xcc, 0x06, 0x40, - ]); - assertEquals( - (await scrypt("password", "NaCl", 1024, 8, 16, 64)) as Uint8Array, - expectedOutput - ); + // deno-fmt-ignore + const expectedOutput: Uint8Array = new Uint8Array([ + 0xfd, + 0xba, + 0xbe, + 0x1c, + 0x9d, + 0x34, + 0x72, + 0x00, + 0x78, + 0x56, + 0xe7, + 0x19, + 0x0d, + 0x01, + 0xe9, + 0xfe, + 0x7c, + 0x6a, + 0xd7, + 0xcb, + 0xc8, + 0x23, + 0x78, + 0x30, + 0xe7, + 0x73, + 0x76, + 0x63, + 0x4b, + 0x37, + 0x31, + 0x62, + 0x2e, + 0xaf, + 0x30, + 0xd9, + 0x2e, + 0x22, + 0xa3, + 0x88, + 0x6f, + 0xf1, + 0x09, + 0x27, + 0x9d, + 0x98, + 0x30, + 0xda, + 0xc7, + 0x27, + 0xaf, + 0xb9, + 0x4a, + 0x83, + 0xee, + 0x6d, + 0x83, + 0x60, + 0xcb, + 0xdf, + 0xa2, + 0xcc, + 0x06, + 0x40, + ]); + assertEquals( + (await scrypt("password", "NaCl", 1024, 8, 16, 64)) as Uint8Array, + expectedOutput, + ); }); Deno.test("scrypt #3", async (): Promise => { - // deno-fmt-ignore - const expectedOutput: Uint8Array = new Uint8Array([ - 0x70, 0x23, 0xbd, 0xcb, 0x3a, 0xfd, 0x73, 0x48, 0x46, 0x1c, 0x06, 0xcd, - 0x81, 0xfd, 0x38, 0xeb, 0xfd, 0xa8, 0xfb, 0xba, 0x90, 0x4f, 0x8e, 0x3e, - 0xa9, 0xb5, 0x43, 0xf6, 0x54, 0x5d, 0xa1, 0xf2, 0xd5, 0x43, 0x29, 0x55, - 0x61, 0x3f, 0x0f, 0xcf, 0x62, 0xd4, 0x97, 0x05, 0x24, 0x2a, 0x9a, 0xf9, - 0xe6, 0x1e, 0x85, 0xdc, 0x0d, 0x65, 0x1e, 0x40, 0xdf, 0xcf, 0x01, 0x7b, - 0x45, 0x57, 0x58, 0x87, - ]); - assertEquals( - (await scrypt( - "pleaseletmein", - "SodiumChloride", - 16384, - 8, - 1, - 64 - )) as Uint8Array, - expectedOutput - ); + // deno-fmt-ignore + const expectedOutput: Uint8Array = new Uint8Array([ + 0x70, + 0x23, + 0xbd, + 0xcb, + 0x3a, + 0xfd, + 0x73, + 0x48, + 0x46, + 0x1c, + 0x06, + 0xcd, + 0x81, + 0xfd, + 0x38, + 0xeb, + 0xfd, + 0xa8, + 0xfb, + 0xba, + 0x90, + 0x4f, + 0x8e, + 0x3e, + 0xa9, + 0xb5, + 0x43, + 0xf6, + 0x54, + 0x5d, + 0xa1, + 0xf2, + 0xd5, + 0x43, + 0x29, + 0x55, + 0x61, + 0x3f, + 0x0f, + 0xcf, + 0x62, + 0xd4, + 0x97, + 0x05, + 0x24, + 0x2a, + 0x9a, + 0xf9, + 0xe6, + 0x1e, + 0x85, + 0xdc, + 0x0d, + 0x65, + 0x1e, + 0x40, + 0xdf, + 0xcf, + 0x01, + 0x7b, + 0x45, + 0x57, + 0x58, + 0x87, + ]); + assertEquals( + (await scrypt( + "pleaseletmein", + "SodiumChloride", + 16384, + 8, + 1, + 64, + )) as Uint8Array, + expectedOutput, + ); }); Deno.test("scrypt #4", async (): Promise => { - // deno-fmt-ignore - const expectedOutput: Uint8Array = new Uint8Array([ - 0x21, 0x01, 0xcb, 0x9b, 0x6a, 0x51, 0x1a, 0xae, 0xad, 0xdb, 0xbe, 0x09, - 0xcf, 0x70, 0xf8, 0x81, 0xec, 0x56, 0x8d, 0x57, 0x4a, 0x2f, 0xfd, 0x4d, - 0xab, 0xe5, 0xee, 0x98, 0x20, 0xad, 0xaa, 0x47, 0x8e, 0x56, 0xfd, 0x8f, - 0x4b, 0xa5, 0xd0, 0x9f, 0xfa, 0x1c, 0x6d, 0x92, 0x7c, 0x40, 0xf4, 0xc3, - 0x37, 0x30, 0x40, 0x49, 0xe8, 0xa9, 0x52, 0xfb, 0xcb, 0xf4, 0x5c, 0x6f, - 0xa7, 0x7a, 0x41, 0xa4, - ]); - assertEquals( - (await scrypt( - "pleaseletmein", - "SodiumChloride", - 1048576, - 8, - 1, - 64 - )) as Uint8Array, - expectedOutput - ); + // deno-fmt-ignore + const expectedOutput: Uint8Array = new Uint8Array([ + 0x21, + 0x01, + 0xcb, + 0x9b, + 0x6a, + 0x51, + 0x1a, + 0xae, + 0xad, + 0xdb, + 0xbe, + 0x09, + 0xcf, + 0x70, + 0xf8, + 0x81, + 0xec, + 0x56, + 0x8d, + 0x57, + 0x4a, + 0x2f, + 0xfd, + 0x4d, + 0xab, + 0xe5, + 0xee, + 0x98, + 0x20, + 0xad, + 0xaa, + 0x47, + 0x8e, + 0x56, + 0xfd, + 0x8f, + 0x4b, + 0xa5, + 0xd0, + 0x9f, + 0xfa, + 0x1c, + 0x6d, + 0x92, + 0x7c, + 0x40, + 0xf4, + 0xc3, + 0x37, + 0x30, + 0x40, + 0x49, + 0xe8, + 0xa9, + 0x52, + 0xfb, + 0xcb, + 0xf4, + 0x5c, + 0x6f, + 0xa7, + 0x7a, + 0x41, + 0xa4, + ]); + assertEquals( + (await scrypt( + "pleaseletmein", + "SodiumChloride", + 1048576, + 8, + 1, + 64, + )) as Uint8Array, + expectedOutput, + ); }); diff --git a/mod.ts b/mod.ts index 77bd092..327264f 100644 --- a/mod.ts +++ b/mod.ts @@ -4,30 +4,30 @@ * @version 1.0.0 */ -import { scrypt } from "./lib/scrypt.ts"; import { - formatPHC, - formatScrypt, - scryptFormat, decomposeFormat, detectFormat, + formatPHC, + formatScrypt, logN, + scryptFormat, ScryptParameters, to32bytes, } from "./lib/helpers.ts"; +import { scrypt } from "./lib/scrypt.ts"; /** * Hash a password with scrypt. Outputs a string in rscrypt format. * @author oplik0 * @param {string} password - Password that will be hashed - * @param {ScryptParameters} [parameters] - Scrypt parameters (n, r and p) used for hashing. - * @param {number} [parameters.logN=14] - log2 of the work factor N. Must be an integer between 1 and 63. Defaults to 14 (N=16384) + * @param {ScryptParameters} [parameters] - Scrypt parameters (n, r and p) used for hashing. + * @param {number} [parameters.logN=14] - log2 of the work factor N. Must be an integer between 1 and 63. Defaults to 14 (N=16384) * @param {number} [parameters.r=8] - Block size. Defaults to 16 * @param {number} [parameters.p=1] - Parralelism factor. Defaults to 1 * @param {string} [parameters.salt] - custom salt (by default it will be randomly generated) * @param {number} [parameters.dklen=64] - desired key length (in bytes) * @param {number} [parameters.N=16384] - full number of iterations if you don't like using logN (this overrides that setting). Must be a power of 2. - * @param {scryptFormat} [format="scrypt"] - format of the result. Defaults to scrypt encrypted data format (https://github.com/Tarsnap/scrypt/blob/master/FORMAT) + * @param {scryptFormat} [format="scrypt"] - format of the result. Defaults to scrypt encrypted data format (https://github.com/Tarsnap/scrypt/blob/master/FORMAT) * @returns {string} - Hash in scrypt format */ export async function hash( @@ -48,8 +48,7 @@ export async function hash( : format === "phc" ? 32 : 64; - let scryptResult: string = - (await scrypt(password, salt, N, r, p, dklen, "base64")) as string; + let scryptResult: string = (await scrypt(password, salt, N, r, p, dklen, "base64")) as string; switch (format) { case "raw": return scryptResult; @@ -64,7 +63,7 @@ export async function hash( /** * Checks provided string against provided hash * @author oplik0 - * @param {string} password - string that will be checked against the hash + * @param {string} password - string that will be checked against the hash * @param {string} testedHash - hash in a compatible format (scrypt or phc formats supported for now) * @param {scryptFormat} [format] - format od the tested hash. Will be detected automatically if not provided * @returns {boolean} result of the check @@ -85,11 +84,11 @@ export async function verify( * @author oplik0 * @param {number} [length=16] - numebr of bytes to generate * @param {string} [outputType] - either string or Uint8Array - * @returns {string|Uint8Array} random salt + * @returns {string|Uint8Array} random salt */ export async function genSalt( length?: number, - outputType?: ("string" | "Uint8Array"), + outputType?: "string" | "Uint8Array", ): Promise { const array = new Uint8Array(length || 32); const decoder = new TextDecoder(); diff --git a/mod_test.ts b/mod_test.ts index b78a1c9..2694485 100644 --- a/mod_test.ts +++ b/mod_test.ts @@ -1,60 +1,57 @@ -import { - assertEquals, - assert, -} from "https://deno.land/std@0.127.0/testing/asserts.ts "; +import { assert, assertEquals } from "https://deno.land/std@0.143.0/testing/asserts.ts "; import { hash, verify } from "./mod.ts"; Deno.test("basic hashing - scrypt format", async (): Promise => { - const hashedPassword = await hash("test-password"); - assert(await verify("test-password", hashedPassword)); + const hashedPassword = await hash("test-password"); + assert(await verify("test-password", hashedPassword)); }); Deno.test("basic hashing - PHC format", async (): Promise => { - const hashedPassword = await hash("test-password", undefined, "phc"); - assert(await verify("test-password", hashedPassword)); + const hashedPassword = await hash("test-password", undefined, "phc"); + assert(await verify("test-password", hashedPassword)); }); Deno.test("basic hashing raw", async (): Promise => { - const hashedPassword = await hash("test-password", { salt: "test" }, "raw"); - assertEquals( - hashedPassword, - "zu8gd0RTeX6r0dbNzBv5ZzXOAQo0UnFUw49uRXrPwAlDocpJSA43WEgAcKNlsBhLyA+zVDluz/0GFa1ShAcr6g==" - ); + const hashedPassword = await hash("test-password", { salt: "test" }, "raw"); + assertEquals( + hashedPassword, + "zu8gd0RTeX6r0dbNzBv5ZzXOAQo0UnFUw49uRXrPwAlDocpJSA43WEgAcKNlsBhLyA+zVDluz/0GFa1ShAcr6g==", + ); }); // source of this test string: https://github.com/barrysteyn/node-scrypt#what-are-the-essential-properties-for-storing-passwords Deno.test("verify scrypt string", async (): Promise => { - assert( - await verify( - "password1", - "c2NyeXB0AAwAAAAIAAAAATpP+fdQAryDiRmCmcoOrZa2mZ049KdbA/ofTTrATQQ+m0L/gR811d0WQyip6p2skXVEMz2+8U+xGryFu2p0yzfCxYLUrAaIzaZELkN2M6k0" - ) - ); + assert( + await verify( + "password1", + "c2NyeXB0AAwAAAAIAAAAATpP+fdQAryDiRmCmcoOrZa2mZ049KdbA/ofTTrATQQ+m0L/gR811d0WQyip6p2skXVEMz2+8U+xGryFu2p0yzfCxYLUrAaIzaZELkN2M6k0", + ), + ); }); // source of this test string: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.scrypt.html Deno.test("verify PHC string", async (): Promise => { - assert( - await verify( - "password", - "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E" - ) - ); + assert( + await verify( + "password", + "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E", + ), + ); }); Deno.test("reject invalid password (PHC)", async (): Promise => { - assertEquals( - await verify( - "invalid-password", - "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E" - ), - false - ); + assertEquals( + await verify( + "invalid-password", + "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E", + ), + false, + ); }); Deno.test("reject invalid password (scrypt)", async (): Promise => { - assertEquals( - await verify( - "invalid-password", - "c2NyeXB0AAwAAAAIAAAAATpP+fdQAryDiRmCmcoOrZa2mZ049KdbA/ofTTrATQQ+m0L/gR811d0WQyip6p2skXVEMz2+8U+xGryFu2p0yzfCxYLUrAaIzaZELkN2M6k0" - ), - false - ); + assertEquals( + await verify( + "invalid-password", + "c2NyeXB0AAwAAAAIAAAAATpP+fdQAryDiRmCmcoOrZa2mZ049KdbA/ofTTrATQQ+m0L/gR811d0WQyip6p2skXVEMz2+8U+xGryFu2p0yzfCxYLUrAaIzaZELkN2M6k0", + ), + false, + ); });