@carecard/auth-util 3.23.0 → 3.24.0

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.
@@ -107,10 +107,10 @@ depend on those folders being present.
107
107
  - `lib/cryptoUtilAuth.js` wraps Node.js `crypto` primitives for signing,
108
108
  verifying, generating HMACs, and salts.
109
109
  - `lib/keyGen.js` owns Ed25519 and RSA key generation.
110
- - `lib/stringUtilAuth.js` owns legacy base64, base64-url-safe, JWT, and
111
- password-hash string parsing helpers.
112
- - Keep direct exports preferred and deprecated nested exports backward
113
- compatible unless a breaking change is explicitly requested.
110
+ - `lib/stringUtilAuth.js` owns legacy base64, base64-url-safe, and JWT string
111
+ helpers. Password-hash parsing stays private to `lib/pwdUtilAuth.js`.
112
+ - Keep direct exports as the public package API. Do not add password aliases,
113
+ nested password objects, or compatibility parsers.
114
114
  - Preserve CommonJS exports unless the repository intentionally migrates module
115
115
  systems.
116
116
 
@@ -133,27 +133,38 @@ depend on those folders being present.
133
133
 
134
134
  ## Password And Crypto Layer
135
135
 
136
- - Preserve the HMAC-based password hashing behavior.
137
- - Preserve the saved password hash string format:
136
+ - Create password credentials with Node.js `crypto.argon2` using Argon2id,
137
+ version 19, 19,456 KiB memory, two passes, one degree of parallelism, a
138
+ unique 16-byte random salt, a 32-byte tag, and a pepper of at least 32 UTF-8
139
+ bytes.
140
+ - Persist only the strict unpadded PHC string:
138
141
 
139
142
  ```text
140
- $1$base64(algorithm)$base64(hash)$base64(salt)$
143
+ $argon2id$v=19$m=19456,t=2,p=1$base64(salt)$base64(tag)
141
144
  ```
142
145
 
143
- - Always handle salt automatically when creating new password hashes.
144
- - Do not silently change cryptographic defaults, password hash string format,
145
- salt behavior, or key output format.
146
- - Prefer Node.js `crypto` primitives already used by the package over new
147
- dependencies.
146
+ - `createPasswordHash(password, pepper)` and
147
+ `verifyPassword(password, savedHash, pepper)` are the only public password
148
+ credential APIs; both are asynchronous.
149
+ - Generate salts internally for every credential. Never accept caller-selected
150
+ salts and never persist or log the pepper.
151
+ - Normalize well-formed Unicode passwords to NFC before deriving the tag, but
152
+ do not trim or impose password policy here. `@carecard/validate` owns policy,
153
+ while login intentionally permits existing passwords of any length.
154
+ - Return `false` for malformed, non-canonical, differently parameterized, or
155
+ legacy saved hashes. Throw for programmer/configuration errors such as an
156
+ invalid pepper.
157
+ - Do not add legacy HMAC verification. Seeded accounts must be rehashed before
158
+ the Argon2id-only service starts.
159
+ - Prefer Node.js `crypto` primitives over new dependencies.
148
160
 
149
161
  ## String Utilities
150
162
 
151
163
  - Use `stringUtilAuth` for base64 and URL-safe string transformations to keep
152
164
  parsing and serialization consistent.
153
- - Keep legacy formatted string parsing behavior backward-compatible.
154
- - Cover edge cases for malformed JWT strings, malformed password hashes, bad
155
- base64 input, missing segments, and unsupported algorithms when these paths
156
- change.
165
+ - Do not expose password-hash parsing through `stringUtilAuth`.
166
+ - Cover edge cases for malformed JWT strings, bad base64 input, missing
167
+ segments, and unsupported algorithms when these paths change.
157
168
 
158
169
  ## Security And Error Handling
159
170
 
package/.nvmrc ADDED
@@ -0,0 +1 @@
1
+ 24.20.0
package/index.d.ts CHANGED
@@ -112,21 +112,6 @@ export interface AuthenticatedRequest extends Request {
112
112
  visitor?: VisitorRequestObject | null;
113
113
  }
114
114
 
115
- /**
116
- * Represents the decomposed parts of a stored password hash.
117
- * @deprecated Use native Buffer methods or other modern alternatives.
118
- */
119
- export interface PasswordParts {
120
- /** Version indicator of the hashing format. */
121
- version: string;
122
- /** Base64 encoded algorithm name. */
123
- alg: string;
124
- /** Base64 encoded password hash. */
125
- hash: string;
126
- /** Base64 encoded random salt. */
127
- salt: string;
128
- }
129
-
130
115
  /**
131
116
  * Contains a pair of public and private cryptographic keys.
132
117
  */
