Криптографически сгенерированный адрес
Криптографически сгенерированный адрес (англ. Cryptographically Generated Address, CGA) — это адрес IPv6, в котором идентификатор узла вычисляется с помощью криптографической хеш-функции[1]. Такая процедура позволяет связать открытый подписи ключ с адресом IPv6 в рамках Secure Neighbor Discovery Protocol (SEND)[2].
Характеристики
Криптографически сгенерированный адрес — это адрес IPv6, у которого идентификатор интерфейса создан по методу CGA. Идентификатор интерфейса формируется как младшие 64 бита IPv6-адреса и используется для идентификации сетевого интерфейса хоста в его подсети. Подсеть определяется старшими 64 битами — префиксом подсети.
Кроме открытого ключа, который связывается с CGA, метод генерации CGA использует несколько входных параметров, включая заданный префикс подсети. Эти параметры, а также прочие параметры, генерируемые в процессе работы метода CGA, формируют структуру данных CGA Parameters. Для проверки соответствующего CGA необходимо знать полный набор этих параметров.
Структура данных CGA Parameters включает:
modifier: случайное 128-битное беззнаковое число;subnetPrefix: 64-битный префикс, определяющий подсеть CGA;collCount: 8-битное беззнаковое число, значение которого должно быть 0, 1 или 2;publicKey: открытый ключ в виде структуры SubjectPublicKeyInfo, закодированной по стандарту DER на базе ASN.1;extFields: необязательное поле переменной длины (по умолчанию длина 0).
Дополнительно, параметр безопасности Sec определяет стойкость CGA к атакам перебора. Это 3-битное беззнаковое число, которое может принимать значения от 0 до 7 и кодируется в трёх старших битах идентификатора интерфейса CGA. Чем выше значение Sec, тем выше уровень безопасности, но время генерации CGA также увеличивается. Для удобства промежуточные значения Sec в псевдокоде далее представлены как 8-битные числа, не превышающие 7.
Метод генерации CGA
Следующий фрагмент псевдокода описывает метод генерации CGA, используемый для создания нового криптографически сгенерированного адреса.
1 процедура generateCGA(Sec, subnetPrefix, publicKey, extFields): 2 modifier := random(0x00000000000000000000000000000000, // 16 октетов (128 бит) 3 0xffffffffffffffffffffffffffffffff) 4 5 метка1: 6 concat := конкатенация(modifier, 0x000000000000000000, // 9 нулевых октетов 7 publicKey, extFields) 8 9 digest := SHA1(concat) 10 Hash2 := digest[0:14] // 14×8 = 112 старших битов 11 12 если Sec ≠ 0 и Hash2[0:2*Sec] ≠ 0: // 2*Sec*8 = 16*Sec старших битов 13 modifier := modifier + 1 14 перейти к метка1 15 конец если 16 17 collCount := 0x00 // 8-битный счётчик коллизий 18 19 метка2: 20 concat := конкатенация(modifier, subnetPrefix, collCount, 21 publicKey, extFields) 22 23 digest := SHA1(concat) 24 Hash1 := digest[0:8] // 8×8 = 64 старших битов 25 26 intID := Hash1 // Hash1 становится идентификатором интерфейса... 27 intID[0] := intID[0] побитовое И 0x1c или (Sec << 5) // ...после формирования Sec и битов u/g 28 29 CGA := конкатенация(subnetPrefix, intID) // соединить для формирования CGA 30 31 если duplicate(CGA): 32 collCount := collCount + 1 33 34 если collCount = 3: 35 прервать 36 конец если 37 38 перейти к метка2 39 конец если 40 41 вернуть [CGA, [modifier, subnetPrefix, collCount, publicKey, extFields]] 42 конец процедуры
Идентификатор интерфейса CGA в основном формируется переменной Hash1, которая берётся из первых 64 бит хешированного блока параметров CGA (строки 20–24). В строке 27 три старших бита перезаписываются значением Sec, а зарезервированные биты "u" и "g" (седьмой и восьмой бит) обнуляются.
Параметр Sec реализует расширение хеша, требуя, чтобы первые 16×Sec бит другой хеш-функции Hash2 были равны 0. Этот хеш получается вычислением хеша по блоку параметров CGA с subnetPrefix и collCount, установленными в 0. Для поиска подходящего Hash2 перебирается modifier, увеличиваясь на 1 на каждом шаге (строки 6–15). Чем больше значение Sec, тем больше нулевых бит требуется, и среднее время поиска растёт экспоненциально.
После объединения префикса подсети и сгенерированного идентификатора интерфейса для создания CGA может быть выполнена проверка на дублирование адреса. Если адрес уже используется, счётчик коллизий collCount увеличивается на 1, и идентификатор интерфейса генерируют заново (строки 20–39). Поскольку collCount не используется при вычислении Hash2, при коллизии адреса не требуется искать новый Hash2. По аналогичной причине subnetPrefix также не используется — если префикс подсети меняется, а открытый ключ хоста — нет, тот же modifier может быть повторно использован без пересчёта Hash2.
В строке 41 возвращается сам CGA вместе со структурой CGA Parameters.
Метод проверки CGA
Криптографически сгенерированные адреса применяются для проверки, что полученное подписанное сообщение отправлено именно от хоста, которому назначен этот адрес. Это обеспечивается тем, что связка, использованная для подписи, привязана к CGA. Таким образом достигается верификация публичного ключа, и нет необходимости в централизованной инфраструктуре открытых ключей. Однако, если требуется аутентификация самого хоста, CGA также должен быть аутентифицирован заранее, иначе связанный публичный ключ может быть ненадёжен (если совпадение не подтверждено иными способами).
Метод проверки CGA, при котором публичный ключ проверяется на привязку к CGA, требует передачи в качестве входных данных соответствующей структуры CGA Parameters и может быть реализован следующим образом.
1 процедура verifyCGA(CGA, [modifier, subnetPrefix, collCount, publicKey, extFields]): 2 если collCount > 2 или CGA[0:8] ≠ subnetPrefix: 3 вернуть false 4 конец если 5 6 concat := конкатенация(modifier, subnetPrefix, collCount, 7 publicKey, extFields) 8 9 digest := SHA1(concat) 10 Hash1 := digest[0:8] // 8×8 = 64 старших битов 11 Hash1[0] := Hash1[0] побитовое И 0x1c // игнорировать Sec и u/g биты 12 13 intID := CGA[8:16] // идентификатор интерфейса (64 младших бита) 14 intID[0] := intID[0] побитовое И 0x1c // игнорировать Sec и u/g биты 15 16 если Hash1 ≠ intID: 17 вернуть false 18 конец если 19 20 Sec := CGA[8] >> 5 // извлечь Sec из идентификатора интерфейса 21 22 concat := конкатенация(modifier, 0x000000000000000000, // 9 нулевых октетов 23 publicKey, extFields) 24 25 digest := SHA1(concat) 26 Hash2 := digest[0:14] // 14×8 = 112 старших битов 27 28 если Sec ≠ 0 и Hash2[0:2*Sec] ≠ 0: // 2*Sec*8 = 16*Sec старших битов 29 вернуть false 30 конец если 31 32 вернуть true // проверка успешна 33 конец процедуры
Процедура начинается с проверки корректности значения collCount (стр. 2) и совпадения subnetPrefix из структуры параметров с префиксом CGA — это важная мера безопасности.
В строках 6–18 вычисляется Hash1 по структуре CGA Parameters (включая публичный ключ и префикс подсети), и соответствующие биты сравниваются с идентификатором интерфейса CGA. При сравнении для простоты первые три (Sec) и седьмой-восьмой ("u" и "g") биты обнуляются (стр. 11, 14).
После извлечения Sec из идентификатора интерфейса CGA вычисляется Hash2 и проверяется, что его первые 16×Sec бит равны 0 (стр. 22–30). Если все проверки успешны, публичный ключ считается привязанным к этому CGA.
Безопасность
Чтобы злоумышленник убедил клиентское устройство в валидности сообщения от некоторого чужого CGA, он должен найти коллизию в соответствующих битах Hash1 и Hash2 методом перебора. Если подобран набор параметров CGA (включая такой публичный ключ, для которого известен приватный), что генерирует требуемый CGA, злоумышленник может подменить узел, фактически владеющий этим CGA, незаметно для клиента (исключая случаи, когда клиент ранее получал другой публичный ключ для того же CGA).
Из 64 бит Hash1 для идентификатора интерфейса реально используется только 59 (5 бит перезаписываются). Для CGA при Sec=0 стоимость поиска такого набора параметров составляет примерно (по O-нотации). Увеличение Sec наращивает сложность на до , так как требуется выполнение большего числа условий на нулевую маску Hash2. При генерации CGA это существенно увеличивает время в зависимости от Sec, однако при использовании и верификации CGA сложность операций не растёт.
Поскольку Sec не является частью структуры параметров, а включается напрямую в адрес, злоумышленник не может подменить его на меньшее (например, 0), чтобы упростить подбор по Hash2: это приведёт к другому (несовпадающему) CGA. Если же просто записать "неподходящее" значение Sec в идентификатор, Hash2 почти наверняка не будет содержать нужного количества старших нулевых бит, и проверка провалится.
В процессе генерации CGA вероятность троекратной коллизии адресов чрезвычайно мала. Если дублирование выявляется третий раз, это скорее всего свидетельствует о сбое конфигурации, реализации или о атака типа «отказ в обслуживании». В связи с этим число допустимых вариантов collCount ограничено значениями 0–2. При верификации CGA параметр проверяется на попадание в этот диапазон, чтобы исключить возможность для атакующего перебрать различные значения collCount без пересчёта Hash2.
Включение префикса подсети в вычисление Hash1 позволяет предотвратить атаки с использованием готовых таблиц (баз данных) для разных подсетей: атакующий не может использовать одну подготовленную базу для взлома адресов с разными префиксами. Кроме того, проверяющий может однозначно убедиться в привязке публичного ключа к конкретному адресу (а не к другому адресу с тем же идентификатором узла, но другим префиксом), так как спецификация CGA требует использовать префикс из структуры параметров для всех хеш-операций, и при верификации обязательно проверяется совпадение префикса и самого CGA.