@oino-ts/hashid 0.1.0 → 0.1.2

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.
@@ -5,13 +5,13 @@
5
5
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
6
  */
7
7
  Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.OINOHashid = void 0;
8
+ exports.OINOHashid = exports.OINOHASHID_MAX_LENGTH = exports.OINOHASHID_MIN_LENGTH = void 0;
9
9
  const node_crypto_1 = require("node:crypto");
10
10
  const base_x_1 = require("base-x");
11
- const HASHID_MIN_LENGTH = 12;
12
- const HASHID_MAX_LENGTH = 40;
13
- const HASHID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
14
- const hashidEncoder = (0, base_x_1.default)(HASHID_ALPHABET);
11
+ exports.OINOHASHID_MIN_LENGTH = 12;
12
+ exports.OINOHASHID_MAX_LENGTH = 42;
13
+ const OINOHASHID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
14
+ const hashidEncoder = (0, base_x_1.default)(OINOHASHID_ALPHABET);
15
15
  /**
16
16
  * Hashid implementation for OINO API:s for the purpose of making it infeasible to scan
17
17
  * through numeric autoinc keys. It's not a solution to keeping the id secret in insecure
@@ -35,10 +35,10 @@ class OINOHashid {
35
35
  * @param staticIds whether hash values should remain static per row or random values
36
36
  *
37
37
  */
