Helper functions for working with bcrypt hashes, password validation, algorithm configuration, and base64 encoding operations using bcrypt's custom alphabet.
Extracts the number of rounds used to encrypt a bcrypt hash.
/**
* Gets the number of rounds used to encrypt the specified hash.
* @param hash Hash to extract the used number of rounds from
* @returns Number of rounds used
*/
function getRounds(hash: string): number;Usage Examples:
import { getRounds } from "bcryptjs";
const hash = "$2b$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
const rounds = getRounds(hash);
console.log(rounds); // 12
// Check if hash meets minimum security requirements
function validateHashSecurity(hash, minRounds = 10) {
const rounds = getRounds(hash);
if (rounds < minRounds) {
console.warn(`Hash uses only ${rounds} rounds, minimum is ${minRounds}`);
return false;
}
return true;
}
// Audit existing password hashes
function auditPasswordHashes(hashes) {
const results = hashes.map(hash => ({
hash,
rounds: getRounds(hash),
secure: getRounds(hash) >= 10
}));
const insecure = results.filter(r => !r.secure);
if (insecure.length > 0) {
console.log(`Found ${insecure.length} hashes with insufficient rounds`);
}
return results;
}Extracts the salt portion from a bcrypt hash without validation.
/**
* Gets the salt portion from a hash. Does not validate the hash.
* @param hash Hash to extract the salt from
* @returns Extracted salt part
*/
function getSalt(hash: string): string;Usage Examples:
import { getSalt } from "bcryptjs";
const hash = "$2b$12$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy";
const salt = getSalt(hash);
console.log(salt); // "$2b$12$N9qo8uLOickgx2ZMRZoMye"
// Reuse salt for related operations (generally not recommended)
function extractSaltForDebugging(hash) {
const salt = getSalt(hash);
const rounds = getRounds(hash);
return {
salt,
rounds,
algorithm: salt.startsWith('$2b$') ? '2b' : 'other'
};
}
// Compare salts between hashes
function compareSalts(hash1, hash2) {
const salt1 = getSalt(hash1);
const salt2 = getSalt(hash2);
return {
same: salt1 === salt2,
salt1,
salt2
};
}Tests if a password will be truncated when hashed due to UTF-8 encoding exceeding 72 bytes.
/**
* Tests if a password will be truncated when hashed, that is its length is
* greater than 72 bytes when converted to UTF-8.
* @param password The password to test
* @returns true if truncated, otherwise false
*/
function truncates(password: string): boolean;Usage Examples:
import { truncates } from "bcryptjs";
// Test various passwords
console.log(truncates("shortpass")); // false
console.log(truncates("a".repeat(72))); // false (exactly 72 bytes)
console.log(truncates("a".repeat(73))); // true (exceeds 72 bytes)
// Unicode characters use multiple bytes
const unicodePassword = "🔒".repeat(20); // Each emoji is 4 bytes
console.log(truncates(unicodePassword)); // true (80 bytes total)
// Validation function for user registration
function validatePassword(password) {
const errors = [];
if (password.length < 8) {
errors.push("Password must be at least 8 characters long");
}
if (truncates(password)) {
errors.push("Password is too long and will be truncated (max 72 UTF-8 bytes)");
}
return {
valid: errors.length === 0,
errors
};
}
// Form validation with user feedback
function checkPasswordLength(password) {
if (truncates(password)) {
return {
warning: true,
message: "Password exceeds 72 bytes and will be truncated. Consider using a shorter password."
};
}
return { warning: false };
}
// Express.js middleware for password validation
const validatePasswordMiddleware = (req, res, next) => {
const { password } = req.body;
if (truncates(password)) {
return res.status(400).json({
error: "Password too long",
message: "Password exceeds 72 UTF-8 bytes and will be truncated"
});
}
next();
};Sets a fallback pseudo-random number generator when neither Web Crypto API nor Node.js crypto are available.
/**
* Sets the pseudo random number generator to use as a fallback if neither
* Web Crypto API nor Node.js crypto are available.
* @param random Function taking the number of bytes to generate, returning
* cryptographically secure random byte values
*/
function setRandomFallback(random: RandomFallback): void;Usage Examples:
import { setRandomFallback } from "bcryptjs";
// Set a secure random fallback using a crypto library
import { randomBytes } from 'some-crypto-library';
setRandomFallback((length) => {
const bytes = randomBytes(length);
return Array.from(bytes); // Convert to array of numbers
});
// Browser environment with Web Crypto API polyfill
if (typeof crypto === 'undefined') {
// Import a crypto polyfill
import('crypto-polyfill').then(cryptoPolyfill => {
setRandomFallback((length) => {
const array = new Uint8Array(length);
cryptoPolyfill.getRandomValues(array);
return Array.from(array);
});
});
}
// Custom secure random implementation (example only - use proven libraries)
function customSecureRandom(length) {
// This is just an example - in practice, use established crypto libraries
const bytes = [];
for (let i = 0; i < length; i++) {
bytes.push(Math.floor(Math.random() * 256)); // NOT SECURE - example only
}
return bytes;
}
// DON'T DO THIS - insecure example
setRandomFallback(customSecureRandom);
// Proper implementation with a secure library
import { webcrypto } from 'node:crypto';
setRandomFallback((length) => {
const array = new Uint8Array(length);
webcrypto.getRandomValues(array);
return Array.from(array);
});Encodes a byte array to base64 using bcrypt's custom alphabet.
/**
* Encodes a byte array to base64 with up to len bytes of input,
* using the custom bcrypt alphabet.
* @param b Byte array to encode
* @param len Maximum input length
* @returns Base64 encoded string using bcrypt alphabet
*/
function encodeBase64(b: Readonly<ArrayLike<number>>, len: number): string;Usage Examples:
import { encodeBase64 } from "bcryptjs";
// Encode byte array
const bytes = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
const encoded = encodeBase64(bytes, bytes.length);
console.log(encoded); // "..CA.uOD/ea"
// Encode with length limit
const longBytes = [0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09];
const partialEncoded = encodeBase64(longBytes, 5); // Only encode first 5 bytes
console.log(partialEncoded); // "..CA.u"
// Working with Uint8Array
const uint8Array = new Uint8Array([65, 66, 67, 68]); // "ABCD"
const encodedUint8 = encodeBase64(uint8Array, 4);
console.log(encodedUint8);
// Custom encoding function
function encodeBytesToBcryptBase64(bytes) {
if (bytes.length === 0) return '';
return encodeBase64(bytes, bytes.length);
}Decodes a base64 string using bcrypt's custom alphabet.
/**
* Decodes a base64 encoded string to up to len bytes of output,
* using the custom bcrypt alphabet.
* @param s String to decode
* @param len Maximum output length
* @returns Array of decoded bytes
*/
function decodeBase64(s: string, len: number): number[];Usage Examples:
import { decodeBase64 } from "bcryptjs";
// Decode base64 string
const encoded = "..CA.uOD/ea";
const decoded = decodeBase64(encoded, 8);
console.log(decoded); // [0, 1, 2, 3, 4, 5, 6, 7]
// Decode with length limit
const partialDecoded = decodeBase64(encoded, 4); // Only decode to 4 bytes
console.log(partialDecoded); // [0, 1, 2, 3]
// Round-trip encoding/decoding test
function testRoundTrip(originalBytes) {
const encoded = encodeBase64(originalBytes, originalBytes.length);
const decoded = decodeBase64(encoded, originalBytes.length);
const matches = originalBytes.every((byte, index) => byte === decoded[index]);
console.log(`Round trip successful: ${matches}`);
return { encoded, decoded, matches };
}
// Convert decoded bytes back to Uint8Array
function decodeToUint8Array(encodedString, length) {
const decoded = decodeBase64(encodedString, length);
return new Uint8Array(decoded);
}
// Utility for working with bcrypt's base64 format
class BcryptBase64 {
static encode(bytes, length = bytes.length) {
return encodeBase64(bytes, length);
}
static decode(str, length) {
return decodeBase64(str, length);
}
static roundTrip(bytes) {
const encoded = this.encode(bytes);
const decoded = this.decode(encoded, bytes.length);
return { encoded, decoded };
}
}These utility functions work with the following types:
/**
* Generic callback type for error-first callbacks
*/
type Callback<T> = (err: Error | null, result?: T) => void;
/**
* Progress callback for long-running operations
*/
type ProgressCallback = (percentage: number) => void;
/**
* Random number generator fallback function
*/
type RandomFallback = (length: number) => number[];// CRITICAL: Random fallback must be cryptographically secure
// Good examples:
import { randomBytes } from 'node:crypto';
setRandomFallback((len) => Array.from(randomBytes(len)));
// Bad examples (DO NOT USE):
setRandomFallback((len) =>
Array.from({length: len}, () => Math.floor(Math.random() * 256))
); // Math.random() is NOT cryptographically secure
setRandomFallback((len) =>
Array.from({length: len}, () => Date.now() % 256)
); // Predictable and NOT secure// Be aware of truncation implications
const longPassword = "very-long-password-that-might-be-truncated-by-bcrypt";
if (truncates(longPassword)) {
console.warn("Password will be truncated - consider warning the user");
// The effective password becomes only the first 72 UTF-8 bytes
}
// Good practice: Check before hashing
function safeHashPassword(password, rounds = 10) {
if (truncates(password)) {
throw new Error("Password too long and will be truncated");
}
return hashSync(password, rounds);
}// Hash analysis utility
function analyzeHash(hash) {
return {
rounds: getRounds(hash),
salt: getSalt(hash),
format: hash.startsWith('$2b$') ? 'bcrypt 2b' : 'other',
length: hash.length
};
}
// Password strength validation
function validatePasswordStrength(password) {
const issues = [];
if (password.length < 8) {
issues.push('Too short (minimum 8 characters)');
}
if (truncates(password)) {
issues.push('Too long (will be truncated at 72 UTF-8 bytes)');
}
if (!/[A-Z]/.test(password)) {
issues.push('Missing uppercase letter');
}
if (!/[a-z]/.test(password)) {
issues.push('Missing lowercase letter');
}
if (!/\d/.test(password)) {
issues.push('Missing number');
}
return {
valid: issues.length === 0,
issues
};
}
// Development utilities
const bcryptUtils = {
analyzeHash,
validatePasswordStrength,
testTruncation: truncates,
extractRounds: getRounds,
extractSalt: getSalt
};