Должен ли я использовать Base64 или Unicode для хранения хэшей и солей?
Я никогда не работал на стороне безопасности веб-приложений, так как я просто из колледжа. Теперь я ищу работу и работаю над некоторыми сайтами на стороне, чтобы сохранить свои навыки острыми и получить новые. Один сайт, над которым я работаю, в значительной степени скопирован из оригинального MEAN
стека из парней, которые его создали, но пытается понять его и сделать что-то лучше, где я могу.
Чтобы вычислить хэш и соль, создатели использовали PBKDF2. Мне не интересно слышать о аргументах за или против PBKDF2, поскольку это не тот вопрос, о котором идет речь. Кажется, они использовали буферы для всего, что я понимаю, это обычная практика в node
. Меня интересуют причины использования base64
для кодирования буфера, а не просто использование UTF-8
, которое является опцией с объектом буфера. Большинство компьютеров в настоящее время могут обрабатывать многие символы в Unicode, если не все из них, но создатели могли бы выбрать кодирование паролей в подмножестве Unicode, не ограничиваясь 65 символами base64
.
Под "выбором между кодировкой как UTF-8
или base64
" я подразумеваю преобразование двоичного кода хэша, вычисленного из пароля, в данную кодировку. node.js
указывает пару способов кодирования двоичных данных в объект Buffer. На странице документации для класса Buffer:
Pure JavaScript is Unicode friendly but not nice to binary data. When dealing with TCP
streams or the file system, it necessary to handle octet streams. Node has several
strategies for manipulating, creating, and consuming octet streams.
Raw data is stored in instances of the Buffer class. A Buffer is similar to an array
of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer
cannot be resized.
Что класс Buffer делает, как я понимаю, принимает некоторые двоичные данные и вычисляет значение каждого 8 (обычно) бит. Затем он преобразует каждый набор бит в символ, соответствующий его значению в указанной вами кодировке. Например, если двоичные данные 00101100
(8 бит), и вы указываете UTF-8
в качестве кодировки, выход будет ,
(запятая). Это то, что каждый, кто смотрит на выход буфера, увидит, глядя на него с помощью текстового редактора, такого как vim
, а также на то, что компьютер "увидит" при "чтении". Класс Buffer имеет несколько доступных кодировок, таких как UTF-8
, base64
и binary
.
Я думаю, они чувствовали, что, сохраняя любой символ UTF-8
, который можно вообразить в хэше, как они должны были бы сделать, не будет фазировать большинство современных компьютеров с их гигабайтами ОЗУ и терабайтами пространства, фактически показывая все эти символы, так как они могут захотеть делать в журналах и т.д., будут вызывать у пользователей пользователей, которым придется смотреть на странные китайские, греческие, болгарские и т.д. символы, а также контрольные символы, такие как кнопка Ctrl
или кнопка Backspace
или даже звуковые сигналы. Им никогда не понадобилось бы разбираться ни в одном из них, если бы они не были опытными пользователями, которые сами тестировали PBKDF2, но первая задача программиста - не давать никому из его пользователей инфаркт. Использование base64
увеличивает накладные расходы примерно на треть, что вряд ли стоит отметить в эти дни, и уменьшает набор символов, что ничто не мешает безопасности. В конце концов, компьютеры написаны полностью в двоичном формате. Как я уже говорил, они могли выбрать другой подмножество Unicode, но base64
уже является стандартным, что упрощает работу и сокращает работу программиста.
Я правильно понимаю причины, по которым создатели этого репозитория решили кодировать свои пароли в base64
вместо всего Юникода? Лучше ли придерживаться их примера, или я должен идти с Unicode или большим подмножеством?
Ответы
Ответ 1
Существует фундаментальная причина безопасности для хранения как Base64, а не Unicode: хеш может содержать значение байта "0", используемое многими языками программирования в качестве маркера конца строки.
Если вы храните свой хэш в виде Юникода, вы, другой программист или какой-либо используемый вами библиотечный код, можете рассматривать его как строку, а не набор байтов, и сравнивать с помощью strcmp()
или аналогичной функции сравнения строк. Если ваш хэш содержит значение байта "0", вы эффективно усекали хэш только до части до "0", делая атаки намного проще.
Кодировка Base64 позволяет избежать этой проблемы: значение байта "0" не может иметь место в кодированной форме хэша, поэтому не имеет значения, сравниваете ли вы кодированные хэши с помощью memcmp()
(правильный путь) или strcmp()
( неправильный путь).
Это не просто теоретическая проблема: либо было несколько случаев кода для проверки цифровых подписей с использованием strcmp()
, что значительно ослабляет безопасность.
Ответ 2
Хэш-значение представляет собой последовательность байтов. Это двоичная информация. Это не последовательность символов.
UTF-8 - это кодирование для превращения последовательностей символов в последовательности байтов. Хранение хэш-значения "как UTF-8" не имеет смысла, так как это уже последовательность байтов, а не последовательность символов.
К сожалению, многие люди привыкли рассматривать байты как скрытый характер; он лежал в основе языка программирования C и все еще заражает некоторые довольно современные и распространенные среды, такие как Python. Однако только путаница и печаль ложатся на этот путь. Обычными симптомами являются люди, плачущие и скулящие по поводу ужасного "нулевого символа", то есть байта значения 0 (совершенно точное значение для байта), который превращается в персонажа, становится особым символом, который служит в качестве конца -строчный индикатор на языках семейства C. Эта путаница может даже привести к уязвимостям (нулевое значение для функции сравнения - раннее, чем ожидалось).
Как только вы поняли, что двоичный файл двоичный, проблема становится: как нам обрабатывать и хранить хеш-значение? В частности, в JavaScript язык, который, как известно, особенно плох при обработке двоичных значений. Решение - это кодирование, которое превращает байты в символы, а не только любой символ, а очень небольшое подмножество хорошо сохранившихся символов. Это называется Base64. Base64 представляет собой общую схему для кодирования байтов в символьные строки, которые не содержат проблемных символов (без ноль, только для ASCII-печатных символов, исключая все управляющие символы и несколько других, таких как кавычки).
Не использовать Base64 подразумевает, что JavaScript может управлять произвольной последовательностью байтов, как если бы это были просто "нормальные символы", и это просто неверно.
Ответ 3
Это простой ответ, так как существует множество байтовых последовательностей, которые не являются хорошо сформированными строками UTF-8. Наиболее распространенным является байт продолжения (0x80-0xbf), которому не предшествует старший байт в многобайтовой последовательности (0xc0-0xf7); байты 0xf8-0xff также недействительны.
Итак, эти последовательности байтов недействительны строки UTF-8:
-
0x80
-
0x40 0xa0
-
0xff
-
0xfe
-
0xFA
Если вы хотите кодировать произвольные данные в виде строки, используйте схему, которая позволяет это. Base64 - одна из этих схем.
Дополнительная точка: вы можете подумать о себе, ну, мне все равно, хорошо ли они сформированы строки UTF-8, я никогда не буду использовать данные в виде строки, я просто хочу передайте эту последовательность байтов для сохранения позже.
Проблема с этим заключается в том, что если вы передаете произвольную последовательность байтов в приложение, ожидающее строку UTF-8, и оно не является корректным, приложение не обязано использовать эту последовательность байтов. Он может отклонить его с ошибкой, он может обрезать строку, она может попытаться "исправить" ее.
Поэтому не пытайтесь хранить произвольные последовательности байтов как строку UTF-8.
Ответ 4
Base64 лучше, но рассмотрите алфавит bases base64 для транспорта. Base64 может конфликтовать с синтаксисом querystring.
Другой вариант, который вы можете рассмотреть, - использование hex. Его длиннее, но редко конфликтует с любым синтаксисом.