38
- constructor(key, domainId, minLength = HASHID_MIN_LENGTH, staticIds = false) {
38
+ constructor(key, domainId, minLength = exports.OINOHASHID_MIN_LENGTH, staticIds = false) {
39
39
  this._domainId = domainId;
40
- if ((minLength < HASHID_MIN_LENGTH) || (minLength > HASHID_MAX_LENGTH)) {
41
- throw Error("OINOHashid minLength needs to be between " + HASHID_MIN_LENGTH + " and " + HASHID_MAX_LENGTH + "!");
40
+ if ((minLength < exports.OINOHASHID_MIN_LENGTH) || (minLength > exports.OINOHASHID_MAX_LENGTH)) {
41
+ throw Error("OINOHashid minLength (" + minLength + ")needs to be between " + exports.OINOHASHID_MIN_LENGTH + " and " + exports.OINOHASHID_MAX_LENGTH + "!");
42
42
  }
43
43
  this._minLength = Math.ceil(minLength / 2);
44
44
  if (key.length != 32) {
@@ -59,15 +59,15 @@ class OINOHashid {
59
59
  // if seed was given use it for pseudorandom chars, otherwise generate them
60
60
  let random_chars = "";
61
61
  if (this._staticIds) {
62
- const hmac_seed = (0, node_crypto_1.createHmac)('sha1', this._key);
62
+ const hmac_seed = (0, node_crypto_1.createHmac)('sha256', this._key);
63
63
  hmac_seed.update(this._domainId + " " + cellSeed);
64
- random_chars = hashidEncoder.encode(hmac_seed.digest()); // hmac_seed.digest('base64url')
64
+ random_chars = hashidEncoder.encode(hmac_seed.digest());
65
65
  }
66
66
  else {
67
67
  (0, node_crypto_1.randomFillSync)(this._iv, 0, 16);
68
- random_chars = hashidEncoder.encode(this._iv); // this._iv.toString('base64url')
68
+ random_chars = hashidEncoder.encode(this._iv);
69
69
  }
70
- const hmac = (0, node_crypto_1.createHmac)('sha1', this._key);
70
+ const hmac = (0, node_crypto_1.createHmac)('sha256', this._key);
71
71
  let iv_seed = random_chars.substring(0, this._minLength);
72
72
  hmac.update(this._domainId + " " + iv_seed);
73
73
  const iv_data = hmac.digest();
@@ -88,7 +88,7 @@ class OINOHashid {
88
88
  */
89
89
  decode(hashid) {
90
90
  // reproduce nonce from seed
91
- const hmac = (0, node_crypto_1.createHmac)('sha1', this._key);
91
+ const hmac = (0, node_crypto_1.createHmac)('sha256', this._key);
92
92
  const iv_seed = hashid.substring(0, this._minLength);
93
93
  hmac.update(this._domainId + " " + iv_seed);
94
94
  const hash = hmac.digest();
@@ -5,10 +5,10 @@
5
5
  */
6
6
  import { createCipheriv, createDecipheriv, createHmac, randomFillSync } from 'node:crypto';
7
7
  import basex from 'base-x';
8
- const HASHID_MIN_LENGTH = 12;
9
- const HASHID_MAX_LENGTH = 40;
10
- const HASHID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
11
- const hashidEncoder = basex(HASHID_ALPHABET);
8
+ export const OINOHASHID_MIN_LENGTH = 12;
9
+ export const OINOHASHID_MAX_LENGTH = 42;
10
+ const OINOHASHID_ALPHABET = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
11
+ const hashidEncoder = basex(OINOHASHID_ALPHABET);
12
12
  /**
13
13
  * Hashid implementation for OINO API:s for the purpose of making it infeasible to scan
14
14
  * through numeric autoinc keys. It's not a solution to keeping the id secret in insecure
@@ -32,10 +32,10 @@ export class OINOHashid {
32
32
  * @param staticIds whether hash values should remain static per row or random values
33
33
  *
34
34
  */
35
- constructor(key, domainId, minLength = HASHID_MIN_LENGTH, staticIds = false) {
35
+ constructor(key, domainId, minLength = OINOHASHID_MIN_LENGTH, staticIds = false) {
36
36
  this._domainId = domainId;
37
- if ((minLength < HASHID_MIN_LENGTH) || (minLength > HASHID_MAX_LENGTH)) {
38
- throw Error("OINOHashid minLength needs to be between " + HASHID_MIN_LENGTH + " and " + HASHID_MAX_LENGTH + "!");
37
+ if ((minLength < OINOHASHID_MIN_LENGTH) || (minLength > OINOHASHID_MAX_LENGTH)) {
38
+ throw Error("OINOHashid minLength (" + minLength + ")needs to be between " + OINOHASHID_MIN_LENGTH + " and " + OINOHASHID_MAX_LENGTH + "!");
39
39
  }
40
40
  this._minLength = Math.ceil(minLength / 2);
41
41
  if (key.length != 32) {
@@ -56,15 +56,15 @@ export class OINOHashid {
56
56
  // if seed was given use it for pseudorandom chars, otherwise generate them
57
57
  let random_chars = "";
58
58
  if (this._staticIds) {
59
- const hmac_seed = createHmac('sha1', this._key);
59
+ const hmac_seed = createHmac('sha256', this._key);
60
60
  hmac_seed.update(this._domainId + " " + cellSeed);
61
- random_chars = hashidEncoder.encode(hmac_seed.digest()); // hmac_seed.digest('base64url')
61
+ random_chars = hashidEncoder.encode(hmac_seed.digest());
62
62
  }
63
63
  else {
64
64
  randomFillSync(this._iv, 0, 16);
65
- random_chars = hashidEncoder.encode(this._iv); // this._iv.toString('base64url')
65
+ random_chars = hashidEncoder.encode(this._iv);
66
66
  }
67
- const hmac = createHmac('sha1', this._key);
67
+ const hmac = createHmac('sha256', this._key);
68
68
  let iv_seed = random_chars.substring(0, this._minLength);
69
69
  hmac.update(this._domainId + " " + iv_seed);
70
70
  const iv_data = hmac.digest();
@@ -85,7 +85,7 @@ export class OINOHashid {
85
85
  */
86
86
  decode(hashid) {
87
87
  // reproduce nonce from seed
88
- const hmac = createHmac('sha1', this._key);
88
+ const hmac = createHmac('sha256', this._key);
89
89
  const iv_seed = hashid.substring(0, this._minLength);
90
90
  hmac.update(this._domainId + " " + iv_seed);
91
91
  const hash = hmac.digest();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/hashid",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "OINO TS package for hashid's.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -18,11 +18,11 @@
18
18
  "types": "./dist/types/index.d.ts",
19
19
  "dependencies": {
20
20
  "@types/node": "^20.12.7",
21
- "@oino-ts/common": "0.1.0",
22
- "base-x": "5.0.0"
21
+ "@oino-ts/common": "0.1.2",
22
+ "base-x": "^5.0.0"
23
23
  },
24
24
  "devDependencies": {
25
- "@oino-ts/types": "0.1.0"
25
+ "@oino-ts/types": "0.1.2"
26
26
  },
27
27
  "files": [
28
28
  "src/*.ts",
@@ -6,43 +6,74 @@
6
6
 
7
7
  import { expect, test } from "bun:test";
8
8
 
9
- import { OINOHashid } from "./OINOHashid";
10
- import { OINOLog, OINOConsoleLog, OINOLogLevel } from "@oino-ts/log"
9
+ import { OINOHASHID_MAX_LENGTH, OINOHashid } from "./OINOHashid";
10
+ import { OINOLog, OINOConsoleLog, OINOLogLevel } from "@oino-ts/common"
11
11
 
12
12
  Math.random()
13
13
 
14
14
  OINOLog.setLogger(new OINOConsoleLog(OINOLogLevel.error))
15
15
 
16
- test("OINOHashId persistent", async () => {
17
- for (let j=12; j<=40; j++) {
18
- const hashid:OINOHashid = new OINOHashid('c7a87c6a5df870842eb6ef6d7937f0b4', 'OINOHashIdTestApp-persistent', j)
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)
19
36
  let i:number = 1
20
37
  let id:string = ''
21
38
  while (i <= j) {
22
39
  id += i % 10
23
40
  const hashed_id = hashid.encode(id, '')
24
41
  const id2 = hashid.decode(hashed_id)
25
- // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id + ", id2: " + id2)
42
+ // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id)
26
43
  expect(id).toMatch(id2)
27
44
  i++
28
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)
29
51
  }
30
-
52
+ console.log("OINOHashId persistent performance: " + hps_min + "k - " + hps_max + "k hashes per second")
31
53
  })
32
54
 
33
- test("OINOHashId random", async () => {
34
- for (let j=12; j<=40; j++) {
35
- const hashid:OINOHashid = new OINOHashid('c7a87c6a5df870842eb6ef6d7937f0b4', 'OINOHashIdTestApp-random', j, true)
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)
36
62
  let i:number = 1
37
63
  let id:string = ''
38
64
  while (i <= j) {
39
65
  id += i % 10
40
66
  const hashed_id = hashid.encode(id, '')
41
67
  const id2 = hashid.decode(hashed_id)
42
- // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id + ", id2: " + id2)
68
+ // console.log("j: " + j + ", i: " + i + ", id: " + id + ", hashed_id: " + hashed_id)
43
69
  expect(id).toMatch(id2)
44
70
  i++
45
71
  }
72
+ const hps = benchmarkOINOHashId(hashid, id, 4000)
73
+ hps_min = Math.min(hps, hps_min)
74
+ hps_max = Math.max(hps, hps_max)
46
75
  }
47
-
76
+ console.log("OINOHashId random performance: " + hps_min + "k - " + hps_max + "k hashes per second")
48
77
  })
78
+
79
+
package/src/OINOHashid.ts CHANGED
@@ -4,13 +4,13 @@
4
4
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
5
5
  */
6
6
 
7
- import { createCipheriv, createDecipheriv, createHmac, randomFillSync } from 'node:crypto';
7
+ import { BinaryLike, createCipheriv, createDecipheriv, createHmac, randomFillSync } from 'node:crypto';
8
8
  import basex from 'base-x'
9
9
 
10
- const HASHID_MIN_LENGTH:number = 12
11
- const HASHID_MAX_LENGTH:number = 40
12
- const HASHID_ALPHABET:string = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
13
- const hashidEncoder = basex(HASHID_ALPHABET)
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
14
 
15
15
  /**
16
16
  * Hashid implementation for OINO API:s for the purpose of making it infeasible to scan
@@ -37,10 +37,10 @@ export class OINOHashid {
37
37
  * @param staticIds whether hash values should remain static per row or random values
38
38
  *
39
39
  */
40
- constructor (key: string, domainId:string, minLength:number = HASHID_MIN_LENGTH, staticIds:boolean = false) {
40
+ constructor (key: string, domainId:string, minLength:number = OINOHASHID_MIN_LENGTH, staticIds:boolean = false) {
41
41
  this._domainId = domainId
42
- if ((minLength < HASHID_MIN_LENGTH) || (minLength > HASHID_MAX_LENGTH)) {
43
- throw Error("OINOHashid minLength needs to be between " + HASHID_MIN_LENGTH + " and " + HASHID_MAX_LENGTH + "!")
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
44
  }
45
45
  this._minLength = Math.ceil(minLength/2)
46
46
  if (key.length != 32) {
@@ -63,27 +63,27 @@ export class OINOHashid {
63
63
  // if seed was given use it for pseudorandom chars, otherwise generate them
64
64
  let random_chars:string = ""
65
65
  if (this._staticIds) {
66
- const hmac_seed = createHmac('sha1', this._key)
66
+ const hmac_seed = createHmac('sha256', this._key as BinaryLike)
67
67
  hmac_seed.update(this._domainId + " " + cellSeed)
68
- random_chars = hashidEncoder.encode(hmac_seed.digest()) // hmac_seed.digest('base64url')
68
+ random_chars = hashidEncoder.encode(hmac_seed.digest() as Uint8Array)
69
69
 
70
70
  } else {
71
- randomFillSync(this._iv, 0, 16)
72
- random_chars = hashidEncoder.encode(this._iv) // this._iv.toString('base64url')
71
+ randomFillSync(this._iv as Uint8Array, 0, 16)
72
+ random_chars = hashidEncoder.encode(this._iv as Uint8Array)
73
73
  }
74
- const hmac = createHmac('sha1', this._key)
74
+ const hmac = createHmac('sha256', this._key as Uint8Array)
75
75
  let iv_seed:string = random_chars.substring(0, this._minLength)
76
76
  hmac.update(this._domainId + " " + iv_seed)
77
77
  const iv_data:Buffer = hmac.digest()
78
- iv_data.copy(this._iv, 0, 0, 16)
78
+ iv_data.copy(this._iv as Uint8Array, 0, 0, 16)
79
79
 
80
80
  let plaintext = id.toString()
81
81
  if (plaintext.length < this._minLength) {
82
82
  plaintext += " " + random_chars.substring(random_chars.length - (this._minLength - plaintext.length - 1))
83
83
  }
84
84
 
85
- const cipher = createCipheriv('aes-128-gcm', this._key, this._iv)
86
- const cryptotext = hashidEncoder.encode(cipher.update(plaintext, 'utf8')) + hashidEncoder.encode(cipher.final())
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
87
  return iv_seed + cryptotext
88
88
  }
89
89
 
@@ -95,16 +95,16 @@ export class OINOHashid {
95
95
  */
96
96
  decode(hashid:string):string {
97
97
  // reproduce nonce from seed
98
- const hmac = createHmac('sha1', this._key)
98
+ const hmac = createHmac('sha256', this._key as Uint8Array)
99
99
  const iv_seed = hashid.substring(0, this._minLength)
100
100
  hmac.update(this._domainId + " " + iv_seed)
101
101
  const hash:Buffer = hmac.digest()
102
- hash.copy(this._iv, 0, 0, 16)
102
+ hash.copy(this._iv as Uint8Array, 0, 0, 16)
103
103
 
104
104
  const cryptotext:string = hashid.substring(this._minLength)
105
105
  const cryptobytes:Buffer = Buffer.from(hashidEncoder.decode(cryptotext))
106
- const decipher = createDecipheriv('aes-128-gcm', this._key, this._iv)
107
- const plaintext = decipher.update(cryptobytes, undefined, 'utf8') //, cryptotext, 'base64url', 'utf8')
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
108
 
109
109
  return plaintext.split(" ")[0]
110
110
  }