@@ -171,38 +156,6 @@ export const jwtUtilAuth: {
171
156
  getHeaderPayloadFromJwt: (jwt: string) => JwtParts | null;
172
157
  };
173
158
 
174
- /**
175
- * Utility functions for password hashing and verification.
176
- * @deprecated use direct imports of the new functions.
177
- */
178
- export const pwdUtilAuth: {
179
- /**
180
- * Generates a password hash using a random salt and specified algorithm.
181
- * @param password - The plain-text password to hash.
182
- * @param secret - A pepper/secret key to combine with the password.
183
- * @param algorithm - The hashing algorithm to use (e.g., 'sha256').
184
- * @returns A string containing the formatted hash with metadata ($1$alg$hash$salt$) or null if an error occurs.
185
- */
186
- createPasswordHashWithRandomSalt: (
187
- password: string,
188
- secret: string,
189
- algorithm: string,
190
- ) => string | null;
191
- /**
192
- * Generates a password hash using the same algorithm and salt from a previously saved hash.
193
- * Useful for verifying a password against a stored hash.
194
- * @param password - The plain-text password to verify.
195
- * @param savedPasswordHash - The full stored hash string (including salt and metadata).
196
- * @param secret - The pepper/secret key used for hashing.
197
- * @returns A hash string that should match the saved hash if the password is correct, or null if an error occurs.
198
- */
199
- createPasswordHashBasedOnSavedAlgorithmSalt: (
200
- password: string,
201
- savedPasswordHash: string,
202
- secret: string,
203
- ) => string | null;
204
- };
205
-
206
159
  /**
207
160
  * Utility functions for string manipulation, base64 encoding, and parsing auth-related strings.
208
161
  * @deprecated Use native Buffer methods or other modern alternatives.
@@ -236,13 +189,6 @@ export const stringUtilAuth: {
236
189
  * @returns Decoded plain-text string.
237
190
  */
238
191
  base64ToAscii: (codedString: string) => string;
239
- /**
240
- * Parses a stored password hash string into its constituent parts.
241
- * @deprecated Use native Buffer methods or other modern alternatives.
242
- * @param passwordHash - The formatted hash string ($1$alg$hash$salt$).
243
- * @returns A PasswordParts object or null if the format is invalid.
244
- */
245
- dollarSignConnectedStringToAlgorithmHashSalt: (passwordHash: string) => PasswordParts | null;
246
192
  /**
247
193
  * Splits a JWT into its three base64-encoded string parts (header, payload, signature).
248
194
  * @deprecated Use native Buffer methods or other modern alternatives.
@@ -322,28 +268,22 @@ export function jwtVerifySignedToken(jwt: string, publicKey: string): boolean;
322
268
  export function jwtGetHeaderPayload(jwt: string): JwtParts | null;
323
269
 
324
270
  /**
325
- * Generates a password hash using a random salt and specified algorithm.
271
+ * Creates an Argon2id PHC password hash with a unique random salt.
326
272
  * @param password - The plain-text password to hash.
327
- * @param secret - A pepper/secret key to combine with the password.
328
- * @param algorithm - The hashing algorithm to use (e.g., 'sha256').
329
- * @returns A string containing the formatted hash with metadata ($1$alg$hash$salt$) or null if an error occurs.
273
+ * @param pepper - A secret pepper containing at least 32 UTF-8 bytes.
274
+ * @returns The strict Argon2id PHC string.
330
275
  */
331
- export function passwordCreateHashWithRandomSalt(
332
- password: string,
333
- secret: string,
334
- algorithm: string,
335
- ): string | null;
276
+ export function createPasswordHash(password: string, pepper: string): Promise<string>;
336
277
 
337
278
  /**
338
- * Generates a password hash using the same algorithm and salt from a previously saved hash.
339
- * Useful for verifying a password against a stored hash.
279
+ * Verifies a password against a strict CareCard Argon2id PHC hash.
340
280
  * @param password - The plain-text password to verify.
341
- * @param savedPasswordHash - The full stored hash string (including salt and metadata).
342
- * @param secret - The pepper/secret key used for hashing.
343
- * @returns A hash string that should match the saved hash if the password is correct, or null if an error occurs.
281
+ * @param savedHash - The stored Argon2id PHC hash.
282
+ * @param pepper - The same secret pepper used when creating the hash.
283
+ * @returns False for a mismatch or malformed/unsupported saved hash.
344
284
  */
