@prsm/hash 1.0.2 → 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 +39 -28
- package/package.json +20 -21
- package/src/index.js +58 -0
- package/types/index.d.ts +28 -0
- package/dist/index.cjs +0 -1
- package/dist/index.d.cts +0 -13
- package/dist/index.d.ts +0 -13
- package/dist/index.js +0 -1
- package/tsconfig.json +0 -20
package/README.md
CHANGED
|
@@ -1,45 +1,56 @@
|
|
|
1
|
-
# hash
|
|
1
|
+
# @prsm/hash
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Salted string hashing on top of `node:crypto`. Zero dependencies. Defaults to `scrypt` for password-safe hashing.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Installation
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
```bash
|
|
8
|
+
npm install @prsm/hash
|
|
9
|
+
```
|
|
8
10
|
|
|
9
|
-
##
|
|
11
|
+
## Usage
|
|
10
12
|
|
|
11
|
-
```
|
|
12
|
-
import
|
|
13
|
+
```js
|
|
14
|
+
import hash from "@prsm/hash"
|
|
13
15
|
|
|
14
|
-
hash.
|
|
15
|
-
|
|
16
|
-
hash.create("secret");
|
|
16
|
+
const encoded = await hash.encode("my password")
|
|
17
|
+
// scrypt:UfH7lmEc5d...:6qG75Cp5hys...
|
|
17
18
|
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
// sha256:e16qmZpJiy1qvGycPkJz0qQnCdyAguGAFV8rqCokiFml10nl9lVU1v0hZ6QBy+laI0AYkHsYtt6wMkEOuNhpMw==:L3bHZeriSAjy8wEIz/fURxhOqxa8KltuvpHPE/nE/eQ=
|
|
21
|
-
// sha256:0SA+O819D52jZOqWuzIWa+KLyT+Ck+b5ze4HI7fAJOhRW3FYk527GnuVOS/pricLy1KqwUfk5wWyQx4z5x3fsA==:wPs8DRMOrZEJYeaPxZzccGPJSozGvNqRhhS6f8ITOyM=
|
|
19
|
+
await hash.verify(encoded, "my password") // true
|
|
20
|
+
await hash.verify(encoded, "wrong") // false
|
|
22
21
|
```
|
|
23
22
|
|
|
24
|
-
|
|
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`.
|
|
28
|
+
|
|
29
|
+
```js
|
|
30
|
+
import { Hasher } from "@prsm/hash"
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
const fast = new Hasher("sha256")
|
|
33
|
+
await fast.encode("not a password")
|
|
28
34
|
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
"sha256:0SA+O819D52jZOqWuzIWa+KLyT+Ck+b5ze4HI7fAJOhRW3FYk527GnuVOS/pricLy1KqwUfk5wWyQx4z5x3fsA==:wPs8DRMOrZEJYeaPxZzccGPJSozGvNqRhhS6f8ITOyM=",
|
|
32
|
-
);
|
|
33
|
-
// valid = true
|
|
35
|
+
const secure = new Hasher("scrypt", 128) // custom salt length
|
|
36
|
+
await secure.encode("a password")
|
|
34
37
|
```
|
|
35
38
|
|
|
36
|
-
##
|
|
39
|
+
## API
|
|
37
40
|
|
|
38
|
-
|
|
39
|
-
|
|
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. |
|
|
40
45
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
46
|
+
## Hash Format
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
algorithm:salt:digest
|
|
44
50
|
```
|
|
45
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
|
|
55
|
+
|
|
56
|
+
Apache-2.0
|
package/package.json
CHANGED
|
@@ -1,32 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@prsm/hash",
|
|
3
|
-
"version": "
|
|
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
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"types": "./dist/index.d.ts"
|
|
8
|
+
"types": "./types/index.d.ts",
|
|
9
|
+
"default": "./src/index.js"
|
|
17
10
|
}
|
|
18
11
|
},
|
|
19
|
-
"
|
|
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": "
|
|
23
|
+
"license": "Apache-2.0",
|
|
22
24
|
"devDependencies": {
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
"tsup": "^7.1.0",
|
|
26
|
-
"typescript": "^5.1.6"
|
|
25
|
+
"typescript": "^5.9.3",
|
|
26
|
+
"vitest": "^3.2.4"
|
|
27
27
|
},
|
|
28
|
-
"
|
|
29
|
-
"
|
|
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
|
package/types/index.d.ts
ADDED
|
@@ -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
|
-
}
|