@prsm/hash 1.0.1 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,41 +1,56 @@
1
- # hash
1
+ # @prsm/hash
2
2
 
3
- A very simple string hashing library.
3
+ Salted string hashing on top of `node:crypto`. Zero dependencies. Defaults to `scrypt` for password-safe hashing.
4
4
 
5
- # create
5
+ ## Installation
6
6
 
7
- ```typescript
8
- import { hash } from "@prsm/hash";
7
+ ```bash
8
+ npm install @prsm/hash
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```js
14
+ import hash from "@prsm/hash"
9
15
 
10
- hash.create("secret");
11
- hash.create("secret");
12
- hash.create("secret");
16
+ const encoded = await hash.encode("my password")
17
+ // scrypt:UfH7lmEc5d...:6qG75Cp5hys...
13
18
 
14
- // sha256:UfH7lmEc5dr65iFPmvsKthzAgMHtdV6Qb4FXYSqlnOaQoZmqQWLBrPnJGLZmQontirQZKO9nTIz+zs544n0x7Q==:6qG75Cp5hysNWs+8TO65fzc1FaSZxykaWa3iatPrw4s=
15
- // sha256:Wq6vrcGG4mKlM7r8DAuDHcYxJlG8fOEoO2sNWofl/snmsZPTaBuy8Dg6i2J28TdcncSgK8EhrCqgv69h5Kk2xA==:QvAc6op8ScJex38AYrZUtFDd69c4OJv5SsVIRgR+FPw=
16
- // sha256:e16qmZpJiy1qvGycPkJz0qQnCdyAguGAFV8rqCokiFml10nl9lVU1v0hZ6QBy+laI0AYkHsYtt6wMkEOuNhpMw==:L3bHZeriSAjy8wEIz/fURxhOqxa8KltuvpHPE/nE/eQ=
17
- // sha256:0SA+O819D52jZOqWuzIWa+KLyT+Ck+b5ze4HI7fAJOhRW3FYk527GnuVOS/pricLy1KqwUfk5wWyQx4z5x3fsA==:wPs8DRMOrZEJYeaPxZzccGPJSozGvNqRhhS6f8ITOyM=
19
+ await hash.verify(encoded, "my password") // true
20
+ await hash.verify(encoded, "wrong") // false
18
21
  ```
19
22
 
20
- # verify
23
+ Each call to `encode()` generates a random salt, so the same input produces different hashes. `verify()` extracts the algorithm and salt from the hash string to compare.
24
+
25
+ ## Algorithms
26
+
27
+ Default is `scrypt` (password-safe, ~5ms per hash). For fast non-security hashing, use `sha256` or `sha512`.
21
28
 
22
- ```typescript
23
- import { hash } from "@prsm/hash";
29
+ ```js
30
+ import { Hasher } from "@prsm/hash"
24
31
 
25
- const valid = hash.verify(
26
- "secret",
27
- "sha256:0SA+O819D52jZOqWuzIWa+KLyT+Ck+b5ze4HI7fAJOhRW3FYk527GnuVOS/pricLy1KqwUfk5wWyQx4z5x3fsA==:wPs8DRMOrZEJYeaPxZzccGPJSozGvNqRhhS6f8ITOyM=",
28
- );
29
- // valid = true
32
+ const fast = new Hasher("sha256")
33
+ await fast.encode("not a password")
34
+
35
+ const secure = new Hasher("scrypt", 128) // custom salt length
36
+ await secure.encode("a password")
30
37
  ```
31
38
 
32
- # custom hasher
39
+ ## API
40
+
41
+ | Function | Description |
42
+ |---|---|
43
+ | `encode(string)` | Hash a string with a random salt. Returns a promise. |
44
+ | `verify(encoded, unencoded)` | Check if a string matches a hash. Returns a promise. |
33
45
 
34
- ```typescript
35
- import { Hasher } from "@prsm/hash";
46
+ ## Hash Format
36
47
 
37
- const hash = new Hasher("sha512", 128);
38
- // hash.create("..");
39
- // hash.verify("..", "sha512:...")
40
48
  ```
49
+ algorithm:salt:digest
50
+ ```
51
+
52
+ Hashes are self-describing - `verify()` reads the algorithm from the hash string, so a scrypt hash can be verified by a sha256-configured instance and vice versa.
53
+
54
+ ## License
41
55
 
