bcryptjs
>=2.0.0postconditions22functions9last verified2026-06-25coverage score75%Postconditions: what we check
- hash · hash-type-errorerrorWhenpassword is not a string or Buffer, or salt is invalid typeThrows
Error: Illegal arguments: [actual_type], [actual_type]Required handlingCaller MUST wrap bcrypt.hash() in try-catch block or use .catch() handler. Type errors occur when forgetting await on Promise or passing wrong types. Validate input types before calling hash() to provide better error messages.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[1] - hash · hash-invalid-salterrorWhensalt parameter is not a string, number, or valid salt formatThrows
Error: Invalid salt version or Illegal argumentsRequired handlingCaller MUST handle errors from invalid salt parameter. Use genSalt() to generate valid salts, or pass numeric salt rounds (recommended: 12).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[2] - hash · hash-rounds-out-of-rangeerrorWhensalt rounds < 4 or > 31Throws
Error: Rounds out of range (4-31)Required handlingCaller MUST validate salt rounds are between 4 and 31. Production systems should use rounds >= 12 for adequate security.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - hash · hash-internal-failureerrorWheninternal hashing operation failsThrows
ErrorRequired handlingCaller MUST handle unexpected errors from hash() operation. While rare, internal failures can occur and should not crash the application.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - compare · compare-type-errorerrorWhenpassword or hash parameter is not a stringThrows
Error: Illegal arguments: [actual_type], [actual_type]Required handlingCaller MUST wrap bcrypt.compare() in try-catch or use .catch() handler. Type errors commonly occur when retrieving hash from database as Buffer instead of string, or when password input is undefined/null.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[5] - compare · compare-invalid-hasherrorWhenhash parameter is not a valid bcrypt hash formatThrows
Error: Invalid hash provided or hash is not a valid bcrypt hashRequired handlingCaller MUST handle errors from malformed hash values. Database corruption, string truncation (VARCHAR < 60), or encoding issues can cause invalid hashes. This is the #1 production authentication bug.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[6] - compare · compare-internal-failureerrorWheninternal comparison operation failsThrows
ErrorRequired handlingCaller MUST handle unexpected errors from compare() operation. Errors should not be silently swallowed as they may indicate security issues or data corruption.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[4] - genSalt · gensalt-invalid-roundserrorWhenrounds parameter < 4 or > 31Throws
Error: Rounds out of range (4-31)Required handlingCaller MUST wrap genSalt() in try-catch or use .catch() handler when rounds parameter could be invalid. Validate rounds before calling.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[3] - genSalt · gensalt-type-errorerrorWhenrounds parameter is not a numberThrows
Error: Illegal arguments: [actual_type]Required handlingCaller MUST validate rounds is a number. Common error when reading from environment variables or config files without parsing to integer.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[7] - genSalt · gensalt-rng-failureerrorWhenrandom number generator fails (browser context without setRandomFallback)Throws
Error: Secure random number generator not availableRequired handlingCaller MUST handle RNG failures. In browser environments, call bcrypt.setRandomFallback() to provide a CSPRNG fallback.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[8] - hashSync · hash-sync-type-errorerrorWhenpassword is not a string or salt is not a string/numberThrows
Error: Illegal arguments: [typeof password], [typeof salt]Required handlingCaller MUST wrap hashSync() in a try-catch block. Type errors throw synchronously. Most common cause: passing undefined/null password (unvalidated input) or an already-hashed password Buffer from a database query instead of a string. Unlike the async hash(), there is no .catch() available — only try-catch.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - hashSync · hash-sync-rounds-out-of-rangeerrorWhensalt is a number and rounds < 4 or > 31Throws
Error: Illegal number of rounds (4-31): [N]Required handlingCaller MUST wrap hashSync() in try-catch when rounds parameter comes from configuration or environment variables. Validate rounds is 4-31 before calling. Production minimum recommended: 12.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - hashSync · hash-sync-blocks-event-looperrorWhenhashSync() is called in a Node.js HTTP request handler or any async contextThrows
Does not throw — instead blocks event loop for 100ms-400ms+Required handlingCaller MUST NOT use hashSync() in Express/Fastify/Next.js API routes, background workers serving concurrent requests, or any code running in the main event loop. The operation blocks all other requests for its duration. Use async hash() instead. hashSync is ONLY appropriate in: CLI scripts, database seed files, one-time migration scripts, and test setup (beforeAll/beforeEach fixtures).costhighin proddegraded serviceusers seeservice unavailablevisibilityvisibleSources[10] - compareSync · compare-sync-type-errorerrorWhenpassword or hash is not a stringThrows
Error: Illegal arguments: [typeof password], [typeof hash]Required handlingCaller MUST wrap compareSync() in try-catch. Common cause: retrieving hash from database as Buffer instead of string, or passing undefined when the user record is not found. Unlike the async compare(), no .catch() is available.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - compareSync · compare-sync-silent-false-on-bad-hasherrorWhenhash parameter is a string but not exactly 60 characters (malformed bcrypt hash)Returnsfalse — NOT an error, not a throw. Silent authentication failure.Required handlingCallers who only check `if (isMatch)` without validating that the stored hash is well-formed will silently deny authentication for ALL users whose hashes were truncated, corrupted, or stored in a VARCHAR(40) column. Always validate that the hash column is VARCHAR(60) or CHAR(60) in the database schema and that the hash was not corrupted in transit. This is the #1 silent authentication bug with bcryptjs — the function returns false for wrong-password AND bad-hash, making it impossible to distinguish them without additional checks.costhighin prodsilent failureusers seeauthentication failurevisibilitysilent
- compareSync · compare-sync-blocks-event-looperrorWhencompareSync() is called in a Node.js HTTP request handler or async contextThrows
Does not throw — instead blocks event loop for 100ms-400ms+Required handlingCaller MUST NOT use compareSync() in Express/Fastify/Next.js API routes or any code running in the main event loop. Use async compare() for all authentication endpoints. compareSync is ONLY appropriate in: CLI scripts, test setup, and one-off verification scripts.costhighin proddegraded serviceusers seeservice unavailablevisibilityvisibleSources[10] - genSaltSync · gensalt-sync-invalid-roundserrorWhenrounds is not a number, or rounds < 4 or rounds > 31Throws
Error: Illegal arguments: [typeof rounds] (wrong type) or Error: Illegal number of rounds (4-31): [N] (out of range)Required handlingCaller MUST wrap genSaltSync() in try-catch when rounds comes from configuration or user input. Validate rounds is an integer between 4 and 31. Common error: reading BCRYPT_ROUNDS from environment variable as string without parseInt() — e.g., process.env.BCRYPT_ROUNDS returns '12' (string) not 12 (number).costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - genSaltSync · gensalt-sync-rng-failureerrorWhencalled in a browser environment without setting a random fallback via setRandomFallback()Throws
Error: Secure random number generator not availableRequired handlingIn browser environments, bcryptjs requires calling bcrypt.setRandomFallback() with a CSPRNG implementation before using any salt-generating functions. Node.js environments use crypto.randomBytes() automatically and are unaffected.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - getRounds · get-rounds-type-errorerrorWhenhash parameter is not a stringThrows
Error: Illegal arguments: [typeof hash]Required handlingCaller MUST wrap getRounds() in try-catch when the hash comes from a database column or external source where it could be null, undefined, a Buffer, or any non-string type. Common cause: the user record was not found (hash is undefined) and the calling code did not guard, so a synchronous throw bubbles up into a login or password-rotation handler that does not expect it. Validate that the hash is a string of length 60 before calling getRounds().costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - getSalt · get-salt-type-errorerrorWhenhash parameter is not a stringThrows
Error: Illegal arguments: [typeof hash]Required handlingCaller MUST wrap getSalt() in try-catch when the hash comes from a database row or external source where it may be null, undefined, or a non-string type (Buffer, Object). The function throws synchronously — there is no Promise and no .catch() path. Validate that the hash is a 60-character string before calling.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - getSalt · get-salt-illegal-lengtherrorWhenhash is a string but its length is not exactly 60 charactersThrows
Error: Illegal hash length: [length] != 60Required handlingCaller MUST handle the length-validation throw. Common cause: the hash was stored in a VARCHAR column narrower than 60 (e.g. VARCHAR(40)), or it was trimmed/encoded on insert, or it was migrated from a different bcrypt variant (some libraries produce $2y$ hashes that may have different lengths). Unlike compareSync() which silently returns false for malformed hashes, getSalt() throws explicitly — so an uncaught throw will crash the handler. Validate hash.length === 60 before calling, or wrap the call.costmediumin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9] - truncates · truncates-type-errorerrorWhenpassword parameter is not a stringThrows
Error: Illegal arguments: [typeof password]Required handlingCaller MUST wrap truncates() in try-catch or validate the input is a string first. Common cause: passing undefined when the user-submitted form field is missing, or passing a Buffer from a binary upload path. The function is synchronous — there is no Promise rejection or .catch() handler available.costlowin prodimmediate exceptionusers seeservice unavailablevisibilityvisibleSources[9]
Sources
Every postcondition cites at least one of these. Grouped by source type; numbered to match the footnotes above.
- [4]npmjs.com/package/bcryptjsBcryptjs
- [8]github.com/dcodeIO/bcrypt.jsdcodeIO/bcrypt.js
- [9]github.com/dcodeIO/bcrypt.js/blobdcodeIO/bcrypt.js · index.js
- [10]raw.githubusercontent.com/dcodeIO/bcrypt.js/masterdcodeIO/bcrypt.js · README.md
- [1]github.com/dcodeIO/bcrypt.js/issuesdcodeIO/bcrypt.js issue #146
- [2]github.com/dcodeIO/bcrypt.js/issuesdcodeIO/bcrypt.js issue #20
- [3]github.com/kelektiv/node.bcrypt.js/issueskelektiv/node.bcrypt.js issue #898
- [5]github.com/dcodeIO/bcrypt.js/issuesdcodeIO/bcrypt.js issue #76
- [6]github.com/kelektiv/node.bcrypt.js/issueskelektiv/node.bcrypt.js issue #1037
- [7]github.com/dcodeIO/bcrypt.js/issuesdcodeIO/bcrypt.js issue #58
Research notes
Curator notes from SOURCES.md captured when the profile was written so you can verify the reasoning, not just the rules.
Sources: bcryptjs
Official Documentation
- npm: https://www.npmjs.com/package/bcryptjs
- GitHub: https://github.com/dcodeIO/bcrypt.js
- README: https://github.com/dcodeIO/bcrypt.js/blob/main/README.md
- TypeScript Definitions: https://github.com/dcodeIO/bcrypt.js/blob/main/types.d.ts
Behavioral Evidence
Error Handling Pattern
The bcryptjs library uses an error-first callback pattern: Callback<T>: (err: Error | null, result?: T) => void, which is called with an error on failure or a value of type T upon success. Synchronous methods throw errors directly, while asynchronous methods pass errors to callbacks or reject promises.
Source: https://github.com/dcodeIO/bcrypt.js/blob/main/README.md
Input Constraints and Validation
Maximum Password Length
bcryptjs has a 72-byte maximum input length for passwords. Note that UTF-8 encoded characters can use up to 4 bytes each, so a 72-character string may exceed this limit depending on the characters used. Inputs exceeding this length are silently truncated, not rejected.
Source: https://github.com/dcodeIO/bcrypt.js README
Hash Format
All generated hashes are 60 characters in length and follow the format: $2a$[rounds]$[salt][hash]
Source: https://github.com/dcodeIO/bcrypt.js README
Function-Specific Error Conditions
hash() and hashSync()
Signatures:
hash(s: string, salt: string | number, callback?: (err: Error, hash: string) => void, progressCallback?: (percent: number) => void): Promise<string>hashSync(s: string, salt: string | number): string
Error Conditions:
-
Missing Salt Parameter
- Error:
Error: Illegal arguments: string, undefined - Occurs when salt is not provided
- Source: https://github.com/dcodeIO/bcrypt.js/issues/146
- Error:
-
Invalid Parameter Types
- Error:
Error: Illegal arguments: string, object - Occurs when passing non-string/non-number types (e.g., Buffer, Promise, undefined)
- Common mistake: Not awaiting
hash()before storing, causing Promise object to be passed to subsequent operations - Source: https://github.com/dcodeIO/bcrypt.js/issues/146
- Error:
-
Invalid Salt Format
- Error:
Error: Invalid salt version: 2orError: Invalid salt version: nu - Occurs when pre-generated salt string has incompatible version format
- Can happen with hashes generated by different bcrypt implementations
- Source: https://github.com/dcodeIO/bcrypt.js/issues/20
- Error:
-
Input Length Constraint
- Behavior: Passwords exceeding 72 bytes are silently truncated (not an error)
- Risk: "password123" and "password123[...very long suffix...]" may hash to the same value
- Source: https://github.com/dcodeIO/bcrypt.js README
compare() and compareSync()
Signatures:
compare(s: string, hash: string, callback?: (err: Error, success: boolean) => void, progressCallback?: (percent: number) => void): Promise<boolean>compareSync(s: string, hash: string): boolean
Error Conditions:
-
Invalid Hash Parameter Type
- Error:
Error: Illegal arguments: string, undefinedorError: Illegal arguments: string, object - Occurs when hash is not a string (e.g., Buffer, undefined, null)
- Common with database retrievals that return Buffer objects
- Source: https://github.com/dcodeIO/bcrypt.js/issues/65
- Error:
-
Invalid Password Parameter Type
- Error:
Error: Illegal arguments - Occurs when password is not a string
- Source: https://github.com/dcodeIO/bcrypt.js/issues/146
- Error:
-
Hash Version Mismatch
- Error:
Error: Invalid salt version - Occurs when hash uses unsupported or incompatible version identifier (e.g., $2$, $2x$)
- bcryptjs primarily supports $2a$ format; $2b$ support added later
- Source: https://github.com/dcodeIO/bcrypt.js/issues/81
- Error:
-
Empty Password Edge Case
- Behavior: Returns
false(not an error) - Empty string passwords are technically valid but comparison returns false
- Source: https://app.studyraid.com/en/read/12358/398968/common-error-types-in-bcryptjs-operations
- Behavior: Returns
-
False Negatives from Database Storage
- Symptom:
compare()always returns false despite correct password - Common causes:
- Hash stored as Buffer instead of string
- Hash truncated in database (VARCHAR too short)
- Hash modified during storage/retrieval (encoding issues)
- Source: https://github.com/dcodeIO/bcrypt.js/issues/76
- Symptom:
genSalt() and genSaltSync()
Signatures:
genSalt(rounds?: number, callback?: (err: Error, salt: string) => void): Promise<string>genSaltSync(rounds?: number): string
Error Conditions:
-
Invalid Rounds Type
- Error:
Error: rounds must be a number - Occurs when rounds is a string (e.g.,
'10') instead of a number - Common with environment variables that are strings by default
- Source: https://github.com/kelektiv/node.bcrypt.js/issues/903
- Error:
-
Rounds Below Minimum (< 4)
- Error:
Error: Illegal arguments - bcrypt specification requires minimum of 4 rounds
- Source: https://github.com/kelektiv/node.bcrypt.js/issues/898
- Error:
-
Rounds Above Maximum (> 31)
- Error:
Error: Invalid salt. Salt must be in the form of: $Vers$log2(NumRounds)$saltvalue - Cryptic error message; actual issue is rounds exceeding bcrypt's log2(rounds) specification limit
- bcrypt uses 5-bit field for rounds, max value is 31
- Source: https://github.com/kelektiv/node.bcrypt.js/issues/437
- Error:
-
Missing Callback (Legacy)
- Error:
Error: No callback supplied - Occurred in older versions before promise support was added
- Modern versions return a promise when no callback is provided
- Source: https://github.com/dcodeIO/bcrypt.js/issues/58
- Error:
Default Behavior:
- If rounds is omitted, default is 10
- Recommended range: 10-12 for most applications (as of 2026)
getRounds()
Signature:
getRounds(hash: string): number
Error Conditions:
-
Non-String Parameter
- Error: Throws
Error(specific message varies) - Type validation enforced; must be a string
- Source: https://snyk.io/advisor/npm-package/bcryptjs/functions/bcryptjs.getSalt
- Error: Throws
-
Malformed Hash
- Behavior: May return incorrect value or throw
- Implementation-dependent; no guaranteed error for invalid format
- Recommended to validate hash format before calling
getSalt()
Signature:
getSalt(hash: string): string
Error Conditions:
IMPORTANT: getSalt() does NOT validate the hash parameter. It performs string manipulation without error checking.
- No Validation
- Behavior: Returns substring of input without validation
- Will not throw error even for completely invalid inputs
- May return garbage data for malformed hashes
- Source: https://snyk.io/advisor/npm-package/bcryptjs/functions/bcryptjs.getSalt
Security Implication: Always validate hash format before calling getSalt() to avoid processing untrusted data.
Common Error Patterns and Debugging
"Illegal arguments" Error Family
The most common error in bcryptjs is Error: Illegal arguments: [type1], [type2], which indicates type mismatch between expected and actual parameters.
Common Causes:
- Forgetting to await async operations
- Passing Buffer objects from databases without
.toString() - Passing environment variables (strings) as rounds parameter
- Missing required parameters
Debugging Approach:
// Check actual types before calling bcryptjs
console.log(typeof password, typeof hash, typeof rounds);
Sources:
- https://github.com/dcodeIO/bcrypt.js/issues/146
- https://javascript.tutorialink.com/why-i-got-illegal-arguments-error-with-simple-hash-function/
Hash Version Compatibility
bcryptjs primarily supports the $2a$ hash format. Support for $2b$ (which fixes a bug in the original specification) was added later. Mixing hash versions between generation and comparison can cause issues.
Recommendation: Use consistent bcryptjs versions across all systems that generate/verify hashes.
Source: https://github.com/dcodeIO/bcrypt.js/issues/81
Asynchronous Behavior
bcryptjs async functions split operations into small chunks. After each chunk completes, the next chunk is placed on the back of the JavaScript event queue, allowing other operations to execute. This prevents blocking the event loop during expensive hashing operations.
Progress Callbacks: Optional progressCallback parameter receives a number between 0.0 and 1.0 indicating completion percentage. Called at most once per 100ms (MAX_EXECUTION_TIME).
Source: https://github.com/dcodeIO/bcrypt.js README
Related Issues and Discussions
Key GitHub Issues
-
Issue #146: Error: Illegal arguments: string, undefined
- https://github.com/dcodeIO/bcrypt.js/issues/146
- Comprehensive discussion of parameter validation errors
-
Issue #20: ERROR: Invalid salt version: 2
- https://github.com/dcodeIO/bcrypt.js/issues/20
- Hash version compatibility issues
-
Issue #81: Add support "2b" hash
- https://github.com/dcodeIO/bcrypt.js/issues/81
- Evolution of hash format support
-
Issue #58: genSalt (async) with no arguments throws callback error instead of returning promise
- https://github.com/dcodeIO/bcrypt.js/issues/58
- Transition from callback-only to promise-based API
-
Issue #65: compareSync throwing error
- https://github.com/dcodeIO/bcrypt.js/issues/65
- Type validation strictness in sync methods
-
Issue #898: Document the upper and lower bounds for the rounds parameter in genSaltSync
- https://github.com/kelektiv/node.bcrypt.js/issues/898
- Valid rounds range (4-31)
-
Issue #437: Wrong error message being thrown when salt rounds value is too high
- https://github.com/kelektiv/node.bcrypt.js/issues/437
- Misleading error messages for out-of-range rounds
-
Issue #76: bcrypt.compare always returns false
- https://github.com/dcodeIO/bcrypt.js/issues/76
- Common pitfalls with database storage and retrieval
Best Practices Summary
Input Validation
- Always validate that passwords and hashes are strings before calling bcryptjs
- Check password length doesn't exceed 72 bytes (especially with UTF-8)
- Validate rounds is a number between 4 and 31
- Convert Buffer objects to strings:
buffer.toString('utf8')
Error Handling
- Wrap synchronous calls in try-catch blocks
- Use error-first callback pattern or promise rejection handling for async calls
- Check for
nullerror parameter before proceeding with results - Log errors with context but avoid exposing sensitive details to users
Database Storage
- Store hashes as VARCHAR(60) or TEXT
- Always store as strings, not Buffers
- Verify encoding is consistent (UTF-8 recommended)
- Test hash retrieval in development to catch storage issues early
Async/Await Pattern
try {
const hash = await bcrypt.hash(password, rounds);
// Use hash immediately after awaiting
} catch (error) {
// Handle error
}
DO NOT:
const hash = bcrypt.hash(password, rounds); // Missing await
// hash is now a Promise, not a string!
Security Considerations
CVE Status
No known CVEs for bcryptjs. As of 2026-02-26, the bcryptjs package has no security vulnerabilities in the NVD (National Vulnerability Database), Snyk, or GitHub Security Advisory databases.
Important Distinction: CVE-2020-7689 affects the bcrypt package (kelektiv/node.bcrypt.js with native C++ bindings), NOT the bcryptjs package (dcodeIO/bcrypt.js pure JavaScript implementation).
Sources:
- Snyk Database: https://security.snyk.io/package/npm/bcryptjs
- NVD CVE-2020-7689: https://nvd.nist.gov/vuln/detail/CVE-2020-7689 (bcrypt only)
- GitHub Advisory GHSA-5wg4-74h6-q47v: https://github.com/advisories/GHSA-5wg4-74h6-q47v (bcrypt only)
CVE-2020-7689 (bcrypt Native Package)
For context, the native bcrypt package (not bcryptjs) was affected by CVE-2020-7689, a data truncation vulnerability:
- Severity: 7.5 HIGH (NIST), 5.9 MEDIUM (Snyk)
- Affected: bcrypt versions <5.0.0
- Issue: Data exceeding 255 bytes was incorrectly truncated
- Fixed: bcrypt v5.0.0
- bcryptjs Status: NOT AFFECTED (pure JavaScript implementation)
Recommended Versions
- Minimum: 2.4.3
- Recommended: 3.0.3 (latest as of 2026-02-26)
- Reason: Active maintenance, TypeScript support, zero known vulnerabilities
Source: https://security.snyk.io/package/npm/bcryptjs
Security Best Practices
1. Use Appropriate Rounds Value (2026)
For modern hardware, use rounds between 10-14:
- 10 rounds: Fast, suitable for high-traffic applications
- 12 rounds: Balanced security/performance (recommended)
- 14 rounds: Higher security, slower hashing
Valid Range: 4-31 (specification limit)
Source: https://auth0.com/blog/hashing-in-action-understanding-bcrypt/
2. Validate Password Length
Passwords exceeding 72 bytes are silently truncated. This is a bcrypt algorithm limitation, not a bug.
Risk: Long passwords may hash to the same value:
// These may produce the same hash if both exceed 72 bytes:
"password" + "A".repeat(100)
"password" + "B".repeat(100)
Mitigation:
- Validate password length at application layer
- Consider pre-hashing with SHA-256 for very long passwords
- Document the 72-byte limit to users
Source: https://github.com/dcodeIO/bcrypt.js README
3. Timing Attack Protection
bcryptjs implements constant-time comparison in the compare() function to prevent timing attacks. The function compares strings character-by-character without early bailout.
Important: Never use standard JavaScript string comparison (===) for password verification. Always use bcrypt.compare() or bcrypt.compareSync().
Sources:
- Snyk Timing Attack Advisory: https://security.snyk.io/vuln/SNYK-JS-BCRYPT-174521
- bcrypt.js GitHub Issues: https://github.com/kelektiv/node.bcrypt.js/issues/720
4. Database Storage Security
Column Size: Use VARCHAR(60) or larger to prevent hash truncation.
Type Safety:
- Store hashes as strings, not Buffers
- Convert Buffer objects before comparison:
buffer.toString('utf8') - Use UTF-8 encoding consistently
Validation:
- Test hash retrieval in development
- Verify no truncation occurs
- Check encoding matches storage format
Source: https://github.com/dcodeIO/bcrypt.js/issues/76
5. Hash Version Compatibility
bcrypt has multiple version identifiers:
- $2$ - Original (deprecated)
- $2a$ - Most common (has wraparound bug for passwords >254 chars)
- $2b$ - Fixed wraparound bug (recommended)
- $2x$, $2y$ - Variant implementations (deprecated)
bcryptjs Support: Supports $2a$ and $2b$ formats
Recommendation: Use consistent library versions across systems. For passwords under 256 characters, $2a$ and $2b$ are functionally identical.
Sources:
- Passlib Documentation: https://passlib.readthedocs.io/en/stable/lib/passlib.hash.bcrypt.html
- bcrypt.js Issue #81: https://github.com/dcodeIO/bcrypt.js/issues/81
6. Browser Environment Security
WARNING: In browser environments without crypto.getRandomValues(), bcryptjs falls back to Math.random(), which is NOT cryptographically secure.
Mitigation:
// Set a cryptographically secure PRNG for browsers
bcrypt.setRandomFallback((len) => {
const buf = new Uint8Array(len);
window.crypto.getRandomValues(buf);
return Array.from(buf);
});
Node.js: Automatically uses crypto.randomBytes() (secure)
Source: https://github.com/dcodeIO/bcrypt.js/issues/110
7. Environment Variable Handling
Environment variables are always strings. Convert to numbers:
// ❌ WRONG
const rounds = process.env.SALT_ROUNDS; // "10" (string)
await bcrypt.genSalt(rounds); // Error: rounds must be a number
// ✅ CORRECT
const rounds = parseInt(process.env.SALT_ROUNDS, 10); // 10 (number)
await bcrypt.genSalt(rounds);
Source: https://github.com/kelektiv/node.bcrypt.js/issues/903
8. getSalt() Input Validation
WARNING: getSalt() does NOT validate input. It returns substrings without error checking.
Risk: May return garbage data for invalid hashes.
Mitigation: Use getRounds() instead, which validates input. Or validate hash format before calling getSalt().
Source: https://snyk.io/advisor/npm-package/bcryptjs/functions/bcryptjs.getSalt
Security Comparison: bcryptjs vs Native bcrypt
| Aspect | bcrypt (Native) | bcryptjs (Pure JS) |
|---|---|---|
| CVE-2020-7689 | Affected (<5.0.0) | Not affected |
| Dependencies | node-gyp, native bindings | Zero dependencies |
| Performance | ~30% faster | Slower (security feature) |
| Platform | Node.js only, requires compilation | Node.js + browsers |
| Vulnerabilities | Platform-specific risks | No native code vulnerabilities |
| Security Posture | Requires regular updates | Stable, well-vetted |
Recommendation: bcryptjs is particularly well-suited for:
- Cross-platform applications (Node.js + browser)
- Environments where native compilation is problematic
- Projects prioritizing zero-dependency security
Source: https://codeforgeek.com/bcrypt-vs-bcryptjs/
Alternative Consideration: Argon2id (2026)
For new projects in 2026, consider Argon2id, which provides:
- Memory-hardness (resistant to GPU/ASIC attacks)
- Configurable memory and parallelism parameters
- Winner of the Password Hashing Competition (2015)
bcrypt Status: Remains secure and widely vetted. No urgent need to migrate existing systems.
Migration Strategy: Opportunistic rehashing (rehash on successful login)
Security Audit Summary
Package: bcryptjs v3.0.3 Audit Date: 2026-02-26 Status: ✅ SECURE
Findings:
- ✅ No known CVEs
- ✅ Active maintenance (last update <3 months)
- ✅ Zero dependencies (minimal attack surface)
- ✅ Constant-time comparison (timing attack protection)
- ✅ Platform-independent (no native code vulnerabilities)
Caveats:
- ⚠️ Silent 72-byte truncation (application-layer validation required)
- ⚠️ getSalt() lacks input validation
- ⚠️ Browser environments need secure random fallback
- ⚠️ Slower than native bcrypt (~30% performance penalty)
Recommendation: Safe for production use with proper error handling and input validation.
Contract Justification
This contract requires error handling because:
- Type validation failures are common and throw/reject errors
- Invalid hash formats cause runtime errors
- Out-of-range parameters (rounds, salt) trigger errors
- Async operations can fail and reject promises
- Database type mismatches (Buffer vs string) are frequent in production
The library is designed to fail fast on invalid inputs rather than silently producing incorrect results, making error handling essential for reliable applications.