Bcrypt

Создайте хеш bcrypt или проверьте пароль по готовому, не покидая браузер: выберите стоимость и вариант $2a$, $2b$ или $2y$ и разберите любой хеш на части.

Ничего из набранного здесь не покидает этот браузер. Работа идёт в Web Worker в этой вкладке — откройте свою панель сети и смотрите: во время вычисления не уходит ни один запрос.

Создать хеш

Пароль, стоимость и вариант. Соль берётся свежей из этого браузера при каждом запуске, поэтому один и тот же пароль каждый раз даёт другой хеш — так bcrypt и работает, это не сбой.

Проверить пароль по хешу

Вставленный хеш сразу разбирается на части — без пароля и без ожидания. Добавьте пароль, чтобы получить ответ о совпадении. Хеш нельзя обратить ни здесь, ни где-либо ещё.

Что на самом деле лежит внутри хеша bcrypt

Хеш bcrypt — это одна строка из шестидесяти символов, и каждая её часть открыта. Секретного в ней нет ничего, кроме пароля, который её породил, а его там нет вовсе. Разобрать хеш на части можно без ключа, без пароля и без каких-либо вычислений — эта страница делает это в тот момент, когда вы его вставляете.

  • Вариант — поле, с которого строка начинается: $2b$, $2a$ или $2y$. Он называет, по чьим правилам сделан хеш.
  • Стоимость, записанная ровно двумя цифрами: от 04 до 31. Это показатель степени, поэтому стоимость 12 означает 2^12 — 4 096 — раундов растягивания ключа, а стоимость 13 вдвое дороже стоимости 12, а не на одну тринадцатую.
  • Соль, двадцать два символа: шестнадцать случайных байт, хранящихся в открытую рядом с тем ответом, который они посолили.
  • Дайджест, последний тридцать один символ: двадцать три байта вывода. Это единственная часть, которая зависит от пароля, и это не хеш — хеш это вся строка целиком.

Алфавит, из которого взяты эти символы, — собственный алфавит bcrypt, и это не стандартный Base64: он идёт ./A-Za-z0-9, так что точка и косая черта открывают его там, где плюс и косая черта закрывают Base64. Обычный декодер Base64 вернёт из него неверные байты, а не ошибку, — это классический способ потерять полдня.

Предел — семьдесят два байта, а по-русски он наступает на тридцати шести символах

bcrypt читает не больше семидесяти двух байт пароля и не обращает внимания на всё, что за ними. Не семьдесят два символа — семьдесят два байта UTF-8, поэтому сколько это символов, зависит от того, сколько стоят буквы языка: двадцать шесть простых латинских букв стоят по одному байту, а любая другая буква — больше. Кириллическая буква стоит два байта, поэтому русский пароль обрезается на тридцати шести символах: половина того числа, с которым встречается читающий по-английски, и такая длина, какую люди действительно набирают. Для этого читателя предел не теоретический, а тот, до которого доходят.

  • ASCII стоит один байт на символ, поэтому говорящий по-английски встречает предел на семидесяти двух символах и почти никогда до него не доходит.
  • Иврит, арабский, русский и греческий стоят два байта на букву, поэтому предел наступает на тридцати шести символах.
  • Японский, корейский и китайский стоят три, поэтому он наступает на двадцати четырёх.
  • Большинство эмодзи стоит четыре, и многие из тех, что люди действительно набирают, собраны из нескольких кодовых точек, соединённых вместе, так что горстки таких уже хватает на весь бюджет.

Счётчик байтов под полем пароля существует затем, чтобы предел можно было видеть приближающимся, а не обнаруживать задним числом. И срез приходится на байт, а не на символ, ровно так, как это делает любая эталонная реализация, — поэтому многобайтовый символ, сидящий на самой границе, теряет часть себя и сохраняет остаток. Это совместимое поведение, а не дефект, и страница говорит об этом, когда так выходит.

А теперь та часть, которая объясняет всё остальное: обрезка не оставляет в ответе следа. Расписание ключа примешивает к своему состоянию ровно семьдесят два байта ключа и никогда не заворачивает более длинный, поэтому хеш стобайтового пароля и хеш первых семидесяти двух его байт дают один и тот же дайджест. Реализации нечего обнаруживать и не о чем сообщать — вот почему ни одна библиотека не поднимает ошибку и почему два разных длинных пароля, совпадающих в первых семидесяти двух байтах, оба подходят к одному и тому же сохранённому хешу. Эта страница тоже не может отказать длинному паролю — если вы проверяете его по хешу, который настоящая система сделала с обрезкой, ответ вам нужен, — поэтому она даёт ответ и говорит, что именно было отрезано.

Вариант меняет префикс, а не дайджест

