Для криптографических ключей «большой» значит не менее 128 бит. (Асимметричные ключи должны обладать определенными математическими свойствами. Таким образом, не каждое число является хорошим ключом, и ключи должны быть больше.) Если вы можете перепробовать несколько миллиардов ключей в секунду (скажем, 232), вам все равно потребуется 295 секунд, чтобы попробовать половину возможных ключей. Год, как вы помните, составляет чуть меньше 225 секунд, так что кто-то напишет «давным-давно» примерно через 1 000 000 000 000 000 000 лет.
До сих пор я пытался разворачивать степени двойки, и я призываю вас начать думать в терминах удвоения каждый раз, когда мощность увеличивается на единицу. Таким образом, 2^2 равно 4, 2^3 равно 8, 2^10 равно 1024. Рост в буквальном смысле определяет избитое слово «экспоненциальный».
Соли расширяют пространство поиска для злоумышленника, который получил копию чего-то, что он может атаковать в автономном режиме. Соли умножают пространство поиска и должны быть уникальными для каждого элемента. Например, если у вас есть 2^32 солей (примерно 4 миллиарда) и ваша база данных состоит из «людей, живущих на Земле», то в среднем каждая соль будет сопоставлена с двумя людьми.
Медленное угадывание
С помощью старых алгоритмов хранения паролей мы можем измерять атаки сотнями гигахэшей в секунду (гиг равен 2^32). Затраты на облако для таких скоростей составляют порядка десятков долларов в час. Когда злоумышленники могут проверять догадки на таких скоростях, это меняет эффективность защиты, особенно при наличии словарей распространенных паролей.
Существует два типа методов их замедления: онлайн и офлайн. Онлайновая защита – это когда ваше программное обеспечение может замечать атаки и реагировать на них. Это может быть либо постоянное замедление скорости, либо оно может расти с повторяющимися сбоями тестов, возможно, даже экспоненциально, или блокировать клиентов. Вот почему на банкоматах можно иметь четырехзначные PIN-коды. После нескольких неудачных догадок банкомат съест карту.
Офлайновая защита работает даже в том случае, когда злоумышленник может обойти ваше программное обеспечение и получить прямой доступ к базе данных значений. Офлайновые защиты хранят выходные данные функций, которые намеренно медленно вычисляются, например итерированные криптографические хэши.
Более новые алгоритмы, такие как Argon2 или Balloon, спроектированы с возможностью настройки и, как правило, позволяют увеличить количество итераций для подстройки степени безопасности хранимых паролей.
Какие параметры использовать – интересная загадка. Некоторые специалисты по безопасности рекомендуют использовать время в секундах для каждого хэша, признавая при этом, что это может привести к отказу в обслуживании вашего приложения [Burman, 2019]. Конечно, заманчиво давать очень консервативные рекомендации, но даже переход от 2^36 хэшей в секунду к 2^10 является значительным улучшением. Я предполагаю, что хорошая привязка – «настолько медленная, насколько это возможно, не разочаровывая пользователей». Выполнение определенных вычислений с вашими параметрами – количеством пользователей, длиной солей, правилами паролей – может привести к различным ответам для вашего приложения.
Порождение ключей
Оказывается, люди плохо запоминают случайные 128-битные или 2048-битные числа, как бы хорошо мы их ни кодировали. Порождение ключей – это термин, обозначающий превращение запоминаемого человеком секрета в нечто, противостоящее словарным атакам. (Также называется растягивание ключей или растягивание паролей.) Например, предположим, что вы хотите зашифровать электронную таблицу со списком ботанских шпионов. Если мы наивно используем в качестве пароля RememberAlderaan, то имперский криптограф может легко провести словарную атаку, о которой мы уже говорили. Поэтому мы используем подход, очень похожий на защиту пароля, который будет храниться в базе данных: мы многократно хэшируем его с солью. (Есть еще кое-что: существуют стандарты для порождения ключей, такие как функция порождения ключей на основе пароля с мудрым названием PBKDF-2. Я думаю, что название R2-D2 усложнило бы поиск.)
Порождение ключей обычно включает в себя подход «растяжения ключа», который предназначен для защиты от словарных атак на пароль. Например, Microsoft Office использует 10 000 итераций PBKDF-2 с паролем для создания ключа документа. Это растяжение важно везде, где для обеспечения безопасности используются данные, контролируемые человеком; растяжение ключей почти идентично способу хранения паролей в системе Unix, с разницей в том, как используются хранимые значения, и, возможно, в алгоритме, используемом для растяжения.
Распространенным шаблоном является использование запоминаемого пароля пользователя для защиты абсолютно случайного криптографического ключа. Другими словами, породите key1 из пароля. Затем используйте key1 для шифрования строго случайного key2 и используйте key2 для шифрования данных, которые вы хотите защитить. Существуют различные варианты, такие как случайные ключи для каждого документа (или других элементов данных, таких как строка или ячейка базы данных), при этом главный ключ хранится в системной связке ключей. Самое главное, что ключ является производным от пароля, а пароль напрямую не используется.
Ротация
«Это старый код, сэр, но он действует» – эти слова позволили повстанцам высадиться на спутнике Эндора, что привело к уничтожению второй «Звезды смерти». И они, между прочим, показывают нам, насколько хорошим может быть построение мира по «Звездным войнам». Ротация ключей, устроенная таким образом, чтобы у злоумышленника, который их украл, было ограниченное время для их использования, является отличной практикой безопасности. Это чревато поломкой. Но ротация является мощным инструментом для ограничения воздействия.
И, как показывает цитата из «Возвращения джедая», существуют ситуации, когда для того, чтобы не подстрелить собственный шаттл, необходимо управлять своими кодами (или ключами) таким образом, чтобы в данный момент времени действовало более одного. Это нетривиальная работа. Она требует дисциплины и тщательного обдумывания того, что делать с каждым ключом и когда это делать. Истечение срока действия пароля также показывает обратную сторону ротации. Если человек вынужден участвовать в каждой ротации, это может быть очень утомительной работой, и оказывается, что люди ловко справляются с ней, добавляя что-то вроде Feb22 к своему паролю; и злоумышленники пытаются делать то же самое, особенно когда они участвуют в целенаправленной атаке и могут иметь несколько образцов пароля Хана Соло.
Случайность
Недостаточный уровень случайности значительно облегчает поиск, и даже неявная закономерность может быть чрезвычайно полезна для злоумышленника.
Хотя кажется, будто некоторые ошибки появляются случайным образом, компьютеры как алгоритмические системы предсказуемы, а не случайны. Системы могут производить что-то, что кажется случайным, но на самом деле это просто «псевдослучайность». Национальный институт стандартов и технологий США (National Institute of Standards and Technology, NIST) определяет случайность как «некоторое значение из множества, которое имеет равную вероятность быть выбранным из общего множества исходов и, следовательно, является непредсказуемым». Псевдослучайность определяется как «детерминированный процесс (или данные, производимые таким процессом), выходные значения которого фактически неотличимы от значений случайного процесса до тех пор, пока внутренние состояния… неизвестны» [NIST, 2018]. Разница в том, что у компьютеров состояние есть, а у игральных костей его нет.
Генераторы псевдослучайных чисел берут начальное значение и итерируют его таким образом, чтобы затруднить прогнозирование следующего или предыдущего результата. (Раньше я говорил о предсказании следующих значений: возможность определять более ранние состояния также является недостатком. Например, если мы угадываем порядковые номера TCP, если мы можем получить последнее значение и вернуться назад, это позволяет нам подделать текущее соединение.)
В течение многих лет это было практическим ограничением. Системы, которые действительно нуждались в случайности, просили пользователя случайным образом набирать что-нибудь на клавиатуре или же использовать микросекундное время физического ввода и применять его в качестве начального значения (или смешивать его с другими данными, чтобы получать начальное значение). Очевидно, что это лучше работает для настольного компьютера, чем для облачного сервера. По мере того как облако становилось все более важным, производители микросхем добавляли специальное оборудование, которое (по сути) порождает очень качественную случайность путем измерения теплового шума [Hamburg, 2012].
Современные операционные системы, как правило, имеют довольно качественную случайность, и эксперты уделяют очень пристальное внимание тому, чтобы эти функции работали. Тем не менее вам, возможно, понадобится случайность криптографического качества. Многие системы по умолчанию выдают вам плохую случайность, потому что так быстрее. Существует совет, который часто относят к тому времени, когда истинная случайность была встроена в чипы, и заключается он в том, что вы не должны доверять генератору случайных чисел в операционной системе и должны использовать свой собственный. Могут быть обстоятельства, когда это верно или когда добавление дополнительной случайности может быть полезно для вас. Но вы, вероятно, сделаете свою систему менее безопасной, если пойдете по этому пути.
Например, люди часто ищут случайность в изменяющихся аспектах среды, обращая внимание на такие вещи, как идентификаторы процессов и время непрерывной работы.
Идентификаторы процессов являются только одним из многих предсказуемых элементов операционной системы. Идентификационные номера пользователей предсказуемы в Unix. Время непрерывной работы, как правило, группируется вокруг меньших чисел, а различные популяции машин имеют характерные колоколообразные кривые. Для консоли Xbox ожидаемое время непрерывной работы – нескольких часов; для телефонов и настольных компьютеров – от нескольких дней до недель. Высокопроизводительные компьютеры – около трех-четырех дней.