inslash 1.1.0 → 1.1.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.
Files changed (2) hide show
  1. package/index.js +45 -42
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -20,7 +20,7 @@ const generateApiKey = (options = {}) => {
20
20
  length = 32,
21
21
  encoding = "hex"
22
22
  } = options;
23
-
23
+
24
24
  const random = crypto.randomBytes(length).toString(encoding);
25
25
  return prefix ? `${prefix}_${random}` : random;
26
26
  };
@@ -38,7 +38,7 @@ const hashWithSalt = async (value, salt, secret, options) => {
38
38
  console.timeEnd(`Hash operation (${iterations} iterations)`);
39
39
 
40
40
  const result = digest.toString(encoding).slice(0, hashLength);
41
-
41
+
42
42
  return {
43
43
  hash: result,
44
44
  timing: iterations, // For informational purposes
@@ -60,10 +60,10 @@ const encodePassport = (meta) => {
60
60
  meta.hash,
61
61
  history
62
62
  ];
63
-
63
+
64
64
  // Add optional metadata if present
65
65
  if (meta.encoding) parts.push(meta.encoding);
66
-
66
+
67
67
  return parts.join("$");
68
68
  };
69
69
 
@@ -71,13 +71,15 @@ const encodePassport = (meta) => {
71
71
  const decodePassport = (passport) => {
72
72
  const parts = passport.split("$");
73
73
  if (parts[1] !== "inslash") throw new Error("Invalid passport format");
74
-
75
- // Check version (new format includes version at index 2)
76
- const version = parts[2] || "1";
77
-
78
- if (version === "1") {
79
- // Legacy format
80
- const [ , , algorithm, iterations, saltLength, hashLength, salt, hash, history ] = parts;
74
+
75
+ // Detect format: check if parts[2] is a numeric version or an algorithm name
76
+ // Legacy format: $inslash$algorithm$iterations$...
77
+ // New format: $inslash$version$algorithm$iterations$...
78
+ const isLegacyFormat = SUPPORTED_ALGORITHMS.includes(parts[2]);
79
+
80
+ if (isLegacyFormat) {
81
+ // Legacy format without explicit version
82
+ const [, , algorithm, iterations, saltLength, hashLength, salt, hash, history] = parts;
81
83
  return {
82
84
  version: "1",
83
85
  algorithm,
@@ -86,11 +88,12 @@ const decodePassport = (passport) => {
86
88
  hashLength: Number(hashLength),
87
89
  salt,
88
90
  hash,
89
- history: JSON.parse(Buffer.from(history, "base64").toString())
91
+ history: history ? JSON.parse(Buffer.from(history, "base64").toString()) : []
90
92
  };
91
93
  } else {
92
- // New format with encoding
93
- const [ , , , algorithm, iterations, saltLength, hashLength, salt, hash, history, encoding ] = parts;
94
+ // New format with explicit version
95
+ const version = parts[2];
96
+ const [, , , algorithm, iterations, saltLength, hashLength, salt, hash, history, encoding] = parts;
94
97
  return {
95
98
  version,
96
99
  algorithm,
@@ -99,7 +102,7 @@ const decodePassport = (passport) => {
99
102
  hashLength: Number(hashLength),
100
103
  salt,
101
104
  hash,
102
- history: JSON.parse(Buffer.from(history, "base64").toString()),
105
+ history: history ? JSON.parse(Buffer.from(history, "base64").toString()) : [],
103
106
  encoding: encoding || "hex"
104
107
  };
105
108
  }
@@ -109,22 +112,22 @@ const decodePassport = (passport) => {
109
112
  const hash = async (value, secret, opts = {}) => {
110
113
  if (!secret) throw new Error("Secret key is required");
111
114
  if (typeof value !== "string" || !value) throw new Error("Value to hash must be a non-empty string");
112
-
115
+
113
116
  // Validate algorithm
114
117
  if (opts.algorithm && !SUPPORTED_ALGORITHMS.includes(opts.algorithm)) {
115
118
  throw new Error(`Unsupported algorithm: ${opts.algorithm}. Supported: ${SUPPORTED_ALGORITHMS.join(", ")}`);
116
119
  }
117
-
120
+
118
121
  // Validate encoding
119
122
  if (opts.encoding && !SUPPORTED_ENCODINGS.includes(opts.encoding)) {
120
123
  throw new Error(`Unsupported encoding: ${opts.encoding}. Supported: ${SUPPORTED_ENCODINGS.join(", ")}`);
121
124
  }
122
-
125
+
123
126
  const options = { ...DEFAULTS, ...opts };
124
127
  const salt = createSalt(options.saltLength);
125
128
  const pepper = process.env.HASH_PEPPER || "";
126
129
  const valueWithPepper = value + pepper;
127
-
130
+
128
131
  const { hash: hashed } = await hashWithSalt(valueWithPepper, salt, secret, options);
129
132
 
130
133
  const meta = {
@@ -163,21 +166,21 @@ const verify = async (value, passport, secret, opts = {}) => {
163
166
  encoding: meta.encoding || "hex",
164
167
  ...opts
165
168
  };
166
-
169
+
167
170
  const pepper = process.env.HASH_PEPPER || "";
168
171
  const valueWithPepper = value + pepper;
169
-
172
+
170
173
  const { hash: computed } = await hashWithSalt(valueWithPepper, meta.salt, secret, options);
171
-
174
+
172
175
  // Use timing-safe comparison
173
176
  const valid = crypto.timingSafeEqual(
174
177
  Buffer.from(computed, options.encoding),
175
178
  Buffer.from(meta.hash, meta.encoding || "hex")
176
179
  );
177
-
180
+
178
181
  let needsUpgrade = false;
179
182
  let upgradeReasons = [];
180
-
183
+
181
184
  if (opts.iterations && opts.iterations > meta.iterations) {
182
185
  needsUpgrade = true;
183
186
  upgradeReasons.push(`iterations (${meta.iterations} -> ${opts.iterations})`);
@@ -190,10 +193,10 @@ const verify = async (value, passport, secret, opts = {}) => {
190
193
  needsUpgrade = true;
191
194
  upgradeReasons.push(`encoding (${meta.encoding} -> ${opts.encoding})`);
192
195
  }
193
-
196
+
194
197
  let upgradedPassport = null;
195
198
  let upgradedMetadata = null;
196
-
199
+
197
200
  if (valid && needsUpgrade) {
198
201
  const newMeta = { ...meta, ...opts };
199
202
  newMeta.history = (meta.history || []).concat([
@@ -205,14 +208,14 @@ const verify = async (value, passport, secret, opts = {}) => {
205
208
  reason: "security upgrade"
206
209
  }
207
210
  ]);
208
-
211
+
209
212
  const newSalt = createSalt(newMeta.saltLength);
210
213
  const { hash: newHash } = await hashWithSalt(valueWithPepper, newSalt, secret, newMeta);
211
-
214
+
212
215
  newMeta.salt = newSalt;
213
216
  newMeta.hash = newHash;
214
217
  newMeta.version = "2";
215
-
218
+
216
219
  upgradedPassport = encodePassport(newMeta);
217
220
  upgradedMetadata = {
218
221
  algorithm: newMeta.algorithm,
@@ -220,7 +223,7 @@ const verify = async (value, passport, secret, opts = {}) => {
220
223
  encoding: newMeta.encoding
221
224
  };
222
225
  }
223
-
226
+
224
227
  return {
225
228
  valid,
226
229
  needsUpgrade,
@@ -281,14 +284,14 @@ const comparePassports = (passport1, passport2) => {
281
284
  try {
282
285
  const meta1 = decodePassport(passport1);
283
286
  const meta2 = decodePassport(passport2);
284
-
287
+
285
288
  return {
286
289
  sameAlgorithm: meta1.algorithm === meta2.algorithm,
287
290
  sameIterations: meta1.iterations === meta2.iterations,
288
291
  sameSalt: meta1.salt === meta2.salt,
289
292
  sameHash: meta1.hash === meta2.hash,
290
293
  sameEncoding: (meta1.encoding || "hex") === (meta2.encoding || "hex"),
291
- 完全相同: meta1.hash === meta2.hash && meta1.salt === meta2.salt
294
+ 完全相同: meta1.hash === meta2.hash && meta1.salt === meta2.salt
292
295
  };
293
296
  } catch (error) {
294
297
  return {
@@ -304,16 +307,16 @@ const estimateSecurity = (passport) => {
304
307
  const meta = decodePassport(passport);
305
308
  const now = new Date();
306
309
  const year = now.getFullYear();
307
-
310
+
308
311
  // Rough estimate of security level
309
312
  let score = 0;
310
313
  let recommendations = [];
311
-
314
+
312
315
  // Algorithm score
313
316
  if (meta.algorithm === "sha512") score += 40;
314
317
  else if (meta.algorithm === "sha384") score += 35;
315
318
  else if (meta.algorithm === "sha256") score += 30;
316
-
319
+
317
320
  // Iterations score (based on year)
318
321
  if (meta.iterations >= 300000) score += 40;
319
322
  else if (meta.iterations >= 200000) score += 35;
@@ -323,7 +326,7 @@ const estimateSecurity = (passport) => {
323
326
  score += 15;
324
327
  recommendations.push("Increase iterations (current: " + meta.iterations + ")");
325
328
  }
326
-
329
+
327
330
  // Salt length
328
331
  if (meta.saltLength >= 32) score += 20;
329
332
  else if (meta.saltLength >= 16) score += 15;
@@ -331,16 +334,16 @@ const estimateSecurity = (passport) => {
331
334
  score += 5;
332
335
  recommendations.push("Increase salt length (current: " + meta.saltLength + ")");
333
336
  }
334
-
337
+
335
338
  // Hash length
336
339
  if (meta.hashLength >= 32) score += 10;
337
-
340
+
338
341
  let level = "Weak";
339
342
  if (score >= 90) level = "Excellent";
340
343
  else if (score >= 75) level = "Strong";
341
344
  else if (score >= 60) level = "Good";
342
345
  else if (score >= 40) level = "Fair";
343
-
346
+
344
347
  return {
345
348
  score,
346
349
  level,
@@ -368,19 +371,19 @@ module.exports = {
368
371
  verify,
369
372
  encodePassport,
370
373
  decodePassport,
371
-
374
+
372
375
  // New enhanced functions
373
376
  batchVerify,
374
377
  inspectPassport,
375
378
  comparePassports,
376
379
  estimateSecurity,
377
380
  generateApiKey,
378
-
381
+
379
382
  // Utilities
380
383
  DEFAULTS,
381
384
  SUPPORTED_ALGORITHMS,
382
385
  SUPPORTED_ENCODINGS,
383
-
386
+
384
387
  // Version info
385
388
  VERSION: "1.1.0"
386
389
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inslash",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "A modern, upgradeable, and secure password hashing utility with passport encoding, hash ancestry, and comprehensive security features.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",