56
+ Apache-2.0
package/package.json CHANGED
@@ -1,32 +1,31 @@
1
1
  {
2
2
  "name": "@prsm/hash",
3
- "version": "1.0.1",
4
- "description": "",
5
- "main": "./dist/index.js",
6
- "types": "./dist/index.d.ts",
3
+ "version": "2.0.1",
4
+ "description": "Simple salted string hashing on top of node:crypto",
7
5
  "type": "module",
8
- "publishConfig": {
9
- "access": "public",
10
- "registry": "https://registry.npmjs.org/"
11
- },
12
6
  "exports": {
13
7
  ".": {
14
- "require": "./dist/index.cjs",
15
- "import": "./dist/index.js",
16
- "types": "./dist/index.d.ts"
8
+ "types": "./types/index.d.ts",
9
+ "default": "./src/index.js"
17
10
  }
18
11
  },
19
- "keywords": [],
12
+ "types": "./types/index.d.ts",
13
+ "files": [
14
+ "src",
15
+ "types"
16
+ ],
17
+ "scripts": {
18
+ "test": "vitest --reporter=verbose --run",
19
+ "test:watch": "vitest",
20
+ "prepublishOnly": "npx tsc --declaration --allowJs --emitDeclarationOnly --skipLibCheck --target es2020 --module nodenext --moduleResolution nodenext --strict false --esModuleInterop true --outDir ./types src/index.js"
21
+ },
20
22
  "author": "nvms",
21
- "license": "ISC",
23
+ "license": "Apache-2.0",
22
24
  "devDependencies": {
23
- "@types/node": "^20.4.1",
24
- "bumpp": "^9.1.1",
25
- "tsup": "^7.1.0",
26
- "typescript": "^5.1.6"
25
+ "typescript": "^5.9.3",
26
+ "vitest": "^3.2.4"
27
27
  },
28
- "scripts": {
29
- "build": "rm -rf dist && tsup src/index.ts --format cjs,esm --dts --clean --minify",
30
- "release": "bumpp package.json --commit 'Release %s' --push --tag && pnpm publish --access public"
28
+ "engines": {
29
+ "node": ">=20"
31
30
  }
32
- }
31
+ }
package/src/index.js ADDED
@@ -0,0 +1,58 @@
1
+ import crypto from "node:crypto"
2
+
3
+ export class Hasher {
4
+ /**
5
+ * @param {'scrypt' | 'sha256' | 'sha512'} [algorithm]
6
+ * @param {number} [saltLength]
7
+ */
8
+ constructor(algorithm = "scrypt", saltLength = 64) {
9
+ this.algorithm = algorithm
10
+ this.saltLength = saltLength
11
+ }
12
+
13
+ /**
14
+ * @param {string} string
15
+ * @returns {Promise<string>} encoded hash in format "algorithm:salt:digest"
16
+ */
17
+ async encode(string) {
18
+ const salt = crypto.randomBytes(this.saltLength).toString("base64")
19
+ return this._hash(string, this.algorithm, salt)
20
+ }
21
+
22
+ /**
23
+ * @param {string} encoded - hash string to verify against
24
+ * @param {string} unencoded - plain string to check
25
+ * @returns {Promise<boolean>}
26
+ */
27
+ async verify(encoded, unencoded) {
28
+ const { algorithm, salt } = this._parse(encoded)
29
+ return (await this._hash(unencoded, algorithm, salt)) === encoded
30
+ }
31
+
32
+ async _hash(string, algorithm, salt) {
33
+ if (algorithm === "scrypt") {
34
+ const key = await new Promise((resolve, reject) => {
35
+ crypto.scrypt(string, salt, 64, (err, derivedKey) => {
36
+ if (err) reject(err)
37
+ else resolve(derivedKey)
38
+ })
39
+ })
40
+ return `scrypt:${salt}:${key.toString("base64")}`
41
+ }
42
+ const hash = crypto.createHash(algorithm)
43
+ hash.update(string)
44
+ hash.update(salt, "utf8")
45
+ return `${algorithm}:${salt}:${hash.digest("base64")}`
46
+ }
47
+
48
+ _parse(encoded) {
49
+ const parts = encoded.split(":")
50
+ if (parts.length !== 3) {
51
+ throw new Error(`Invalid hash string. Expected 3 parts, got ${parts.length}`)
52
+ }
53
+ return { algorithm: parts[0], salt: parts[1], digest: parts[2] }
54
+ }
55
+ }
56
+
57
+ const hash = new Hasher()
58
+ export default hash
@@ -0,0 +1,28 @@
1
+ export class Hasher {
2
+ /**
3
+ * @param {'scrypt' | 'sha256' | 'sha512'} [algorithm]
4
+ * @param {number} [saltLength]
5
+ */
6
+ constructor(algorithm?: "scrypt" | "sha256" | "sha512", saltLength?: number);
7
+ algorithm: "scrypt" | "sha256" | "sha512";
8
+ saltLength: number;
9
+ /**
10
+ * @param {string} string
11
+ * @returns {Promise<string>} encoded hash in format "algorithm:salt:digest"
12
+ */
13
+ encode(string: string): Promise<string>;
14
+ /**
15
+ * @param {string} encoded - hash string to verify against
16
+ * @param {string} unencoded - plain string to check
17
+ * @returns {Promise<boolean>}
18
+ */
19
+ verify(encoded: string, unencoded: string): Promise<boolean>;
20
+ _hash(string: any, algorithm: any, salt: any): Promise<string>;
21
+ _parse(encoded: any): {
22
+ algorithm: any;
23
+ salt: any;
24
+ digest: any;
25
+ };
26
+ }
27
+ export default hash;
28
+ declare const hash: Hasher;
package/dist/index.cjs DELETED
@@ -1 +0,0 @@
1
- var l=Object.create;var a=Object.defineProperty;var m=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var c=Object.getPrototypeOf,u=Object.prototype.hasOwnProperty;var d=(r,s)=>{for(var t in s)a(r,t,{get:s[t],enumerable:!0})},g=(r,s,t,h)=>{if(s&&typeof s=="object"||typeof s=="function")for(let i of p(s))!u.call(r,i)&&i!==t&&a(r,i,{get:()=>s[i],enumerable:!(h=m(s,i))||h.enumerable});return r};var A=(r,s,t)=>(t=r!=null?l(c(r)):{},g(s||!r||!r.__esModule?a(t,"default",{value:r,enumerable:!0}):t,r)),b=r=>g(a({},"__esModule",{value:!0}),r);var f={};d(f,{Hasher:()=>n,hash:()=>v});module.exports=b(f);var e=A(require("crypto"),1),n=class{constructor(s="sha256",t=64){this.algorithm=s,this.saltLength=t}verify(s,t){let{algorithm:h,salt:i}=this.parse(s);return this.hash(t,h,i)===s}encode(s){let t=e.default.randomBytes(this.saltLength).toString("base64");return this.hash(s,this.algorithm,t)}hash(s,t,h){let i=e.default.createHash(t);return i.update(s),i.update(h,"utf8"),`${t}:${h}:${i.digest("base64")}`}parse(s){let t=s.split(":");if(t.length!==3)throw new Error(`Invalid hash string. Expected 3 parts, got ${t.length}`);let h=t[0],i=t[1],o=t[2];return{algorithm:h,salt:i,digest:o}}},v=new n;0&&(module.exports={Hasher,hash});
package/dist/index.d.cts DELETED
@@ -1,13 +0,0 @@
1
- type Algorithm = "sha256" | "sha512";
2
- declare class Hasher {
3
- private algorithm;
4
- private saltLength;
5
- constructor(algorithm?: Algorithm, saltLength?: number);
6
- verify(encoded: string, unencoded: string): boolean;
7
- encode(string: string): string;
8
- hash(string: string, algorithm: Algorithm, salt: string): string;
9
- private parse;
10
- }
11
- declare const hash: Hasher;
12
-
13
- export { Hasher, hash };
package/dist/index.d.ts DELETED
@@ -1,13 +0,0 @@
1
- type Algorithm = "sha256" | "sha512";
2
- declare class Hasher {
3
- private algorithm;
4
- private saltLength;
5
- constructor(algorithm?: Algorithm, saltLength?: number);
6
- verify(encoded: string, unencoded: string): boolean;
7
- encode(string: string): string;
8
- hash(string: string, algorithm: Algorithm, salt: string): string;
9
- private parse;
10
- }
11
- declare const hash: Hasher;
12
-
13
- export { Hasher, hash };
package/dist/index.js DELETED
@@ -1 +0,0 @@
1
- import a from"crypto";var h=class{constructor(s="sha256",t=64){this.algorithm=s,this.saltLength=t}verify(s,t){let{algorithm:i,salt:r}=this.parse(s);return this.hash(t,i,r)===s}encode(s){let t=a.randomBytes(this.saltLength).toString("base64");return this.hash(s,this.algorithm,t)}hash(s,t,i){let r=a.createHash(t);return r.update(s),r.update(i,"utf8"),`${t}:${i}:${r.digest("base64")}`}parse(s){let t=s.split(":");if(t.length!==3)throw new Error(`Invalid hash string. Expected 3 parts, got ${t.length}`);let i=t[0],r=t[1],n=t[2];return{algorithm:i,salt:r,digest:n}}},o=new h;export{h as Hasher,o as hash};
package/tsconfig.json DELETED
@@ -1,20 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "module": "esnext",
4
- "target": "es2021",
5
- "moduleResolution": "node",
6
- "outDir": "./dist",
7
- "sourceMap": true,
8
- "experimentalDecorators": true,
9
- "emitDecoratorMetadata": true,
10
- "allowSyntheticDefaultImports": true,
11
- "noImplicitAny": false,
12
- "noImplicitReturns": false,
13
- "strictNullChecks": false,
14
- "baseUrl": "."
15
- },
16
- "exclude": [
17
- "node_modules",
18
- "lib"
19
- ]
20
- }