@sequencemedia/pbkdf2 1.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.
@@ -0,0 +1,34 @@
1
+ const {
2
+ env: {
3
+ NODE_ENV = 'development'
4
+ }
5
+ } = process
6
+
7
+ function env () {
8
+ return (
9
+ NODE_ENV === 'production'
10
+ )
11
+ }
12
+
13
+ const presets = [
14
+ [
15
+ '@babel/env', {
16
+ targets: {
17
+ node: 'current'
18
+ },
19
+ useBuiltIns: 'usage',
20
+ corejs: 3
21
+ }
22
+ ]
23
+ ]
24
+
25
+ const plugins = []
26
+
27
+ module.exports = (api) => {
28
+ if (api) api.cache.using(env)
29
+
30
+ return {
31
+ presets,
32
+ plugins
33
+ }
34
+ }
@@ -0,0 +1,19 @@
1
+ import argsMap from '#utils/args-map'
2
+
3
+ export const ITERATIONS = (
4
+ argsMap.has('ITERATIONS')
5
+ ? argsMap.get('ITERATIONS')
6
+ : 10
7
+ )
8
+
9
+ export const KEYLEN = (
10
+ argsMap.has('KEYLEN')
11
+ ? argsMap.get('KEYLEN')
12
+ : 64
13
+ )
14
+
15
+ export const DIGEST = (
16
+ argsMap.has('DIGEST')
17
+ ? argsMap.get('DIGEST')
18
+ : 'sha512'
19
+ )
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ declare module '@sequencemedia/pbkdf2' {
2
+ export function compare(password: string, params: { salt: string, hash: Buffer, iteration?: number, keylen?: number, digest?: string }): Promise<boolean>
3
+ export function hash (password: string, params: { salt: string, iteration?: number, keylen?: number, digest?: string }): Promise<Buffer>
4
+ export function salt (size: number): Promise<Buffer>
5
+ }
package/index.mjs ADDED
@@ -0,0 +1,9 @@
1
+ import compare from '#utils/compare'
2
+ import hash from '#utils/hash'
3
+ import salt from '#utils/salt'
4
+
5
+ export {
6
+ compare,
7
+ hash,
8
+ salt
9
+ }
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@sequencemedia/pbkdf2",
3
+ "version": "1.0.1",
4
+ "main": "./index.mjs",
5
+ "type": "module",
6
+ "author": {
7
+ "name": "Jonathan Perry for Sequence Media Limited",
8
+ "email": "sequencemedia@sequencemedia.net",
9
+ "url": "http://sequencemedia.net"
10
+ },
11
+ "license": "ISC",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git@github.com:sequencemedia/pbkdf2.git"
15
+ },
16
+ "scripts": {
17
+ "lint": "cross-env NODE_ENV=production eslint . --ext .mjs --ext .cjs",
18
+ "lint:fix": "npm run lint -- --fix",
19
+ "test": "cross-env NODE_ENV=test mocha tests --recursive --verbose --color",
20
+ "compare": "cross-env NODE_ENV=production node scripts/compare.mjs",
21
+ "hash": "cross-env NODE_ENV=production node scripts/hash.mjs",
22
+ "salt": "cross-env NODE_ENV=production node scripts/salt.mjs",
23
+ "prepare": "husky install"
24
+ },
25
+ "dependencies": {
26
+ "debug": "^4.3.4",
27
+ "yargs-parser": "^21.1.1"
28
+ },
29
+ "devDependencies": {
30
+ "@babel/cli": "^7.20.7",
31
+ "@babel/core": "^7.20.12",
32
+ "@babel/eslint-parser": "^7.19.1",
33
+ "@babel/preset-env": "^7.20.2",
34
+ "@sequencemedia/hooks": "^1.0.314",
35
+ "@types/node": "^18.11.18",
36
+ "chai": "^4.3.7",
37
+ "core-js": "^3.27.2",
38
+ "cross-env": "^7.0.3",
39
+ "eslint": "^8.32.0",
40
+ "eslint-config-standard": "^17.0.0",
41
+ "eslint-plugin-import": "^2.27.5",
42
+ "eslint-plugin-node": "^11.1.0",
43
+ "eslint-plugin-promise": "^6.1.1",
44
+ "husky": "^8.0.3",
45
+ "mocha": "^10.2.0"
46
+ },
47
+ "imports": {
48
+ "#config/*": "./config/*.mjs",
49
+ "#utils/*": "./utils/*.mjs"
50
+ }
51
+ }
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+
3
+ import argsMap from '#utils/args-map'
4
+ import compare from '#utils/compare'
5
+ import {
6
+ fromBase64ToBuffer,
7
+ fromBase64
8
+ } from '#utils/common'
9
+ import {
10
+ ITERATIONS,
11
+ KEYLEN,
12
+ DIGEST
13
+ } from '#config/compare'
14
+
15
+ if (!argsMap.has('password')) throw new Error('Parameter `password` is required')
16
+ const password = argsMap.get('password')
17
+
18
+ if (!argsMap.has('hash')) throw new Error('Parameter `hash` is required')
19
+ const hash = fromBase64ToBuffer(argsMap.get('hash'))
20
+
21
+ if (!argsMap.has('salt')) throw new Error('Parameter `salt` is required')
22
+ const salt = fromBase64(argsMap.get('salt'))
23
+
24
+ const iterations = (
25
+ argsMap.has('iterations')
26
+ ? argsMap.get('iterations')
27
+ : ITERATIONS
28
+ )
29
+
30
+ const keylen = (
31
+ argsMap.has('keylen')
32
+ ? argsMap.get('keylen')
33
+ : KEYLEN
34
+ )
35
+
36
+ const digest = (
37
+ argsMap.has('digest')
38
+ ? argsMap.get('digest')
39
+ : DIGEST
40
+ )
41
+
42
+ compare(password, { hash, salt, iterations, keylen, digest })
43
+ .then((isValid) => {
44
+ console.log(isValid ? '👍' : '👎')
45
+ })
46
+ .catch(() => {
47
+ console.error('💩')
48
+ })
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ import argsMap from '#utils/args-map'
4
+ import {
5
+ fromBase64,
6
+ fromBufferToBase64
7
+ } from '#utils/common'
8
+ import hash from '#utils/hash'
9
+ import {
10
+ ITERATIONS,
11
+ KEYLEN,
12
+ DIGEST
13
+ } from '#config/compare'
14
+
15
+ if (!argsMap.has('password')) throw new Error('Parameter `password` is required')
16
+ const password = argsMap.get('password')
17
+
18
+ if (!argsMap.has('salt')) throw new Error('Parameter `salt` is required')
19
+ const salt = fromBase64(argsMap.get('salt'))
20
+
21
+ const iterations = (
22
+ argsMap.has('iterations')
23
+ ? argsMap.get('iterations')
24
+ : ITERATIONS
25
+ )
26
+
27
+ const keylen = (
28
+ argsMap.has('keylen')
29
+ ? argsMap.get('keylen')
30
+ : KEYLEN
31
+ )
32
+
33
+ const digest = (
34
+ argsMap.has('digest')
35
+ ? argsMap.get('digest')
36
+ : DIGEST
37
+ )
38
+
39
+ hash(password, { salt, iterations, keylen, digest })
40
+ .then((buffer) => {
41
+ console.log(fromBufferToBase64(buffer))
42
+ })
43
+ .catch(() => {
44
+ console.error('💩')
45
+ })
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+
3
+ import argsMap from '#utils/args-map'
4
+ import {
5
+ fromBufferToBase64
6
+ } from '#utils/common'
7
+ import salt from '#utils/salt'
8
+
9
+ if (!argsMap.has('size')) throw new Error('Parameter `size` is required')
10
+ const size = argsMap.get('size')
11
+
12
+ salt(size)
13
+ .then((buffer) => {
14
+ console.log(fromBufferToBase64(buffer))
15
+ })
16
+ .catch(() => {
17
+ console.error('💩')
18
+ })
@@ -0,0 +1,135 @@
1
+ import {
2
+ expect
3
+ } from 'chai'
4
+ import {
5
+ fromBase64,
6
+ toBase64,
7
+ fromBase64ToBuffer,
8
+ fromBufferToBase64
9
+ } from '#utils/common'
10
+
11
+ describe('`#utils/common`', () => {
12
+ describe('fromBase64', () => it('is a function', () => expect(fromBase64).to.be.a('function')))
13
+
14
+ describe('toBase64', () => it('is a function', () => expect(toBase64).to.be.a('function')))
15
+
16
+ describe('fromBase64ToBuffer', () => it('is a function', () => expect(fromBase64ToBuffer).to.be.a('function')))
17
+
18
+ describe('fromBufferToBase64', () => it('is a function', () => expect(fromBufferToBase64).to.be.a('function')))
19
+
20
+ describe('fromBase64()', () => {
21
+ describe('User 1', () => {
22
+ it('returns a string', () => {
23
+ const BASE64 = 'YzFjMTY4MmMyMWE4MjFkYjRhNWE5YjJmNDY0OTUzM2Q='
24
+ const UTF8 = 'c1c1682c21a821db4a5a9b2f4649533d'
25
+
26
+ return expect(fromBase64(BASE64)).to.equal(UTF8)
27
+ })
28
+ })
29
+
30
+ describe('User 2', () => {
31
+ it('returns a string', () => {
32
+ const BASE64 = 'MDI2OGM0MDU4NWJiOTgxNWEyYzQwN2RkYjM1YTY5YWU='
33
+ const UTF8 = '0268c40585bb9815a2c407ddb35a69ae'
34
+
35
+ return expect(fromBase64(BASE64)).to.equal(UTF8)
36
+ })
37
+ })
38
+
39
+ describe('User 3', () => {
40
+ it('returns a string', () => {
41
+ const BASE64 = 'ZDI3OTZmZWU3M2I3M2Y3MTgxMmYyNGM3MjU4NDE2NDM='
42
+ const UTF8 = 'd2796fee73b73f71812f24c725841643'
43
+
44
+ return expect(fromBase64(BASE64)).to.equal(UTF8)
45
+ })
46
+ })
47
+ })
48
+
49
+ describe('toBase64()', () => {
50
+ describe('User 1', () => {
51
+ it('returns a string', () => {
52
+ const UTF8 = 'c1c1682c21a821db4a5a9b2f4649533d'
53
+ const BASE64 = 'YzFjMTY4MmMyMWE4MjFkYjRhNWE5YjJmNDY0OTUzM2Q='
54
+
55
+ return expect(toBase64(UTF8)).to.equal(BASE64)
56
+ })
57
+ })
58
+
59
+ describe('User 2', () => {
60
+ it('returns a string', () => {
61
+ const UTF8 = '0268c40585bb9815a2c407ddb35a69ae'
62
+ const BASE64 = 'MDI2OGM0MDU4NWJiOTgxNWEyYzQwN2RkYjM1YTY5YWU='
63
+
64
+ return expect(toBase64(UTF8)).to.equal(BASE64)
65
+ })
66
+ })
67
+
68
+ describe('User 3', () => {
69
+ it('returns a string', () => {
70
+ const UTF8 = 'd2796fee73b73f71812f24c725841643'
71
+ const BASE64 = 'ZDI3OTZmZWU3M2I3M2Y3MTgxMmYyNGM3MjU4NDE2NDM='
72
+
73
+ return expect(toBase64(UTF8)).to.equal(BASE64)
74
+ })
75
+ })
76
+ })
77
+
78
+ describe('fromBase64ToBuffer()', () => {
79
+ describe('User 1', () => {
80
+ it('returns a buffer', () => {
81
+ const BASE64 = 'YzFjMTY4MmMyMWE4MjFkYjRhNWE5YjJmNDY0OTUzM2Q='
82
+ const BUFFER = Buffer.from('c1c1682c21a821db4a5a9b2f4649533d')
83
+
84
+ return expect(Buffer.compare(fromBase64ToBuffer(BASE64), BUFFER)).to.equal(0)
85
+ })
86
+ })
87
+
88
+ describe('User 2', () => {
89
+ it('returns a buffer', () => {
90
+ const BASE64 = 'MDI2OGM0MDU4NWJiOTgxNWEyYzQwN2RkYjM1YTY5YWU='
91
+ const BUFFER = Buffer.from('0268c40585bb9815a2c407ddb35a69ae')
92
+
93
+ return expect(Buffer.compare(fromBase64ToBuffer(BASE64), BUFFER)).to.equal(0)
94
+ })
95
+ })
96
+
97
+ describe('User 3', () => {
98
+ it('returns a buffer', () => {
99
+ const BASE64 = 'ZDI3OTZmZWU3M2I3M2Y3MTgxMmYyNGM3MjU4NDE2NDM='
100
+ const BUFFER = Buffer.from('d2796fee73b73f71812f24c725841643')
101
+
102
+ return expect(Buffer.compare(fromBase64ToBuffer(BASE64), BUFFER)).to.equal(0)
103
+ })
104
+ })
105
+ })
106
+
107
+ describe('fromBufferToBase64()', () => {
108
+ describe('User 1', () => {
109
+ it('returns a string', () => {
110
+ const BUFFER = Buffer.from('c1c1682c21a821db4a5a9b2f4649533d')
111
+ const BASE64 = 'YzFjMTY4MmMyMWE4MjFkYjRhNWE5YjJmNDY0OTUzM2Q='
112
+
113
+ return expect(fromBufferToBase64(BUFFER)).to.equal(BASE64)
114
+ })
115
+ })
116
+
117
+ describe('User 2', () => {
118
+ it('returns a string', () => {
119
+ const BUFFER = Buffer.from('0268c40585bb9815a2c407ddb35a69ae')
120
+ const BASE64 = 'MDI2OGM0MDU4NWJiOTgxNWEyYzQwN2RkYjM1YTY5YWU='
121
+
122
+ return expect(fromBufferToBase64(BUFFER)).to.equal(BASE64)
123
+ })
124
+ })
125
+
126
+ describe('User 3', () => {
127
+ it('returns a string', () => {
128
+ const BUFFER = Buffer.from('d2796fee73b73f71812f24c725841643')
129
+ const BASE64 = 'ZDI3OTZmZWU3M2I3M2Y3MTgxMmYyNGM3MjU4NDE2NDM='
130
+
131
+ return expect(fromBufferToBase64(BUFFER)).to.equal(BASE64)
132
+ })
133
+ })
134
+ })
135
+ })
@@ -0,0 +1,81 @@
1
+ import {
2
+ expect
3
+ } from 'chai'
4
+ import compare from '#utils/compare'
5
+
6
+ describe('`#utils/compare`', () => {
7
+ describe('compare', () => it('is a function', () => expect(compare).to.be.a('function')))
8
+
9
+ describe('compare()', () => {
10
+ const ITERATIONS = 32768
11
+
12
+ describe('User 1', () => {
13
+ const HASH = Buffer.from('kOhpM7//M4I9+WcWLH7vbb/yN2+8qKyxQ0xAfI4JzamcRMdwwl7vAh0ch7UJ0JoBe9AqaAGuUJZF/mVA9EqaLQ==', 'base64')
14
+ const SALT = 'c1c1682c21a821db4a5a9b2f4649533d'
15
+
16
+ describe('`password` is valid', () => {
17
+ it('returns true', async () => {
18
+ const password = '^49pPaRdVSNC'
19
+ const isValid = await compare(password, { hash: HASH, salt: SALT, iterations: ITERATIONS })
20
+
21
+ return expect(isValid).to.be.true
22
+ })
23
+ })
24
+
25
+ describe('`password` is not valid', () => {
26
+ it('returns false', async () => {
27
+ const password = 'not password'
28
+ const isValid = await compare(password, { hash: HASH, salt: SALT, iterations: ITERATIONS })
29
+
30
+ return expect(isValid).to.be.false
31
+ })
32
+ })
33
+ })
34
+
35
+ describe('User 2', () => {
36
+ const HASH = Buffer.from('BrtOel2qy5HrYWn45jBKf5b3HUKdVeQfIj1ljhGxQ/SiWsmMMuOl756H4MXkMOnVJKrgFfHFy7cgFXUaSFA2ow==', 'base64')
37
+ const SALT = '0268c40585bb9815a2c407ddb35a69ae'
38
+
39
+ describe('`password` is valid', () => {
40
+ it('returns true', async () => {
41
+ const password = 'S0uSaC&FzDOO'
42
+ const isValid = await compare(password, { hash: HASH, salt: SALT, iterations: ITERATIONS })
43
+
44
+ return expect(isValid).to.be.true
45
+ })
46
+ })
47
+
48
+ describe('`password` is not valid', () => {
49
+ it('returns false', async () => {
50
+ const password = 'not password'
51
+ const isValid = await compare(password, { hash: HASH, salt: SALT, iterations: ITERATIONS })
52
+
53
+ return expect(isValid).to.be.false
54
+ })
55
+ })
56
+ })
57
+
58
+ describe('User 3', () => {
59
+ const HASH = Buffer.from('bV76sVqsM0doKBFnaHsZQC9OgcVnF/+gzkG7HFXdD7jyQdN1PqCVijyXKMijmYQPWJohi5f7sVxhoLJ5kf8aQA==', 'base64')
60
+ const SALT = 'd2796fee73b73f71812f24c725841643'
61
+
62
+ describe('`password` is valid', () => {
63
+ it('returns true', async () => {
64
+ const password = '@^EJmdlkt5A#'
65
+ const isValid = await compare(password, { hash: HASH, salt: SALT, iterations: ITERATIONS })
66
+
67
+ return expect(isValid).to.be.true
68
+ })
69
+ })
70
+
71
+ describe('`password` is not valid', () => {
72
+ it('returns false', async () => {
73
+ const password = 'not password'
74
+ const isValid = await compare(password, { hash: HASH, salt: SALT, iterations: ITERATIONS })
75
+
76
+ return expect(isValid).to.be.false
77
+ })
78
+ })
79
+ })
80
+ })
81
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,5 @@
1
+ {
2
+ "compilerOptions": {
3
+ "types": ["node"]
4
+ }
5
+ }
@@ -0,0 +1,9 @@
1
+ import yargsParser from 'yargs-parser'
2
+
3
+ const {
4
+ argv = []
5
+ } = process
6
+
7
+ export const args = argv.slice(2)
8
+
9
+ export default new Map(Object.entries(yargsParser(args)))
@@ -0,0 +1,15 @@
1
+ export function fromBase64ToBuffer (string) {
2
+ return Buffer.from(string, 'base64')
3
+ }
4
+
5
+ export function fromBufferToBase64 (buffer) {
6
+ return buffer.toString('base64')
7
+ }
8
+
9
+ export function fromBase64 (string) {
10
+ return Buffer.from(string, 'base64').toString()
11
+ }
12
+
13
+ export function toBase64 (string) {
14
+ return Buffer.from(string).toString('base64')
15
+ }
@@ -0,0 +1,23 @@
1
+ import {
2
+ timingSafeEqual
3
+ } from 'node:crypto'
4
+ import hash from './hash.mjs'
5
+
6
+ /**
7
+ * function compare (password, salt, h, complete = () => {}) {
8
+ * hash(password, salt, (e, v) => {
9
+ * if (e) return complete(e)
10
+ *
11
+ * complete(null, timingSafeEqual(h, v))
12
+ * })
13
+ * }
14
+ */
15
+
16
+ export default async function compare (password, {
17
+ hash: alpha,
18
+ ...params
19
+ } = {}) {
20
+ const omega = await hash(password, params)
21
+
22
+ return timingSafeEqual(alpha, omega)
23
+ }
package/utils/hash.mjs ADDED
@@ -0,0 +1,31 @@
1
+ import {
2
+ pbkdf2
3
+ } from 'node:crypto'
4
+ import {
5
+ ITERATIONS,
6
+ KEYLEN,
7
+ DIGEST
8
+ } from '#config/compare'
9
+
10
+ /**
11
+ * function hash (password, salt, complete = () => {}) {
12
+ * pbkdf2(password, salt, ITERATIONS, KEYLEN, DIGEST, complete)
13
+ * }
14
+ */
15
+
16
+ export default function hash (password, {
17
+ salt,
18
+ iterations = ITERATIONS,
19
+ keylen = KEYLEN,
20
+ digest = DIGEST
21
+ } = {}) {
22
+ return (
23
+ new Promise((resolve, reject) => {
24
+ pbkdf2(password, salt, iterations, keylen, digest, (e, buffer) => {
25
+ (!e)
26
+ ? resolve(buffer)
27
+ : reject(e)
28
+ })
29
+ })
30
+ )
31
+ }
package/utils/salt.mjs ADDED
@@ -0,0 +1,21 @@
1
+ import {
2
+ randomBytes
3
+ } from 'node:crypto'
4
+
5
+ /**
6
+ * function salt (size, complete = () => {}) {
7
+ * randomBytes(size, complete)
8
+ * }
9
+ */
10
+
11
+ export default function salt (size) {
12
+ return (
13
+ new Promise((resolve, reject) => {
14
+ randomBytes(size, (e, buffer) => {
15
+ (!e)
16
+ ? resolve(buffer)
17
+ : reject(e)
18
+ })
19
+ })
20
+ )
21
+ }