@oino-ts/hashid 0.3.3 → 0.3.4

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/package.json CHANGED
@@ -1,33 +1,33 @@
1
- {
2
- "name": "@oino-ts/hashid",
3
- "version": "0.3.3",
4
- "description": "OINO TS package for hashid's.",
5
- "author": "Matias Kiviniemi (pragmatta)",
6
- "license": "MPL-2.0",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/pragmatta/oino-ts.git"
10
- },
11
- "keywords": [
12
- "hashid",
13
- "typescript",
14
- "library"
15
- ],
16
- "main": "./dist/cjs/index.js",
17
- "module": "./dist/esm/index.js",
18
- "types": "./dist/types/index.d.ts",
19
- "dependencies": {
20
- "@types/node": "^20.12.7",
21
- "@oino-ts/common": "0.3.3",
22
- "base-x": "^5.0.0"
23
- },
24
- "devDependencies": {
25
- "@oino-ts/types": "0.3.3"
26
- },
27
- "files": [
28
- "src/*.ts",
29
- "dist/cjs/*.js",
30
- "dist/esm/*.js",
31
- "dist/types/*.d.ts"
32
- ]
33
- }
1
+ {
2
+ "name": "@oino-ts/hashid",
3
+ "version": "0.3.4",
4
+ "description": "OINO TS package for hashid's.",
5
+ "author": "Matias Kiviniemi (pragmatta)",
6
+ "license": "MPL-2.0",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/pragmatta/oino-ts.git"
10
+ },
11
+ "keywords": [
12
+ "hashid",
13
+ "typescript",
14
+ "library"
15
+ ],
16
+ "main": "./dist/cjs/index.js",
17
+ "module": "./dist/esm/index.js",
18
+ "types": "./dist/types/index.d.ts",
19
+ "dependencies": {
20
+ "@types/node": "^20.12.7",
21
+ "@oino-ts/common": "0.3.4",
22
+ "base-x": "^5.0.0"
23
+ },
24
+ "devDependencies": {
25
+ "@oino-ts/types": "0.3.4"
26
+ },
27
+ "files": [
28
+ "src/*.ts",
29
+ "dist/cjs/*.js",
30
+ "dist/esm/*.js",
31
+ "dist/types/*.d.ts"
32
+ ]
33
+ }
@@ -1,79 +1,79 @@
1
- /*
2
- * This Source Code Form is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
- */
6
-
7
- import { expect, test } from "bun:test";
8
-
9
- import { OINOHASHID_MAX_LENGTH, OINOHashid } from "./OINOHashid";
10
- import { OINOLog, OINOConsoleLog, OINOLogLevel } from "@oino-ts/common"
11
-
12
- Math.random()
13
-
14
- OINOLog.setLogger(new OINOConsoleLog(OINOLogLevel.error))
15
-
16
- function benchmarkOINOHashId(hashid: OINOHashid, id: string, iterations: number = 1000): number {
17
- const start = performance.now();
18
-
19
- for (let i = 0; i < iterations; i++) {
20
- const h = hashid.encode(id, '');
21
- hashid.decode(h)
22
- }
23
-
24
- const end = performance.now();
25
- const duration = end - start;
26
- return Math.round(iterations / duration)
27
- }
28
-
29
- await test("OINOHashId persistent", async () => {
30
-
31
- let hps_min = Number.MAX_VALUE
32
- let hps_max = 0
33
-
34
- for (let j=12; j<=OINOHASHID_MAX_LENGTH; j++) {
35
- const hashid:OINOHashid = new OINOHashid('c7a87c6a5df870842eb6ef6d7937f0b4', 'OINOHashIdTestApp-persistent', j, true)
36
- let i:number = 1
37
- let id:string = ''
38
- while (i <= j) {
39
- id += i % 10
40
- const hashed_id = hashid.encode(id, '')
41
- const id2 = hashid.decode(hashed_id)
42
- // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id)
43
- expect(id).toMatch(id2)
44
- i++
45
- }
46
- const hps = benchmarkOINOHashId(hashid, id, 4000)
47
- hps_min = Math.min(hps, hps_min)
48
- hps_max = Math.max(hps, hps_max)
49
- expect(hps_min).toBeGreaterThanOrEqual(15)
50
- expect(hps_max).toBeLessThanOrEqual(25)
51
- }
52
- console.log("OINOHashId persistent performance: " + hps_min + "k - " + hps_max + "k hashes per second")
53
- })
54
-
55
- await test("OINOHashId random", async () => {
56
-
57
- let hps_min = Number.MAX_VALUE
58
- let hps_max = 0
59
-
60
- for (let j=12; j<=OINOHASHID_MAX_LENGTH; j++) {
61
- const hashid:OINOHashid = new OINOHashid('c7a87c6a5df870842eb6ef6d7937f0b4', 'OINOHashIdTestApp-random', j, false)
62
- let i:number = 1
63
- let id:string = ''
64
- while (i <= j) {
65
- id += i % 10
66
- const hashed_id = hashid.encode(id, '')
67
- const id2 = hashid.decode(hashed_id)
68
- // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id)
69
- expect(id).toMatch(id2)
70
- i++
71
- }
72
- const hps = benchmarkOINOHashId(hashid, id, 4000)
73
- hps_min = Math.min(hps, hps_min)
74
- hps_max = Math.max(hps, hps_max)
75
- }
76
- console.log("OINOHashId random performance: " + hps_min + "k - " + hps_max + "k hashes per second")
77
- })
78
-
79
-
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { expect, test } from "bun:test";
8
+
9
+ import { OINOHASHID_MAX_LENGTH, OINOHashid } from "./OINOHashid";
10
+ import { OINOLog, OINOConsoleLog, OINOLogLevel } from "@oino-ts/common"
11
+
12
+ Math.random()
13
+
14
+ OINOLog.setLogger(new OINOConsoleLog(OINOLogLevel.error))
15
+
16
+ function benchmarkOINOHashId(hashid: OINOHashid, id: string, iterations: number = 1000): number {
17
+ const start = performance.now();
18
+
19
+ for (let i = 0; i < iterations; i++) {
20
+ const h = hashid.encode(id, '');
21
+ hashid.decode(h)
22
+ }
23
+
24
+ const end = performance.now();
25
+ const duration = end - start;
26
+ return Math.round(iterations / duration)
27
+ }
28
+
29
+ await test("OINOHashId persistent", async () => {
30
+
31
+ let hps_min = Number.MAX_VALUE
32
+ let hps_max = 0
33
+
34
+ for (let j=12; j<=OINOHASHID_MAX_LENGTH; j++) {
35
+ const hashid:OINOHashid = new OINOHashid('c7a87c6a5df870842eb6ef6d7937f0b4', 'OINOHashIdTestApp-persistent', j, true)
36
+ let i:number = 1
37
+ let id:string = ''
38
+ while (i <= j) {
39
+ id += i % 10
40
+ const hashed_id = hashid.encode(id, '')
41
+ const id2 = hashid.decode(hashed_id)
42
+ // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id)
43
+ expect(id).toMatch(id2)
44
+ i++
45
+ }
46
+ const hps = benchmarkOINOHashId(hashid, id, 4000)
47
+ hps_min = Math.min(hps, hps_min)
48
+ hps_max = Math.max(hps, hps_max)
49
+ expect(hps_min).toBeGreaterThanOrEqual(15)
50
+ expect(hps_max).toBeLessThanOrEqual(25)
51
+ }
52
+ console.log("OINOHashId persistent performance: " + hps_min + "k - " + hps_max + "k hashes per second")
53
+ })
54
+
55
+ await test("OINOHashId random", async () => {
56
+
57
+ let hps_min = Number.MAX_VALUE
58
+ let hps_max = 0
59
+
60
+ for (let j=12; j<=OINOHASHID_MAX_LENGTH; j++) {
61
+ const hashid:OINOHashid = new OINOHashid('c7a87c6a5df870842eb6ef6d7937f0b4', 'OINOHashIdTestApp-random', j, false)
62
+ let i:number = 1
63
+ let id:string = ''
64
+ while (i <= j) {
65
+ id += i % 10
66
+ const hashed_id = hashid.encode(id, '')
67
+ const id2 = hashid.decode(hashed_id)
68
+ // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id)
69
+ expect(id).toMatch(id2)
70
+ i++
71
+ }
72
+ const hps = benchmarkOINOHashId(hashid, id, 4000)
73
+ hps_min = Math.min(hps, hps_min)
74
+ hps_max = Math.max(hps, hps_max)
75
+ }
76
+ console.log("OINOHashId random performance: " + hps_min + "k - " + hps_max + "k hashes per second")
77
+ })
78
+
79
+
package/src/OINOHashid.ts CHANGED
@@ -1,113 +1,113 @@
1
- /*
2
- * This Source Code Form is subject to the terms of the Mozilla Public
3
- * License, v. 2.0. If a copy of the MPL was not distributed with this
4
- * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
- */
6
-
7
- import { BinaryLike, createCipheriv, createDecipheriv, createHmac, randomFillSync } from 'node:crypto';
8
- import basex from 'base-x'
9
-
10
- export const OINOHASHID_MIN_LENGTH:number = 12
11
- export const OINOHASHID_MAX_LENGTH:number = 42
12
- const OINOHASHID_ALPHABET:string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
13
- const hashidEncoder = basex(OINOHASHID_ALPHABET)
14
-
15
- /**
16
- * Hashid implementation for OINO API:s for the purpose of making it infeasible to scan
17
- * through numeric autoinc keys. It's not a solution to keeping the id secret in insecure
18
- * channels, just making it hard enough to not iterate through the entire key space. Half
19
- * of the the hashid length is nonce and half cryptotext, i.e. 16 char hashid 8 chars of
20
- * base64 encoded nonce ~ 6 bytes or 48 bits of entropy.
21
- *
22
- */
23
- export class OINOHashid {
24
-
25
- private _key:Buffer
26
- private _iv:Buffer
27
- private _domainId:string
28
- private _minLength:number
29
- private _staticIds
30
-
31
- /**
32
- * Hashid constructor
33
- *
34
- * @param key AES128 key (32 char hex-string)
35
- * @param domainId a sufficiently unique domain ID in which row-Id's are unique
36
- * @param minLength minimum length of nonce and crypto
37
- * @param staticIds whether hash values should remain static per row or random values
38
- *
39
- */
40
- constructor (key: string, domainId:string, minLength:number = OINOHASHID_MIN_LENGTH, staticIds:boolean = false) {
41
- this._domainId = domainId
42
- if ((minLength < OINOHASHID_MIN_LENGTH) || (minLength > OINOHASHID_MAX_LENGTH)) {
43
- throw Error("OINOHashid minLength (" + minLength + ")needs to be between " + OINOHASHID_MIN_LENGTH + " and " + OINOHASHID_MAX_LENGTH + "!")
44
- }
45
- this._minLength = Math.ceil(minLength/2)
46
- if (key.length != 32) {
47
- throw Error("OINOHashid key needs to be a 32 character hex-string!")
48
- }
49
- this._staticIds = staticIds
50
- this._key = Buffer.from(key, 'hex')
51
- this._iv = Buffer.alloc(16)
52
- }
53
-
54
- /**
55
- * Encode given id value as a hashid either using random data or given seed value for nonce.
56
- *
57
- * @param id numeric value
58
- * @param cellSeed a sufficiently unique seed for the current cell to keep hashids unique but persistent (e.g. fieldname + primarykey values)
59
- *
60
- */
61
- encode(id:string, cellSeed:string = ""):string {
62
-
63
- // if seed was given use it for pseudorandom chars, otherwise generate them
64
- let random_chars:string = ""
65
- if (this._staticIds) {
66
- const hmac_seed = createHmac('sha256', this._key as BinaryLike)
67
- hmac_seed.update(this._domainId + " " + cellSeed)
68
- random_chars = hashidEncoder.encode(hmac_seed.digest() as Uint8Array)
69
-
70
- } else {
71
- randomFillSync(this._iv as Uint8Array, 0, 16)
72
- random_chars = hashidEncoder.encode(this._iv as Uint8Array)
73
- }
74
- const hmac = createHmac('sha256', this._key as Uint8Array)
75
- let iv_seed:string = random_chars.substring(0, this._minLength)
76
- hmac.update(this._domainId + " " + iv_seed)
77
- const iv_data:Buffer = hmac.digest()
78
- iv_data.copy(this._iv as Uint8Array, 0, 0, 16)
79
-
80
- let plaintext = id.toString()
81
- if (plaintext.length < this._minLength) {
82
- plaintext += " " + random_chars.substring(random_chars.length - (this._minLength - plaintext.length - 1))
83
- }
84
-
85
- const cipher = createCipheriv('aes-128-gcm', this._key as Uint8Array, this._iv as Uint8Array)
86
- const cryptotext = hashidEncoder.encode(cipher.update(plaintext, 'utf8') as Uint8Array) + hashidEncoder.encode(cipher.final() as Uint8Array)
87
- return iv_seed + cryptotext
88
- }
89
-
90
- /**
91
- * Decode given hashid.
92
- *
93
- * @param hashid value
94
- *
95
- */
96
- decode(hashid:string):string {
97
- // reproduce nonce from seed
98
- const hmac = createHmac('sha256', this._key as Uint8Array)
99
- const iv_seed = hashid.substring(0, this._minLength)
100
- hmac.update(this._domainId + " " + iv_seed)
101
- const hash:Buffer = hmac.digest()
102
- hash.copy(this._iv as Uint8Array, 0, 0, 16)
103
-
104
- const cryptotext:string = hashid.substring(this._minLength)
105
- const cryptobytes:Buffer = Buffer.from(hashidEncoder.decode(cryptotext))
106
- const decipher = createDecipheriv('aes-128-gcm', this._key as Uint8Array, this._iv as Uint8Array)
107
- const plaintext = decipher.update(cryptobytes as Uint8Array, undefined, 'utf8') //, cryptotext, 'base64url', 'utf8')
108
-
109
- return plaintext.split(" ")[0]
110
- }
111
-
112
-
113
- }
1
+ /*
2
+ * This Source Code Form is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
+ */
6
+
7
+ import { BinaryLike, createCipheriv, createDecipheriv, createHmac, randomFillSync } from 'node:crypto';
8
+ import basex from 'base-x'
9
+
10
+ export const OINOHASHID_MIN_LENGTH:number = 12
11
+ export const OINOHASHID_MAX_LENGTH:number = 42
12
+ const OINOHASHID_ALPHABET:string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
13
+ const hashidEncoder = basex(OINOHASHID_ALPHABET)
14
+
15
+ /**
16
+ * Hashid implementation for OINO API:s for the purpose of making it infeasible to scan
17
+ * through numeric autoinc keys. It's not a solution to keeping the id secret in insecure
18
+ * channels, just making it hard enough to not iterate through the entire key space. Half
19
+ * of the the hashid length is nonce and half cryptotext, i.e. 16 char hashid 8 chars of
20
+ * base64 encoded nonce ~ 6 bytes or 48 bits of entropy.
21
+ *
22
+ */
23
+ export class OINOHashid {
24
+
25
+ private _key:Buffer
26
+ private _iv:Buffer
27
+ private _domainId:string
28
+ private _minLength:number
29
+ private _staticIds
30
+
31
+ /**
32
+ * Hashid constructor
33
+ *
34
+ * @param key AES128 key (32 char hex-string)
35
+ * @param domainId a sufficiently unique domain ID in which row-Id's are unique
36
+ * @param minLength minimum length of nonce and crypto
37
+ * @param staticIds whether hash values should remain static per row or random values
38
+ *
39
+ */
40
+ constructor (key: string, domainId:string, minLength:number = OINOHASHID_MIN_LENGTH, staticIds:boolean = false) {
41
+ this._domainId = domainId
42
+ if ((minLength < OINOHASHID_MIN_LENGTH) || (minLength > OINOHASHID_MAX_LENGTH)) {
43
+ throw Error("OINOHashid minLength (" + minLength + ")needs to be between " + OINOHASHID_MIN_LENGTH + " and " + OINOHASHID_MAX_LENGTH + "!")
44
+ }
45
+ this._minLength = Math.ceil(minLength/2)
46
+ if (key.length != 32) {
47
+ throw Error("OINOHashid key needs to be a 32 character hex-string!")
48
+ }
49
+ this._staticIds = staticIds
50
+ this._key = Buffer.from(key, 'hex')
51
+ this._iv = Buffer.alloc(16)
52
+ }
53
+
54
+ /**
55
+ * Encode given id value as a hashid either using random data or given seed value for nonce.
56
+ *
57
+ * @param id numeric value
58
+ * @param cellSeed a sufficiently unique seed for the current cell to keep hashids unique but persistent (e.g. fieldname + primarykey values)
59
+ *
60
+ */
61
+ encode(id:string, cellSeed:string = ""):string {
62
+
63
+ // if seed was given use it for pseudorandom chars, otherwise generate them
64
+ let random_chars:string = ""
65
+ if (this._staticIds) {
66
+ const hmac_seed = createHmac('sha256', this._key as BinaryLike)
67
+ hmac_seed.update(this._domainId + " " + cellSeed)
68
+ random_chars = hashidEncoder.encode(hmac_seed.digest() as Uint8Array)
69
+
70
+ } else {
71
+ randomFillSync(this._iv as Uint8Array, 0, 16)
72
+ random_chars = hashidEncoder.encode(this._iv as Uint8Array)
73
+ }
74
+ const hmac = createHmac('sha256', this._key as Uint8Array)
75
+ let iv_seed:string = random_chars.substring(0, this._minLength)
76
+ hmac.update(this._domainId + " " + iv_seed)
77
+ const iv_data:Buffer = hmac.digest()
78
+ iv_data.copy(this._iv as Uint8Array, 0, 0, 16)
79
+
80
+ let plaintext = id.toString()
81
+ if (plaintext.length < this._minLength) {
82
+ plaintext += " " + random_chars.substring(random_chars.length - (this._minLength - plaintext.length - 1))
83
+ }
84
+
85
+ const cipher = createCipheriv('aes-128-gcm', this._key as Uint8Array, this._iv as Uint8Array)
86
+ const cryptotext = hashidEncoder.encode(cipher.update(plaintext, 'utf8') as Uint8Array) + hashidEncoder.encode(cipher.final() as Uint8Array)
87
+ return iv_seed + cryptotext
88
+ }
89
+
90
+ /**
91
+ * Decode given hashid.
92
+ *
93
+ * @param hashid value
94
+ *
95
+ */
96
+ decode(hashid:string):string {
97
+ // reproduce nonce from seed
98
+ const hmac = createHmac('sha256', this._key as Uint8Array)
99
+ const iv_seed = hashid.substring(0, this._minLength)
100
+ hmac.update(this._domainId + " " + iv_seed)
101
+ const hash:Buffer = hmac.digest()
102
+ hash.copy(this._iv as Uint8Array, 0, 0, 16)
103
+
104
+ const cryptotext:string = hashid.substring(this._minLength)
105
+ const cryptobytes:Buffer = Buffer.from(hashidEncoder.decode(cryptotext))
106
+ const decipher = createDecipheriv('aes-128-gcm', this._key as Uint8Array, this._iv as Uint8Array)
107
+ const plaintext = decipher.update(cryptobytes as Uint8Array, undefined, 'utf8') //, cryptotext, 'base64url', 'utf8')
108
+
109
+ return plaintext.split(" ")[0]
110
+ }
111
+
112
+
113
+ }
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export { OINOHashid } from "./OINOHashid.js"
1
+ export { OINOHashid } from "./OINOHashid.js"