345
- export function passwordCreateHashFromSavedHash(
285
+ export function verifyPassword(
346
286
  password: string,
347
- savedPasswordHash: string,
348
- secret: string,
349
- ): string | null;
287
+ savedHash: string,
288
+ pepper: string,
289
+ ): Promise<boolean>;
package/index.js CHANGED
@@ -10,8 +10,8 @@ module.exports = {
10
10
  jwtCreateServiceAuthorizationHeader: jwtUtilAuth.createServiceAuthorizationHeader,
11
11
  jwtVerifySignedToken: jwtUtilAuth.verifyJwtSignature,
12
12
  jwtGetHeaderPayload: jwtUtilAuth.getHeaderPayloadFromJwt,
13
- passwordCreateHashWithRandomSalt: pwdUtilAuth.createPasswordHashWithRandomSalt,
14
- passwordCreateHashFromSavedHash: pwdUtilAuth.createPasswordHashBasedOnSavedAlgorithmSalt,
13
+ createPasswordHash: pwdUtilAuth.createPasswordHash,
14
+ verifyPassword: pwdUtilAuth.verifyPassword,
15
15
 
16
16
  // Deprecated
17
17
  /**
@@ -22,14 +22,6 @@ module.exports = {
22
22
  verifyJwtSignature: jwtUtilAuth.verifyJwtSignature,
23
23
  getHeaderPayloadFromJwt: jwtUtilAuth.getHeaderPayloadFromJwt,
24
24
  },
25
- /**
26
- * @deprecated use direct import of the new functions.
27
- */
28
- pwdUtilAuth: {
29
- createPasswordHashWithRandomSalt: pwdUtilAuth.createPasswordHashWithRandomSalt,
30
- createPasswordHashBasedOnSavedAlgorithmSalt:
31
- pwdUtilAuth.createPasswordHashBasedOnSavedAlgorithmSalt,
32
- },
33
25
  /**
34
26
  * @deprecated Use native Buffer methods or other modern alternatives.
35
27
  */
@@ -1,70 +1,103 @@
1
- const crypto = require('crypto');
1
+ 'use strict';
2
2
 
3
- const PASSWORD_HASH_VERSION = '1';
3
+ const { argon2, randomBytes, timingSafeEqual } = require('node:crypto');
4
+ const { promisify } = require('node:util');
4
5
 
5
- // Pattern: Pure Function - creates a digest in which both the pepper and random salt participate.
6
- function createSaltedPasswordDigest(password, secret, algorithm, salt) {
7
- return crypto
8
- .createHmac(algorithm, secret)
9
- .update(Buffer.from(salt, 'base64'))
10
- .update(password)
11
- .digest('base64');
12
- }
6
+ const deriveArgon2Tag = promisify(argon2);
7
+ const ARGON2_ALGORITHM = 'argon2id';
8
+ const ARGON2_VERSION = 19;
9
+ const ARGON2_MEMORY_KIB = 19456;
10
+ const ARGON2_PASSES = 2;
11
+ const ARGON2_PARALLELISM = 1;
12
+ const PASSWORD_SALT_BYTES = 16;
13
+ const PASSWORD_TAG_BYTES = 32;
14
+ const MINIMUM_PEPPER_BYTES = 32;
15
+ const PASSWORD_HASH_PATTERN =
16
+ /^\$argon2id\$v=19\$m=19456,t=2,p=1\$([A-Za-z0-9+/]{22})\$([A-Za-z0-9+/]{43})$/;
13
17
 
14
- // Pattern: Pure Function - recreates the historical digest for existing stored credentials.
15
- function createLegacyPasswordDigest(password, secret, algorithm) {
16
- return crypto.createHmac(algorithm, secret).update(password).digest('base64');
18
+ function createPepperBuffer(pepper) {
19
+ if (typeof pepper !== 'string') {
20
+ throw new TypeError('Password pepper must be a string.');
21
+ }
22
+ const pepperBuffer = Buffer.from(pepper, 'utf8');
23
+ if (pepperBuffer.length < MINIMUM_PEPPER_BYTES) {
24
+ throw new RangeError('Password pepper must be at least 32 bytes.');
25
+ }
26
+ return pepperBuffer;
17
27
  }
18
28
 
19
- // Pattern: Serializer - preserves the published password-hash storage contract.
20
- function serializePasswordHash(algorithmBase64, hashBase64, salt) {
21
- return `$${PASSWORD_HASH_VERSION}$${algorithmBase64}$${hashBase64}$${salt}$`;
29
+ function createPasswordBuffer(password) {
30
+ if (typeof password !== 'string' || !password.isWellFormed()) {
31
+ throw new TypeError('Password must be a well-formed Unicode string.');
32
+ }
33
+ return Buffer.from(password.normalize('NFC'), 'utf8');
22
34
  }
23
35
 
24
- // Pattern: Constant-Time Comparison - avoids credential-dependent string comparison timing.
25
- function passwordHashesMatch(candidateHash, savedPasswordHash) {
26
- const candidateBuffer = Buffer.from(candidateHash);
27
- const savedBuffer = Buffer.from(savedPasswordHash);
28
- return (
29
- candidateBuffer.length === savedBuffer.length &&
30
- crypto.timingSafeEqual(candidateBuffer, savedBuffer)
31
- );
36
+ function encodePhcBase64(value) {
37
+ return value.toString('base64').replace(/=+$/, '');
32
38
  }
33
39
 
34
- // Pattern: Factory - creates a password hash whose random salt changes the credential digest.
35
- const createPasswordHashWithRandomSalt = (password, secret, algorithm) => {
36
- const salt = crypto.randomBytes(32).toString('base64');
37
- const algorithmBase64 = Buffer.from(algorithm).toString('base64');
38
- const hashBase64 = createSaltedPasswordDigest(password, secret, algorithm, salt);
39
- return serializePasswordHash(algorithmBase64, hashBase64, salt);
40
- };
41
-
42
- // Pattern: Compatibility Verification - verifies salted hashes while retaining existing credentials.
43
- const createPasswordHashBasedOnSavedAlgorithmSalt = (password, savedPasswordHash, secret) => {
44
- if (typeof savedPasswordHash !== 'string') {
40
+ function decodePhcBase64(value, expectedLength) {
41
+ const decodedValue = Buffer.from(value, 'base64');
42
+ if (decodedValue.length !== expectedLength || encodePhcBase64(decodedValue) !== value) {
45
43
  return null;
46
44
  }
45
+ return decodedValue;
46
+ }
47
47
 
48
- const splitStringArray = savedPasswordHash.split('$');
49
- if (splitStringArray.length !== 6) {
48
+ function parsePasswordHash(savedHash) {
49
+ if (typeof savedHash !== 'string') {
50
50
  return null;
51
51
  }
52
-
53
- const version = splitStringArray[1];
54
- const algBase64 = splitStringArray[2];
55
- const salt = splitStringArray[4];
56
- if (version !== PASSWORD_HASH_VERSION || !algBase64 || !salt) {
52
+ const match = savedHash.match(PASSWORD_HASH_PATTERN);
53
+ if (!match) {
57
54
  return null;
58
55
  }
59
- const algorithm = Buffer.from(algBase64, 'base64').toString('utf8');
60
- const saltedDigest = createSaltedPasswordDigest(password, secret, algorithm, salt);
61
- const saltedHash = serializePasswordHash(algBase64, saltedDigest, salt);
62
- const legacyDigest = createLegacyPasswordDigest(password, secret, algorithm);
63
- const legacyHash = serializePasswordHash(algBase64, legacyDigest, salt);
64
- return passwordHashesMatch(legacyHash, savedPasswordHash) ? legacyHash : saltedHash;
65
- };
56
+ const salt = decodePhcBase64(match[1], PASSWORD_SALT_BYTES);
57
+ const tag = decodePhcBase64(match[2], PASSWORD_TAG_BYTES);
58
+ return salt && tag ? { salt, tag } : null;
59
+ }
60
+
61
+ function createArgon2Parameters(passwordBuffer, salt, pepperBuffer) {
62
+ return {
63
+ message: passwordBuffer,
64
+ nonce: salt,
65
+ parallelism: ARGON2_PARALLELISM,
66
+ tagLength: PASSWORD_TAG_BYTES,
67
+ memory: ARGON2_MEMORY_KIB,
68
+ passes: ARGON2_PASSES,
69
+ secret: pepperBuffer,
70
+ };
71
+ }
72
+
73
+ async function derivePasswordTag(password, salt, pepperBuffer) {
74
+ const parameters = createArgon2Parameters(createPasswordBuffer(password), salt, pepperBuffer);
75
+ return deriveArgon2Tag(ARGON2_ALGORITHM, parameters);
76
+ }
77
+
78
+ function serializePasswordHash(salt, tag) {
79
+ const parameters = `m=${ARGON2_MEMORY_KIB},t=${ARGON2_PASSES},p=${ARGON2_PARALLELISM}`;
80
+ return `$${ARGON2_ALGORITHM}$v=${ARGON2_VERSION}$${parameters}$${encodePhcBase64(salt)}$${encodePhcBase64(tag)}`;
81
+ }
82
+
83
+ async function createPasswordHash(password, pepper) {
84
+ const pepperBuffer = createPepperBuffer(pepper);
85
+ const salt = randomBytes(PASSWORD_SALT_BYTES);
86
+ const tag = await derivePasswordTag(password, salt, pepperBuffer);
87
+ return serializePasswordHash(salt, tag);
88
+ }
89
+
90
+ async function verifyPassword(password, savedHash, pepper) {
91
+ const pepperBuffer = createPepperBuffer(pepper);
92
+ if (typeof password !== 'string' || !password.isWellFormed()) {
93
+ return false;
94
+ }
95
+ const parsedHash = parsePasswordHash(savedHash);
96
+ if (!parsedHash) {
97
+ return false;
98
+ }
99
+ const candidateTag = await derivePasswordTag(password, parsedHash.salt, pepperBuffer);
100
+ return timingSafeEqual(candidateTag, parsedHash.tag);
101
+ }
66
102
 
67
- module.exports = {
68
- createPasswordHashWithRandomSalt,
69
- createPasswordHashBasedOnSavedAlgorithmSalt,
70
- };
103
+ module.exports = { createPasswordHash, verifyPassword };
@@ -52,25 +52,6 @@ const base64ToAscii = codedString => {
52
52
  return Buffer.from(codedString, 'base64').toString('utf8');
53
53
  };
54
54
 
55
- /**
56
- * Decompose $ connected string and return an object
57
- * return null if error
58
- * @deprecated Use native Buffer methods or other modern alternatives.
59
- * @param passwordHash
60
- */
61
- const dollarSignConnectedStringToAlgorithmHashSalt = passwordHash => {
62
- const splitStringArray = passwordHash.split('$');
63
- if (splitStringArray.length !== 6) {
64
- return null;
65
- }
66
- return {
67
- version: splitStringArray[1],
68
- alg: splitStringArray[2],
69
- hash: splitStringArray[3],
70
- salt: splitStringArray[4],
71
- };
72
- };
73
-
74
55
  /**
75
56
  * Decompose . connected string and return an object with
76
57
  * {header: 'string', payload: 'string', signature: 'string'}
@@ -117,7 +98,6 @@ module.exports = {
117
98
  reverseStringUrlSafe,
118
99
  asciiToBase64,
119
100
  base64ToAscii,
120
- dollarSignConnectedStringToAlgorithmHashSalt,
121
101
  dotConnectedStringToHeaderPayloadSignature,
122
102
  objectToBase64UrlSafeString,
123
103
  urlSafeBase64ToObject,
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@carecard/auth-util",
3
- "version": "3.23.0",
3
+ "version": "3.24.0",
4
4
  "repository": "https://github.com/CareCard-ca/pkg-auth-util.git",
5
5
  "description": "Auth utility functions",
6
6
  "main": "index.js",
7
7
  "types": "index.d.ts",
8
+ "engines": {
9
+ "node": ">=24.20.0 <25"
10
+ },
8
11
  "scripts": {
9
12
  "test": "node scripts/runPackageTask.mjs test",
10
13
  "test:order": "node --test scripts/testOrder/randomizeTestOrder.test.mjs scripts/testParallel/runIndexedMochaTests.test.mjs scripts/packageTaskRunner.test.mjs scripts/canonicalTestCommand.test.mjs",
@@ -47,8 +50,8 @@
47
50
  "typescript": "6.0.3"
48
51
  },
49
52
  "dependencies": {
50
- "@carecard/common-util": "3.23.0",
51
- "@carecard/validate": "3.23.0",
53
+ "@carecard/common-util": "3.24.0",
54
+ "@carecard/validate": "3.24.0",
52
55
  "@types/express": "5.0.6"
53
56
  },
54
57
  "overrides": {
package/readme.md CHANGED
@@ -26,7 +26,7 @@ Non-negotiable code organization rule: Functions with the same or equivalent beh
26
26
  ## Features
27
27
 
28
28
  - **JWT Utilities**: Create, verify, and parse JSON Web Tokens with support for EdDSA (Ed25519) and RSA.
29
- - **Password Utilities**: Secure password hashing using HMAC with random salt and a custom string format for easy storage.
29
+ - **Password Utilities**: Asynchronous Argon2id password hashing through Node.js `crypto`, with a unique random salt and application pepper.
30
30
  - **Key Generation**: Generate Ed25519 and RSA key pairs for JWT signing.
31
31
  - **Crypto Utilities**: Low-level cryptographic primitives for signing, verification, and hashing.
32
32
  - **String Utilities**: Base64 and Base64UrlSafe encoding/decoding, and custom string parsing.
@@ -42,7 +42,11 @@ npm install @carecard/auth-util
42
42
  ### JWT Utilities (`jwtUtilAuth`)
43
43
 
44
44
  ```javascript
45
- const { jwtCreateSignedToken, jwtGetHeaderPayload, jwtVerifySignedToken } = require('@carecard/auth-util');
45
+ const {
46
+ jwtCreateSignedToken,
47
+ jwtGetHeaderPayload,
48
+ jwtVerifySignedToken,
49
+ } = require('@carecard/auth-util');
46
50
 
47
51
  const header = { alg: 'EdDSA', typ: 'JWT' };
48
52
  const payload = { sub: '1234567890', name: 'John Doe' };
@@ -62,7 +66,10 @@ const { header: decodedHeader, payload: decodedPayload } = jwtGetHeaderPayload(t
62
66
  ### Service-To-Service JWT Creation
63
67
 
64
68
  ```javascript
65
- const { jwtCreateServiceAuthorizationHeader, jwtCreateServiceToken } = require('@carecard/auth-util');
69
+ const {
70
+ jwtCreateServiceAuthorizationHeader,
71
+ jwtCreateServiceToken,
72
+ } = require('@carecard/auth-util');
66
73
 
67
74
  const token = jwtCreateServiceToken({
68
75
  issuer: 'ms-institutions',
@@ -77,23 +84,26 @@ const authorization = jwtCreateServiceAuthorizationHeader({
77
84
  });
78
85
  ```
79
86
 
80
- ### Password Utilities (`pwdUtilAuth`)
87
+ ### Password Utilities
81
88
 
82
89
  ```javascript
83
- const { pwdUtilAuth } = require('@carecard/auth-util');
90
+ const { createPasswordHash, verifyPassword } = require('@carecard/auth-util');
84
91
 
85
- const password = 'mySecretPassword';
86
- const secret = 'application-wide-secret';
87
- const algorithm = 'sha512';
92
+ const password = 'correct horse battery staple';
93
+ const pepper = process.env.MS_AUTH_HASH_KEY; // At least 32 UTF-8 bytes.
88
94
 
89
- // Create a new password hash with a random salt
90
- const hash = pwdUtilAuth.createPasswordHashWithRandomSalt(password, secret, algorithm);
91
- // Resulting format: $1$base64(algorithm)$base64(hash)$base64(salt)$
95
+ // Create a strict Argon2id PHC credential with a unique random salt.
96
+ const savedHash = await createPasswordHash(password, pepper);
92
97
 
93
- // Verify a password against a saved hash
94
- const isCorrect = pwdUtilAuth.createPasswordHashBasedOnSavedAlgorithmSalt(password, hash, secret) === hash;
98
+ // Verify through the package boundary. Malformed and legacy credentials return false.
99
+ const isCorrect = await verifyPassword(password, savedHash, pepper);
95
100
  ```
96
101
 
102
+ Both functions preserve password spaces and normalize well-formed Unicode to
103
+ NFC. Password-policy checks belong to `@carecard/validate`; this package hashes
104
+ and compares the supplied value without enforcing password length or
105
+ composition. Keep the pepper outside persisted credentials and logs.
106
+
97
107
  ### Key Generation
98
108
 
99
109
  ```javascript
@@ -150,7 +160,7 @@ npm run test:types
150
160
  The package is organized into several modules:
151
161
 
152
162
  - `jwtUtilAuth`: Manages the JWT lifecycle.
153
- - `pwdUtilAuth`: Handles password hashing and verification.
163
+ - `pwdUtilAuth`: Implements asynchronous Argon2id password hashing and verification behind the direct package exports.
154
164
  - `keyGen`: Utility for generating cryptographic keys.
155
165
  - `cryptoUtilAuth`: Core cryptographic operations using Node.js `crypto` module.
156
166
  - `stringUtilAuth`: String manipulation and format conversions.