Вариант выглядит как номер версии, но это не он. Три варианта, которые пишет этот инструмент, не упорядочены, ни один не приходит на смену другому, и выбор между ними — решение о совместимости, а не о безопасности.

  • $2b$ — то, на чём остановилась OpenBSD и что сегодня выдают Python, Node и Go. Здесь это значение по умолчанию и верный ответ, когда ничто не вынуждает взять другое.
  • $2a$ — тот, что постарше, и некоторые долгоживущие развёртывания на Java и Spring Security до сих пор ждут именно его.
  • $2y$ — то, что пишет password_hash в PHP, а значит и Laravel, так что вставляемому в тот мир хешу обычно нужен он.

Различает эти три то, как они обходятся с ключом длиннее 255 байт, а bcrypt уже перестал читать на семидесяти двух — поэтому ни один пароль, какой здесь можно набрать, до этого различия не добирается. Для всего, что этому инструменту когда-либо дадут, все три дают одни и те же двадцать три байта и расходятся лишь в четырёх символах в начале. Смените вариант — дайджест не сдвинется.

Есть ещё два, и этот инструмент называет их, а не вычисляет. $2x$ — не исправление: Openwall отчеканил его, чтобы намеренно воспроизвести ошибку расширения знака, так что хеши, сделанные сломанным кодом, всё ещё можно было проверять, — а реализовать его здесь означало бы поместить известный дефект внутрь самого инструмента. $2$ — исходный, из времён до того, как к ключу добавили завершающий нулевой байт. Ни один из двух не пишет сегодня ничто действующее; если у вас на руках такой, он вышел из системы настолько старой, что вариант — наименьшее из того, что вы нашли.

Стоимость и во что она обходится на вашей собственной машине

Стоимость — это фактор работы, от 4 до 31, и это показатель степени: каждый шаг удваивает время. В этом весь смысл bcrypt. Хеш пароля обязан быть медленным, потому что атакующий с украденной таблицей платит ту же цену за каждую догадку, а удвоение стоимости вдвое сокращает число догадок в секунду, которое он получает за свои деньги.

Сколько именно занимает та или иная стоимость — это факт о машине, которая её выполняет, а не число, которое можно записать заранее: телефон и сервер отстоят друг от друга на два порядка. Поэтому страница измеряет, а не цитирует: при загрузке она прогоняет один короткий хеш на вашем собственном устройстве и экстраполирует от него — вот почему оценка рядом с элементом управления стоимостью на мгновение пуста, а затем появляется. Выше нескольких секунд она спрашивает, прежде чем начать, чтобы опечатка в цифре не читалась как зависшая страница.

Если выбирать число: 12 — то, что по умолчанию выдают Laravel и пакет bcrypt для Python, и разумный производственный минимум на сегодня. Стоимости пониже — для тестовых фикстур, где набор тестов, засевающий пятьдесят пользователей, не должен тратить на каждого по секунде. Всё, что примерно выше 15, стоит измерить на вашем настоящем трафике входов, прежде чем выпускать, потому что каждый вход платит ту же цену.

# Apache: записать строку htpasswd со стоимостью 12
htpasswd -nbBC 12 alice "correct horse battery staple"

# PHP и Laravel: password_hash выдаёт $2y$
php -r 'echo password_hash("correct horse battery staple", PASSWORD_BCRYPT);'

# Python: пакет bcrypt выдаёт $2b$, а gensalt принимает стоимость
python -c "import bcrypt; print(bcrypt.hashpw(b'correct horse battery staple', bcrypt.gensalt(12)))"

Соль, и когда сделанный здесь хеш не годится для хранения

Соль — это шестнадцать случайных байт, которые берутся свежими для каждого хеша, и хранится она в открытую внутри самой строки. Она не секрет и никогда им и не задумывалась: её работа в том, чтобы два одинаковых пароля давали два никак не связанных хеша, так что украденную таблицу нельзя атаковать одним заранее посчитанным словарём.

Поэтому же один и тот же пароль даёт вам здесь другой хеш при каждом нажатии кнопки. Это не сбой, и прежний ответ не устарел: свежая соль — это работающий bcrypt. Любой из этих хешей подходит к паролю, потому что соль путешествует внутри той строки, которую проверяют.

Поле соли в дополнительных настройках существует ради одной задачи: воспроизвести чужой хеш в точности, чтобы можно было поставить две реализации друг против друга. Вставьте в него целый хеш — стоимость и вариант последуют за ним сами, так что верный пароль воспроизведёт ввод символ в символ. Всё, что получено таким способом, годится для сравнения, а не для хранения: соль, использованная дважды, перестала делать свою работу.

Хеш bcrypt нельзя обратить

Люди приходят на страницы вроде этой в поисках способа расшифровать хеш bcrypt. Такого способа нет, и причина не в том, что это трудно: пароля в строке нет. Шестьдесят символов несут вариант, стоимость, шестнадцать случайных байт и двадцать три байта вывода, и никакое их сочетание не содержит пароля какой бы то ни было длины. Ничего не шифровалось, значит, и расшифровывать нечего.

Что сайт может сделать — и что делают те, кто рекламирует расшифровку, — это гадать. Берётся список распространённых паролей, каждый хешируется с вашей солью и на вашей стоимости, и проверяется, не подойдёт ли какой-нибудь. bcrypt устроен так, чтобы сделать дорогим именно это, — ради того стоимость и существует, — и этот инструмент не предлагает ничего подобного ни в каком виде.

Честная версия этого вопроса — обычно другой вопрос, и у него есть ответ: что лежит в этой строке и даёт ли её вот этот пароль? Оба ответа на этой странице. Вставьте хеш, чтобы увидеть всё, что он несёт, и добавьте пароль, чтобы получить «да» или «нет».

Частые вопросы

Отправляется ли мой пароль на сервер?
Нет. Всё происходит в этой вкладке браузера — хеширование идёт в Web Worker на вашей машине, пароль никогда не попадает в адрес, и ничего не пишется в хранилище браузера. Откройте свою панель сети, наберите пароль и нажмите кнопку: ни один запрос не уходит. В этом и разница между этой страницей и двумя сайтами, которые стоят выше неё по тому же запросу.
Можно ли расшифровать хеш bcrypt обратно в пароль?
Нет, и не потому, что это трудно. Пароля в строке нет: там вариант, стоимость, соль и двадцать три байта вывода, и ничто из этого не содержит ввода. Всякий, кто предлагает расшифровать такой хеш, перебирает распространённые пароли против вашей соли, а bcrypt нарочно устроен так, чтобы это было медленно.
Почему один и тот же пароль каждый раз даёт другой хеш?
Потому что для каждого запуска берётся новая случайная соль, а соль — часть строки. Любой из этих хешей подходит к тому же самому паролю: проверяющий читает соль из того хеша, который вы вставляете, и ему не нужно знать, какой запуск его породил.
Мой пароль обрезали на семидесяти двух байтах. Почему не было ошибки?
Потому что обнаруживать нечего. bcrypt примешивает к своему состоянию ровно семьдесят два байта ключа и никогда не читает дальше, поэтому длинный пароль и первые семьдесят два его байта дают один и тот же дайджест — обрезка не оставляет библиотеке никакого следа, который та могла бы заметить. Эта страница сообщает вам, что так вышло, и это максимум того, что реализация может честно сделать.
Почему мой пароль упёрся в предел на тридцати шести символах?
Потому что предел — семьдесят два байта, а не семьдесят два символа, и кириллическая, ивритская, арабская или греческая буква стоит в UTF-8 два байта. Японские, корейские и китайские стоят три, поэтому там предел наступает на двадцати четырёх символах, а большинство эмодзи стоит четыре. Счётчик под полем показывает число байт прямо по ходу набора.
Какой вариант выбрать?
Тот, которого ждёт система, куда вы вставляете: $2y$ для PHP и Laravel, $2a$ для старого Spring Security, $2b$ во всех остальных случаях. Это решение о совместимости, а не о безопасности: при любой длине, какую можно дать этому инструменту, все три дают одинаковые дайджесты и расходятся лишь в тех четырёх символах в начале.
Какую стоимость использовать?
12 — распространённое значение по умолчанию и разумный производственный минимум. Для тестовых фикстур берите низкую стоимость, чтобы ваш набор тестов не платил за растягивание ключа полсотни раз, а всё, что выше 15, сначала измерьте на вашем настоящем трафике входов, потому что каждый успешный вход платит ту же цену, что и атакующий.
Безопасно ли класть сделанный здесь хеш в настоящую таблицу пользователей?
Для засева базы, написания фикстуры или добавления строки htpasswd — да: соль берётся из криптографического источника случайности вашего браузера. Но это черновая страница, а не служба учётных данных: если вы наберёте соль сами, результат перестанет быть безопасным для хранения — страница говорит об этом сразу, — а пароль, который стоит защищать, лучше чеканить там, где ему предстоит жить.
Почему мой хеш начинается с $2y$, хотя код, который его сделал, говорит bcrypt?
Потому что password_hash в PHP пишет $2y$, а Laravel построен на нём. Алгоритм тот же, что и у $2b$; вариант записывает, какая реализация написала строку. Проверяющий, который его отвергает, отвергает вариант, а не терпит неудачу в сопоставлении пароля.
Я вставил хеш, а он говорит, что соль записана иначе. Он сломан?
Нет. Двадцать два символа кодируют больше битов, чем нужно шестнадцати байтам, поэтому четыре бита в конце отбрасываются, и у некоторых солей есть больше одной записи, раскодирующейся в те же байты. Настоящие системы такие выдавали, поэтому эта страница читает такой хеш, а не отвергает его, и сообщает вам каноническую запись.

Похожие инструменты