Обработка пустых строк — один из наиболее критичных сценариев при
тестировании механизмов хеширования паролей. Библиотека Password-hash в
JavaScript, как правило, принимает строку и возвращает её
криптографический хеш с солью. Однако пустая строка ("") не
должна рассматриваться как валидный пароль в большинстве систем
аутентификации.
При тестировании важно учитывать два уровня поведения:
С точки зрения библиотеки хеширования:
import passwordHash from 'password-hash';
const hash = passwordHash.generate('');
console.log(hash);
Большинство реализаций не выбрасывают ошибку, а спокойно вычисляют хеш от пустой строки. Это создаёт риск ложного ощущения допустимости такого пароля.
На уровне приложения необходимо проверять входные данные до вызова хеширования:
function safeHashPassword(password) {
if (typeof password !== 'string' || password.length === 0) {
throw new Error('Пароль не может быть пустым');
}
return passwordHash.generate(password);
}
Граничный случай: строка из пробелов
" "
Она не является пустой с точки зрения длины, но по смыслу эквивалентна отсутствию пароля. Поэтому важно применять дополнительную нормализацию:
if (password.trim().length === 0) {
throw new Error('Пароль не может состоять только из пробелов');
}
Unicode вводит значительную сложность в тестирование паролей, поскольку визуально одинаковые строки могут иметь различное бинарное представление.
Примеры проблемных сценариев:
é vs e + ́)const a = 'café'; // NFC
const b = 'cafe\u0301'; // NFD
console.log(a === b); // false
Для системы аутентификации это критично: пользователь может вводить один и тот же пароль разными способами, но хеши будут различаться.
function normalizePassword(password) {
return password.normalize('NFC');
}
const hash = passwordHash.generate(normalizePassword(password));
Важно фиксировать выбранную форму нормализации (обычно NFC) и использовать её и при регистрации, и при входе.
const password = '?pass';
Emoji может занимать несколько UTF-16 кодовых единиц. При
посимвольной обработке (password.length) возможны
некорректные результаты.
Пример:
console.log('?'.length); // 2
Это влияет на:
Рекомендуется использовать Unicode-aware подсчёт:
const length = [...password].length;
Длинные пароли — потенциальный вектор атак на производительность (DoS через вычисление хеша).
Типичный сценарий:
const longPassword = 'a'.repeat(10_000_000);
const hash = passwordHash.generate(longPassword);
Риски:
Практическое решение — ввод жёсткого лимита:
const MAX_PASSWORD_LENGTH = 4096;
function validatePassword(password) {
if (password.length > MAX_PASSWORD_LENGTH) {
throw new Error('Пароль слишком длинный');
}
}
Важно понимать, что многие криптографические библиотеки всё равно используют внутреннее усечение входа, но полагаться на это нельзя.
Для проверки производительности применяются нагрузочные тесты:
import passwordHash from 'password-hash';
console.time('hash');
const hash = passwordHash.generate('a'.repeat(100000));
console.timeEnd('hash');
Цель — выявить экспоненциальные деградации или зависимость алгоритма от длины входа.
Иногда в систему могут попасть строки, содержащие неожиданные управляющие символы:
\u0000 (null byte)\u200B zero-width space)Пример:
const password = 'pass\u200Bword';
Визуально строка выглядит как password, но фактически
это разные данные.
function sanitize(password) {
return password.replace(/[\u200B-\u200D\uFEFF]/g, '');
}
Граничные случаи важно проверять не только на этапе генерации, но и на этапе сравнения:
const hash = passwordHash.generate('password');
console.log(passwordHash.verify('', hash)); // false
Для пустых и нормализованных строк важно убедиться, что:
Наиболее сложные случаи возникают при комбинации факторов:
Пример теста:
describe('Password edge cases', () => {
test('unicode normalization consistency', () => {
const p1 = 'café';
const p2 = 'cafe\u0301';
const h1 = passwordHash.generate(p1.normalize('NFC'));
const h2 = passwordHash.generate(p2.normalize('NFC'));
expect(h1).toBe(h2);
});
test('empty password rejection', () => {
expect(() => {
safeHashPassword('');
}).toThrow();
});
test('long password rejection', () => {
expect(() => {
safeHashPassword('a'.repeat(5000));
}).toThrow();
});
});
JavaScript позволяет передавать нестроковые значения, что также требует тестирования:
passwordHash.generate(null);
passwordHash.generate(undefined);
passwordHash.generate(12345);
Некоторые реализации приводят их к строке, другие выбрасывают исключение. Поэтому обязательна явная валидация:
function assertString(password) {
if (typeof password !== 'string') {
throw new Error('Пароль должен быть строкой');
}
}
Ключевые направления проверки в контексте Password-hash: