Криптография
Пароли и хеширование с Argon2id
Почему быстрые хеши не годятся для паролей, что делают соль и коэффициент нагрузки и как хешировать и проверять пароли с Argon2id в Python через argon2-cffi.
Разделяем кодирование, шифрование и хеширование на лабораторных с Base64, SHA-256 и OpenSSL. Почему Base64 ничего не защищает, а хеши необратимы.
Кодирование, шифрование и хеширование превращают данные во что-то другое, поэтому их и путают. Цели у них разные. Кодирование меняет способ записи данных, и обратить его может любой. Шифрование скрывает данные от тех, у кого нет ключа, а с этим ключом его можно обратить. Хеширование даёт короткий отпечаток, из которого нельзя получить исходные данные. Запомните одну фразу: Base64 не шифрование, а хеш ничего не скрывает при коротких входных данных.
В статье эти три идеи разводятся по отдельности, а затем вы запускаете каждую в терминале на одном и том же тексте. Результаты вы сравните сами.
Нужен терминал с base64, sha256sum и openssl. В Windows 11 команды ниже работают в Git Bash, который поставляется с Git for Windows. Для шага с хешированием есть версия для PowerShell. Предварительные знания криптографии не нужны, но сначала стоит прочитать статью о шифре Цезаря: она помогает понять идею ключа.
Все команды лабораторной безопасно запускать на своей машине. Используйте тестовый файл, настоящие секреты здесь не нужны.
Три операции отвечают на три разных вопроса.
| Операция | На какой вопрос отвечает | Нужен ключ? | Можно обратить? |
|---|---|---|---|
| Кодирование | Как представить эти байты в виде текста или в формате, который ожидает другая система? | Нет | Да, любой |
| Шифрование | Кто может прочитать эти данные? | Да | Да, владельцы нужного ключа |
| Хеширование | Изменились ли эти данные, совпадают ли два значения? | Нет | Нет, так задумано |
Сверяйтесь с этой таблицей во время лабораторной. Остальная часть статьи объясняет каждую строку.
Кодирование переписывает данные в другой форме, чтобы их могла передать система. Классический пример — Base64. Он берёт байты и записывает их 64 печатными символами: от A до Z, от a до z, от 0 до 9, + и /, а для дополнения используется =. Формат определён в RFC 4648. Его используют вложения в письмах, data URL и многие payload API, потому что транспорт ожидает текст.
В Base64 нет секрета. Таблица соответствия публична и неизменна. Любой инструмент, знающий правило, превратит вывод обратно во вход. Кодирование нужно для совместимости, секретность в его задачи не входит.
Шифрование берёт читаемые данные, открытый текст, и с помощью алгоритма и ключа превращает их в шифртекст. Тот, у кого нет ключа, не должен узнать из шифртекста ничего полезного. Тот, у кого ключ есть, может его расшифровать.
Есть два семейства. Симметричное шифрование использует один секретный ключ для шифрования и расшифрования. Обычный выбор здесь — AES. Асимметричное шифрование использует открытый и закрытый ключи. В этой статье используется симметричное шифрование, как и в команде OpenSSL из лабораторной.
Хорошее шифрование зависит не только от алгоритма. Нужно выбрать безопасный режим, генерировать ключи хорошим источником случайности и защищать ключ. Команда OpenSSL в лабораторной получает ключ из пароля через PBKDF2. В справке OpenSSL сказано, что PBKDF2 по умолчанию использует 10,000 итераций, если не изменить это через -iter.
Криптографическая хеш-функция принимает вход любой длины и возвращает вывод фиксированной длины. SHA-256 описан в FIPS 180-4, его вывод всегда равен 256 битам и записывается 64 шестнадцатеричными символами. Для начинающих важны два свойства:
Хеширование однонаправленно. По выводу нельзя узнать исходный текст. Можно проверить, даёт ли кандидат на вход известный хеш. Восстановить вход из одного хеша нельзя, разве что перебором догадок.
Хеш работает как отпечаток, замком он не служит. SHA-256 публичного файла может вычислить кто угодно. Хеш доказывает совпадение двух копий. Файл он не скрывает.
Большинство реальных ошибок безопасности в этой области происходит от неподходящего инструмента.
Если знать разницу с самого начала, в эти ловушки вы не попадёте.
Откройте Git Bash в пустой папке. Закодируйте короткую фразу, затем декодируйте результат.
echo -n 'hello lab' | base64Вывод на этой машине с OpenSSL 3.2.4 и Git Bash:
aGVsbG8gbGFiТеперь декодируйте этот вывод:
echo -n 'aGVsbG8gbGFi' | base64 -dВывод:
hello labОбратите внимание на две вещи. Во-первых, ключ не понадобился. Во-вторых, echo -n важен. Без -n команда echo добавляет перевод строки, и вывод Base64 меняется. Выполните echo 'hello lab' | base64 и сравните: у меня получилось aGVsbG8gbGFiCg==. Здесь Base64 превращает 9 байт в 12 символов, поэтому вывод длиннее входа.
Спросите себя: мог бы это обратить кто-то без всякого секрета? Да. Этот тест и отделяет кодирование от шифрования.
Пропустите ту же фразу через SHA-256:
echo -n 'hello lab' | sha256sumВывод:
8fc6baba79a0d60b37dee612ccc1ec096ef52dba8cb0a24a6451e8bfb0e4d761 *-Теперь добавьте один восклицательный знак и запустите снова:
echo -n 'hello lab!' | sha256sumВывод:
36902fb7fdea33458ad09dfeb52118dc47f99a8ca2b9001080366d55511de915 *-У двух хешей нет видимого общего рисунка. Один символ изменил весь вывод. Оба состоят из 64 шестнадцатеричных символов, это 256 бит.
- в конце означает, что вход пришёл со стандартного ввода, а не из файла. * перед ним — маркер двоичного режима, который печатает sha256sum в Git Bash. Другие системы могут печатать вместо него два пробела, хеш от этого не меняется. Хеш файла работает так же.
Создайте небольшой файл и вычислите его хеш:
echo 'secret note' > note.txt
sha256sum note.txtНа моей машине, с окончаниями строк Unix в файле, вывод был таким:
b5f891a5e064fec661025d0b0ec2de157487a6c2e5cc46dc0971fee56f86c645 *note.txtИзмените файл, хотя бы один символ, и вычислите хеш снова. Значение будет другим. Так работают проверки целостности. Проверка говорит, что файл изменился. Она не говорит, что именно изменилось, и не мешает атакующему изменить и файл, и опубликованный хеш.
Если вы предпочитаете PowerShell, в Windows есть встроенная команда:
Get-FileHash note.txt -Algorithm SHA256В выводе проверьте строку Hash, в ней 64 шестнадцатеричных символа. Хеш зависит от точных байтов файла. Set-Content в PowerShell записывает окончания строк Windows (CRLF), которые отличаются от окончания LF в примере для Bash выше. На моей машине файл, записанный таким способом, дал такой хеш:
406A5646AE75CA75D032F306ABD33085DC9F656E3109BB962AEAB335EEC9ED85Те же байты, захешированные sha256sum в Git Bash, дали то же значение в нижнем регистре. Если ваш хеш отличается от примера для Bash, но совпадает с результатом PowerShell для вашего файла, причина в окончаниях строк.
Создайте заметку и зашифруйте её алгоритмом AES-256-CBC. OpenSSL запросит пароль. Выберите такой, который запомните и будете использовать только для этой лабораторной.
echo 'secret note' > note.txt
openssl enc -aes-256-cbc -pbkdf2 -salt -in note.txt -out note.encOpenSSL запрашивает пароль, и зашифрованный файл записывается в note.enc. Параметр -salt добавляет случайную соль, поэтому при двойном шифровании одного файла вывод получается разным. Зашифрованный с -salt файл начинается с текста ASCII Salted__. Я проверил это на своей машине. Остальная часть файла двоичная и нечитаемая.
Теперь расшифруйте его:
openssl enc -d -aes-256-cbc -pbkdf2 -in note.encВведите тот же пароль. Вывод:
secret noteРасшифровка работает только с правильным паролем. Я проверил с неправильным: OpenSSL напечатал bad decrypt и завершился с ошибкой.
Теперь измените один байт зашифрованного файла и попробуйте его расшифровать. Я инвертировал один бит внутри блока шифртекста в копии с именем tampered.enc. Расшифровка завершилась ошибкой bad decrypt, а команда вернула статус 1.
Такой сбой — счастливая случайность, связанная с дополнением в CBC. Другие изменения могут дать мусор на выходе без всякой ошибки. Обычное шифрование не даёт надёжного сигнала о том, что шифртекст изменён. Системы, которым нужны и секретность, и целостность, используют аутентифицированное шифрование, например AES-GCM, или добавляют код аутентификации сообщения. Это продвинутая тема, но она объясняет, почему зашифрованному файлу нельзя доверять автоматически.
Пропустите один и тот же текст через каждую операцию и сравните:
| Операция | Команда | Вывод для hello lab (полученный) |
|---|---|---|
| Кодирование | echo -n 'hello lab' | base64 |
aGVsbG8gbGFi |
| Хеширование | echo -n 'hello lab' | sha256sum |
8fc6baba...e4d761 |
| Шифрование | openssl enc ... -in note.txt |
Двоичные данные, для чтения нужен пароль |
Спросите, что даёт каждый вывод:
hello lab.hello lab, но сам вход из одного хеша не восстановить.secret note можно восстановить только с паролем.Называть Base64 «шифрованием» или «обфускацией» для секретов. Base64 ничего не скрывает. Любой, кто видит строку Base64, может её декодировать. Храните секреты в менеджере секретов или зашифрованном хранилище, кодирования для этого мало.
Использовать хеш как замок. Хеш однонаправлен, но короткие или распространённые входы можно угадать. Захешируйте hello lab, и атакующий со списком распространённых фраз восстановит его, не взламывая SHA-256. Поэтому для хеширования паролей нужны соль и медленные алгоритмы.
Использовать обычный SHA-256 для паролей. SHA-256 быстрый по замыслу: для контрольных сумм файлов это подходит, а для хранения паролей подходит плохо. Используйте функцию хеширования паролей, созданную для этой задачи.
Доверять хешу из того же места, что и файл. Контрольная сумма на той же скомпрометированной странице ничего не доказывает. Получите ожидаемый хеш по отдельному доверенному каналу или проверьте цифровую подпись.
Забывать, что шифрованию нужно управление ключами. В лабораторной используется пароль, введённый в приглашении. Реальные системы должны хранить, ротировать и защищать ключи. Потеря ключа означает потерю данных.
Писать собственную криптографию. Используйте проверенные библиотеки и протоколы. Команды лабораторной нужны для обучения, шаблоном для продакшен-систем они не служат.
Повторите три лабораторные с другой фразой, затем с файлом в несколько килобайт. Заметьте, что вывод Base64 растёт примерно на треть, хеш остаётся длиной 64 символа, а зашифрованный файл увеличивается только на заголовок и дополнение.
Дальше почитайте о хешировании паролей. Сложность там связана со стоимостью перебора, само хеширование ни при чём. Следующая статья серии, Как работает хеширование паролей с Argon2id, объясняет соли, коэффициенты сложности и то, почему важны медленные хеши. Если сначала хотите освежить в памяти шифры, прочитайте Шифр Цезаря на Python. Там ключ и алгоритм показаны в виде, который можно проверить.
Изучая реальные протоколы, ищите в каждой схеме три вещи: что она кодирует, что шифрует и что хеширует. Большая часть путаницы исчезает, если спросить, какую из трёх задач решает каждая часть.
Криптография
Почему быстрые хеши не годятся для паролей, что делают соль и коэффициент нагрузки и как хешировать и проверять пароли с Argon2id в Python через argon2-cffi.
Криптография
Публичные и приватные ключи с нуля: что доказывает подпись, почему приватный ключ остаётся в секрете, и лабораторная на Ed25519, где изменённый файл не проходит проверку.
Криптография
Криптография с нуля: шифрование, ключи, хеши, подписи и почему шифр Цезаря учит идеям, но ничего не защищает.