Поведение bcrypt.js при обработке строк пароля напрямую зависит от того, как JavaScript представляет строки и как библиотека преобразует их в байты перед хешированием. На практике большинство проблем возникает не в самом алгоритме, а в некорректно учтённых граничных значениях входных данных.
Пустая строка является допустимым входом с точки зрения JavaScript, но с точки зрения криптографического хеширования это отдельный крайний случай.
import bcrypt from "bcryptjs";
const hash = bcrypt.hashSync("", 10);
const result = bcrypt.compareSync("", hash);
Поведение стабильно: пустая строка всегда хешируется в детерминированное значение при фиксированном salt. Однако с точки зрения безопасности это недопустимый пароль, так как энтропия равна нулю.
Особенность заключается в том, что:
Пробелы в начале, конце и внутри строки не интерпретируются библиотекой как специальные символы.
const password1 = "password";
const password2 = " password ";
Для bcrypt это разные строки, и их хеши полностью различаются.
Типичная ошибка возникает на уровне UI или API:
Это приводит к несоответствию хешей при одинаковом визуальном пароле.
bcrypt.js корректно работает с любыми ASCII-символами:
const password = "!@#$%^&*()_+-=[]{}|;':\",.<>/?";
const hash = bcrypt.hashSync(password, 10);
Особенности:
Ошибки чаще всего появляются не в bcrypt, а в слоях сериализации:
JavaScript позволяет использовать управляющие символы внутри строки:
const password = "pass\0word";
bcrypt.js не трактует \0 как терминатор строки, как это
происходит в некоторых языках C.
Поведение:
Проблема возникает при интеграции с системами, где null-байт считается ограничителем строки (например, некоторые C-бэкенды или старые API).
bcrypt.js работает с UTF-8 представлением строки. Это означает, что:
const password = "пароль?密码";
const hash = bcrypt.hashSync(password, 10);
Особенности:
bcrypt использует ограничение на входную строку: учитываются только первые 72 байта.
Это один из самых важных граничных случаев:
const password = "a".repeat(1000);
Поведение:
Это приводит к уязвимости:
"password123" и
"password123AAAAAAAAAAAA..." (длинная версия) могут быть
эквивалентныПри тестировании важно учитывать:
Unicode строки могут иметь разные представления одного и того же символа:
const a = "é"; // NFC
const b = "e\u0301"; // NFD
bcrypt.js не выполняет нормализацию автоматически.
Результат:
Это критично при:
При передаче паролей через API часто возникает проблема преобразования строк:
fetch("/api/login", {
method: "POST",
body: JSON.stringify({ password: "pa\"ss\\word" })
});
Ошибки возникают не в bcrypt.js, а в промежуточных слоях:
Важно учитывать, что bcrypt получает уже восстановленную строку, и любые изменения до этого этапа влияют на итоговый хеш.
Проверка поведения bcrypt.js обычно включает набор типовых сценариев:
expect(bcrypt.compareSync("", bcrypt.hashSync("", 10))).toBe(true);
expect(bcrypt.compareSync("a", bcrypt.hashSync("a", 10))).toBe(true);
const password = "测试?пароль";
const hash = bcrypt.hashSync(password, 10);
expect(bcrypt.compareSync(password, hash)).toBe(true);
const longPassword = "a".repeat(200);
const hash = bcrypt.hashSync(longPassword, 10);
const modified = "a".repeat(200) + "b";
const hash2 = bcrypt.hashSync(modified, 10);
bcrypt.compareSync(longPassword, hash) === bcrypt.compareSync(modified, hash);
Одинаковый результат при значительном отличии строк указывает на достижение лимита.
Для выявления скрытых проблем применяются случайные генерации строк:
function randomString(len) {
return Array.from({ length: len }, () =>
String.fromCharCode(Math.floor(Math.random() * 0x10FFFF))
).join("");
}
Такие тесты позволяют выявлять:
bcrypt.js ожидает строку. При передаче других типов происходит неявное преобразование:
bcrypt.hashSync(null); // "null"
bcrypt.hashSync(undefined); // "undefined"
bcrypt.hashSync(12345); // "12345"
Это создаёт скрытый класс ошибок:
Функция сравнения:
bcrypt.compareSync(password, hash);
устойчива к:
но чувствительна к: