Ресурсы: техническое описание TLS, LaTeX - в картинки (img), криптографическая библиотека Arduino, шифр "Кузнечик" на ассемблере AMD64/AVX и ARM64
Посмотрим на совсем небольшой фрагмент кода.
while(1){
GP0 = 0;
GP0 = 1;
}
Этот код предназначен для микроконтроллера PIC12F675 (один из очень популярных и очень простых микроконтроллеров) и, можно считать, написан на C (но здесь это не важно). Некоторые пояснения для тех, кто с микроконтроллерами дела не имел: микроконтроллеры предназначены для управления прочей электроникой и электротехникой, а GP0 здесь – это такой “синтаксический сахар”, а именно – способ задать логическое значение на выводе микроконтроллера при помощи специальной, зарезервированной переменной. PIC12F675 имеет несколько подходящих выводов, все они пронумерованы: GP0..GP5 (GP – это от General Purpose). Строчкам кода, приведённым выше, ещё предшествует несколько строк, настраивающих начальное состояние микроконтроллера и задающих режим работы, это здесь тоже не важно, поскольку речь пойдёт о другом.
Итак, GP0 = 0 – “записывает” в нулевой выход логический ноль, GP0 = 1 – “записывает” логическую единицу. Так что это не совсем “запись переменной”. Вариант while(1) – не менее типовой для мира микроконтроллеров способ задать бесконечный цикл. У микроконтроллера нет развесистой операционной системы. В алгоритмическом смысле, микроконтроллеры, обычно, либо спят, либо работают в бесконечном цикле while(1), иногда – то есть, очень редко, – отвлекаясь на обработку прерываний (в данном случае, считаем, что никаких прерываний и прочих неожиданностей не происходит).
Получается, что приведённый фрагмент последовательно выполняет запись ноля и единицы в логический вывод. Какой сигнал образуется на этом выводе? Пусть логические уровни задаются напряжением 0 вольт – для ноля и +5 вольт – для единицы (на практике, понятно, используется отсечение по порогу, но, опять же, это детали из области микроэлектроники). Нетрудно предположить, что некоторые предложения языка высокого уровня C аппаратурой выполняются пошагово, а значит, на выводе с номером ноль должен появиться ноль, а потом единица, а потом опять ноль, опять единица и так далее. Продолжительность импульса – ключевой момент, о нём как раз и пойдёт речь.
Обычно, даже не знакомые с аппаратурой и языками ассемблера разработчики быстро догадываются, что, пусть в исходном коде ничего про это не написано, но какая-то продолжительность переключения всё же будет наблюдаться. Если считать, что на переключение, описанное в коде, затрачивается одинаковое время, то, подключив осциллограф для вывода развёртки по времени, сможем наблюдать набор “прямоугольных” импульсов одинаковой продолжительности (“ширины”), которые разделены “ямами” той же продолжительности (соответствует состоянию “ноль”). Такой “прямоугольный” сигнал называют “меандром”.
Однако, если скомпилировать код, прошить программу в микроконтроллер, запустить его и подключить осциллограф к нужному выводу, то картина получится несколько иная, меандр не очень-то сбалансирован. Вот результат на фото ниже.

(Здесь я использовал компилятор Microchip MPLAB XC8 версии 2.46, в бесплатном варианте.) Единицы получились в четыре раза длиннее нолей (здесь положительное направление, как обычно, вверх). Как так вышло? Вообще, “чисто структурное” понимание исходного кода подразумевает, что while(1) лишь задаёт бесконечный цикл – это доступно компилятору, а как там работают остальные детали, какой алгоритм задуман – компилятор учитывать не может. То есть, текст на ЯВУ задаёт лишь схему итераций. Это логично, но иногда нужно знать ассемблер и то, как работает аппаратура. В данном случае, происходит следующее.
Микроконтроллер работает со скоростью миллион программных тактов в секунду. Компилятор превращает цикл с двумя присваиваниями в некоторый набор кодов команд микроконтроллера. Реализация цикла должна использовать команду безусловного перехода, которая тоже потребляет такты. При этом начало цикла содержит дополнительную команду, задающую способ адресации регистров. В итоге, получается следующий набор машинных команд, которые, в результате прошивки, выполняет микроконтроллер (псевдокод, соответствующий ассемблеру; здесь loop – это метка, по которой происходит передача управления каждый раз в конце тела цикла):
loop: STATUS 5 // выбор режима адресации, один такт; SET_BIT_GP0 0 // установка бита вывода GP0 в ноль, один такт; SET_BIT_GP0 1 // установка бита вывода GP0 в единицу, один такт; GOTO loop // переход к началу цикла, два такта;
В данном микроконтроллере GOTO (безусловный переход) занимает два программных такта, а остальные упомянутые команды – по одному. Это означает, что в цикле, между переключениями статуса вывода, будет четыре такта. Здесь перед последней командой (GOTO) вывод установлен в единицу (SET_BIT_GP0 1). Посчитаем, начиная с команды, устанавливающей единицу, такты: 1 (SET_BIT_GP0) + 2 (GOTO) + 1 (STATUS) == 4 такта. После чего вывод устанавливается в ноль на один такт. Интерпретация полностью соответствует картинке с осциллографа. Ниже, для сравнения, соответствующий фрагмент кода уже на ассемблере PIC. Точно в такой код преобразует исходную конструкцию с while(1) компилятор. Для задания состояния логического вывода микроконтроллер использует один бит в специальном регистре, поэтому тут выведены команды, устанавливающие (bsf) и сбрасывающие (bcf) биты.
mainloop: // while(...) bcf status, 5 // выбор режима адресации; bcf (5), 0 // GP0 = 0 (clear bit); bsf (5), 0 // GP0 = 1 (set bit); goto mainloop // while(1);
Получается, вывод компилятора тут не соответствовал ожиданиям при элементарном подходе. Но, конечно, догадаться о том, что реализация цикла тоже требует некоторых команд – не так трудно. Вообще же, текст программы ничего не говорит об организации цикла: while(1){…} только описывает его границы, это чисто структурный элемент, если воспринимать реализацию на высоком уровне – на языке высокого уровня.
Небольшое “лирическое отступление”. Автоматическая оптимизация циклов компиляторами является хрестоматийным примером неверной интерпретации того, как “программы работают”: многие разработчики на языках высокого уровня, – даже опытные, – пытаясь измерять производительность реализации той или иной функции, начинают с того, что вызывают функцию повторно в цикле, фиксируя время начала и время окончания, – лишь для того, чтобы по результатам измерений обнаружить, что функция “исполняется” практически мгновенно, поскольку оптимизирующий компилятор просто выкинул её вызов из результирующего кода, так как выяснилось, что вывод функции в программе ни на что не влияет (а что касается переменной счётчика цикла, то тут всё заменяется на единственное присваивание – в переменную записывается окончательное значение, которое можно определить на этапе компиляции). Интересно, кстати, что конкретно в C есть и достаточно много неопределённостей на уровне языка, связанных с обработкой присваиваний значений переменным, но наш пример – это не тот случай, поскольку здесь диалект C использует не-совсем-переменные для вывода электрических значений на контакты.
Вернёмся к рисованию меандра программой для микроконтроллера. Хотелось бы получить “ровный” меандр. На языке C для PIC в нашем конкретном случае это можно сделать так.
while(1){
GPIO = GPIO^0x01;
}
Здесь присваивание значений конкретным выводам заменено на XOR для всего регистра. GPIO – это псевдоним, обозначающий тот самый специальный регистр (массив битов), биты которого соответствуют конкретным логическим выводам. В нашем случае – переключается нулевой вывод (GP0), поэтому изменяется младший бит (бит с индексом нуль, этому соответствует значение маски – 0x01, единица). Логика, стоящая за данным решением, следующая: нужно исключить из тела цикла двойственность состояний. А именно – XOR переключает бит: если там был ноль, то будет единица, если была единица, то станет ноль; тут больше нет записи двух разных значений. Такой вариант кода гораздо лучше соответствует задаче, если бы задачей было получение “ровного, сбалансированного” меандра (заметьте, впрочем, что изначальная задача этой записки – другая: определить, что выводится микроконтроллером и, главное, почему).
Меандр “сбалансировался”, а результат, демонстрируемый осциллографом, поменялся, что и видно на фото экрана ниже.

Однако длительность каждого импульса теперь равна шести тактам. Почему? Потому что компилятор дописал новых команд, опасаясь за целостность общей логики кода: значения в регистрах микроконтроллера могут поменяться вне зависимости от работы АЛУ (арифметико-логического устройства). И это только нам известно, что подобные изменения не предусмотрены ни схемой включения, ни настройками микроконтроллера, а вот компилятор про это догадаться не может, поскольку компилятор не видит сам алгоритм.
Если PIC-ассемблер взять, что называется, в руки, то данный цикл можно оптимизировать. Во-первых, команду выбора схемы адресации можно вынести за пределы цикла, так как выбор зафиксирован. Это сэкономит один такт. Во-вторых, компилятор здесь записывает предложение “GPIO = GPIO^0x01” в три команды: загружает значение специального регистра GPIO в рабочий регистр (в PIC-микроконтроллерах он называется W), выполняет XOR и записывает значение в GPIO. Но нужный XOR можно сделать в одну команду, загрузив заранее, до начала цикла, маску (единицу) в рабочий регистр W и выполняя XOR непосредственно с GPIO. Это позволит сэкономить ещё два такта. А вот два такта, нужных на goto – так сэкономить уже не выйдет (можно, наверное, придумать особенно экзотические методы, но это на общий смысл не повлияет). Естественно, всё это возможно только потому, что задача сводится к преобразованию цикла записи состояний в значение одного логического вывода микроконтроллера, но в итоге – можно добиться трёх тактов на цикл. Соответствующий фрагмент ассемблерного кода приведён ниже.
bcf status, 5
movlw 0x01
mainloop:
xorwf (5), 1
goto mainloop
Теперь импульсы стали короче в два раза, что и подтверждается очередной фотографией экрана осциллографа.

Конечно, реальная ситуация может быть сложнее, а компилятор может “догадаться” о какой-то лучшей оптимизации. Если рассматривать современные мощные микропроцессоры, то там, например, вообще сложно судить о реальном количестве тактов, затрачиваемых той или иной машинной командой, даже на уровне ассемблера. Ассемблер сейчас не всегда нужен, даже если нужно программировать микроконтроллеры, но знакомство с ассемблерами и принципами работы той аппаратуры, на которой программа будет исполняться, часто помогает осознать причины несоответствия ожиданий тексту программы на языке высокого уровня.
Комментировать »
Очередное “ИИ решает математические задачи”, в этот раз – от Google и про олимпиадные задачи, но, хотя бы, есть уточнения про ограничения. В исходном сообщении сказано, что “AI achieves silver-medal standard solving International Mathematical Olympiad problems” (“ИИ достиг уровня серебряной медали, решая задачи Международной математической олимпиады”).
Если прочитать сообщение внимательно, то оказывается, что, на первом шаге, задачи были вручную переформулированы в виде предложений на формальном языке, который “подходит для системы”. Буквально: “First, the problems were manually translated into formal mathematical language for our systems to understand” – “Прежде всего, задачи были вручную переведены на формальный математический язык, чтобы наши системы их поняли”. То есть, данный ИИ, – который, как теперь напишут в СМИ (не Google), “демонстрирует уровень серебряного медалиста Международной математической олимпиады”, – даже не может прочитать задачи в том виде, как они представлены для людей (я, собственно, давно привожу этот момент в качестве примера).
Под “формальным математическим языком”, судя по всему, имеется в виду язык системы компьютерных доказательств Lean. То есть, речь, конечно, не про то, что задачу просто “записали строго”: исходные формулировки и так достаточно строгие. Заметьте, что корректный перевод исходного текста задачи на тот или иной формальный язык требует достаточно высокой квалификации и, обычно, примерного понимания сути самой задачи. Рассматриваемой системе ИИ этот шаг, очевидно, недоступен.
На втором шаге ИИ-нейросеть генерировала тексты, представляющие собой запись на формальном языке “доказательства” для поставленной задачи, а то, что получилось, автоматически проверяла при помощи системы компьютерных доказательств Lean, после чего “вносила исправления”. Это называется оптимизированный перебор. Или, так как происходит поиск с дополнительными условиями по всему пространству возможных текстов “программ-доказательств” для Lean, можно сказать, что это “форсированный поиск” по тем “веткам”, которые, предположительно, ведут к корректному “доказательству”. Под “корректным” тут подразумевается такой текст программы, который будет принят системой доказательств Lean – то есть, она не выдаст сообщений об ошибках. Это, конечно, никакой не поиск решения олимпиадной задачи в математическом смысле – это поиск текста, парного к тексту формулировки задачи, если в качестве связующей системы используется Lean.
Да, метод перебора является одним из корректных методов математического доказательства, но здесь имеется в виду перебор некоторых вариантов и наборов данных, а не подбор текстов с записью решения. Нельзя сказать, что успешная оптимизация перебора для поиска формальных записей “доказательств” каких-то простых по формулировке задач не является полезным достижением. Не понятно, почему это преподносится как математическая деятельность ИИ и как шаг на пути к “универсальному искусственному интеллекту” (AGI). При этом, как написано, на подбор текста для некоторых из решённых задач система ИИ тратила до трёх дней (!). Другими словами: сверхмощный специализированный компьютер три дня подбирал даже не решение, а текст программы, который представляет собой запись “доказательства” на некотором формальном языке.
Комментарии (1) »
С “червём” CrowdStrike, конечно, странная история, но не менее странно и то, что уж в корпоративной-то Windows-среде есть все штатные способы настроить тестовую зону для раскатки обновлений. То есть, обновления должны сперва локально раздаваться только на некоторые машины, где отдельная служба может проверить, что оно не сломало всё, куда дотянулось, а только потом, когда некоторая уверенность появилась, обновления можно попробовать применять дальше. Наверное, с CrowdStrike ситуация другая. Наверное, можно так устроить программу-агента с центральным управлением, что она сломается только после того, как расползётся по достаточному количеству компьютеров.
Да, понятно, что описанный способ администрирования, с контролируемым изменением системного ПО, как и многие смежные технологии, это сейчас скорее из области теории, поскольку на практике, когда у вас есть внешний “непогрешимый ИИ”, управляющий “безопасностью ИТ-решений”, то этот ИИ может сам центрально раздать любой код на любые машины, где работают его “пробники”, поскольку windows-операторы свою часть уже сделали в самом начале: “прокликали Next->Next->Next до Finish”.
(Касается, кстати, и мониторинговых решений с агентами для линукс-систем. Тут, конечно, ситуация получше, но, вообще-то, в корпоративных средах предпочитают расставить на все машины, до которых удалось дотянуться, предположим, Zabbix-агент, и не задумываться о том, что это за программа и каковы её возможности.)
Комментировать »
В статье Situational Awareness, которую я недавно упоминал, уровень “интеллекта” LLM GPT-4 (ChatGPT) неоднократно обозначен как соответствующий продвинутому старшему школьнику (smart high schooler). Появился свежий препринт (Vision Language Models are blind) на эту же тему, в котором, впрочем, выясняют, что самые продвинутые современные системы LLM – точнее, VLM, в том числе, GPT-4o, – не могут справиться с элементарными задачками, которые под силу и не особо продвинутому дошкольнику.
Впрочем, все эти задачи связаны с “картинками”, но именно поэтому в качестве предмета исследования выбраны те ИИ-системы, которые заявлены для работы с “визуальными” материалами. То есть, тут нельзя сказать, что данные системы “не имеют возможности видеть”, использовав распространённый манипулятивный способ перевода реальных проблем “интеллекта” LLM (например), возникающих с самыми элементарными задачами, в разряд “ограничений” самой технологии.
Между тем, как указано в работе, эти ИИ-системы не могут верно определить количество пересечений для двух ломаных – см. скриншот с примером из исходной работы ниже.

Не справляются представленные ИИ-системы и с другими элементарными задачами: определить выделенную букву в слове, посчитать количество окружностей, определить, пересекаются ли круги, и т.д., и т.п.
Понятно, что речь тут может идти только о стопроцентном результате, когда верно указаны все ответы. Для “продвинутого старшего школьника” подсчёт точек пересечения линий на подобной картинке – более чем элементарная задача. Угадывание же при помощи оптимизированного перебора, которое выдают за “думание” для данных систем, тут не производит никакого впечатления. Лучший результат, который приводят в работе, это угадывание в ~77% случаев, продемонстрированное VLM Sonnet-3.5; GPT-4o – лишь около 49%, при этом, по формулировке задачи, случайный выбор ответа, без учёта картинки, дал бы одну треть верно угаданных результатов.
Так что, когда следующий раз в СМИ встретится очередная восторженная публикация о том, что “новая LLM на нейросети успешно сдала экзамены в вуз”, можно смело задуматься о том, смогла ли эта LLM хотя бы заполнить регистрационную анкету и вписать ответы в нужные “окошки”.
Комментировать »
Практический пример того, как автоматизация процессов работы с исходным кодом и репозиториями (читать нужно: CI/CD) может легко и неожиданно “выйти боком”: токен с правами полного доступа к репозиториям Python и PyPI на GitHub долгое время находился в открытом доступе.
Разбор инцидента из первых рук позволяет понять, как так вышло: наружу отправился локальный файл .pyc, случайно оставшийся в локальной (общей для процесса сборки) директории, которая требовалась для работы приложения в Docker-контейнере; ну и не менее случайно – в этом .pyc-файле сохранился токен доступа.
Комментировать »
Книга Эдмунда Беркли (Edmund Berkeley) Giant Brains, or Machines That Think (дословно: “Огромные мозги, или Машины, которые думают”, англ.; полный текст доступен по ссылке) вышла в 1949 году, 75 лет назад. В ней рассказано о базовых технических принципах, позволяющих строить компьютеры, а также о нескольких конкретных “думающих” “машинах” того времени, в том числе, про аналоговые вычислители.
Несмотря на название, “хайпа” в книге совсем нет. Напротив, автор ещё в предисловии отмечает, что некоторые его обобщения, – особенно, относящиеся к трактовке понятия “думания”, – могут быть спорными. Основную часть высказанных в этой книге утверждений про машины переписывают и сегодня, но уже безапелляционно и в канве современного “ИИ-хайпа”, а точнее – всех этих LLM/GPT/AI, успешно сдающих студенческие тесты по психологии.
Так, в книге дана та же трактовка способности складывать числа, как признака интеллекта, которую и сейчас постоянно используют. Беркли в данной книге относит к признакам “думания” (think) способность сложить в уме два числа, 12 и 8, получив число 20. При этом описывается, как мог бы складывать числа в уме человек – дописывание позиций к начальному значению (см. ниже). В качестве иллюстрации утверждается, что если бы нашлась, например, лошадь, которая смогла бы складывать числа и выдавать ответы, то такую лошадь люди непременно бы посчитали способной думать (к сожалению, не объясняется, почему говорящая лошадь, не умеющая выдавать ответы на арифметические вопросы, должна быть объявлена не-думающей; это, между тем, очень близко к типичной современной трактовке работы LLM, которые выдают текст на естественном языке).
Тут особенно интересно следующее. Автор сначала описывает “процесс думания”, осуществляемый человеком, складывающим числа: вот, мол, этот человек использует свой разум для того, чтобы отсчитать восемь позиций, начиная от двенадцатой, и прийти к числу двадцать. То есть, казалось бы, тут почти уже охвачен действительно важный, определяющий момент – возможность описать процесс счёта для внешнего наблюдателя, снабдив описание представлением о числах и отделив сам процесс от конкретных, “механических” значений. К сожалению, этот момент в книге тут же теряется, а вместо него, в сопровождении дополнительных аспектов условного ветвления и поиска по индексу (тьюринг-полнота, видимо), развивается небогатое обратное утверждение: мол, если нечто, – даже не говорящая лошадь, а механический калькулятор, – может вывести сумму 12 и 8, то это нечто тоже думает. Естественно, дальше в книге подчёркивается, что речь,- пока что, по состоянию на 1949 год, – не идёт об “интуитивном мышлении”, но в дальнейшем возможна и его реализация. То есть, ещё одна яркая параллель с современными популярными статьями про AGI (универсальный ИИ). Но, напомню, всё опубликовано в 1949 году.
(Вообще, то, насколько по-разному люди считают в уме, как раз является весьма богатым направлением. Важен сам используемый процесс осознаваемого представления, о котором человек может рассказать: оказывается, кто-то представляет себе ленту с ячейками (машина Тьюринга?), кто-то – счётные палочки, а кто-то – таблицу, нарисованную на воображаемом бумажном листе.)
В книге, кстати, определены и детально описаны многие современные разумные способы применения машин: каталогизация данных, поиск в библиотеках, распознавание образов, распознавание рукописного текста, голосовой ввод текста и т.д. Это то, чего часто не хватает современным “хайп-публикациям” про ИИ.
Комментировать »
Кстати, один из свежих примеров текстов, восхваляющих и превозносящих “достижения LLM/GPT”, с точки зрения “интеллекта”: Situational Awareness. Использование слов “восхваляющих” и “превозносящих” тут вовсе не является преувеличением, так как в тексте по ссылке много раз повторяется, что системы LLM/GPT уже успешно решают сложные математические задачи, а также могут рассуждать (reason) и мыслить (think). Сам же тот текст про то, что необходимо немедленно засекретить, ограничить и разграничить понятные узкому кругу лиц технологии ИИ на уровне правительства Штатов. Так что основную часть мы пропускаем, а коснёмся только некоторых занимательных оценок и прогнозов. Пусть прогнозы в Situational Awareness строятся методом экстраполяции графика с помощью линейки, но эти прогнозы, хотя бы, даны с конкретными датами, по годам.
Так, уже в самом начале статьи (на третьей странице) обещают к 2025/26 годам ИИ, превосходящий “выпускника колледжа” (шкала, конечно, традиционная для этой области технологий массовой информации; но, всё же, превзойти “выпускника колледжа” – что бы это значило?). 2025 год – это следующий год. Конечно, под определение “уровня выпускника колледжа” нетрудно подвести очередное успешно принятое комиссией эссе, но на той же странице сказано, что эксперты (pundits) “мейнстрима” всё ещё слепо рассуждают о том, будто данные программы ИИ “всего лишь предсказывают следующее слово”, и лишь немногие знают, что там “на самом деле” (новых деталей, впрочем, не приводится).
К концу текущего десятилетия (а это тот самый, знаменитейший, 2030 год) обещают уже “суперинтеллект” (superintelligence). Занятно, что в ряду характеристик, определяющих качественное превосходство “суперинтеллекта”, построенного на сотнях миллионов видеопроцессоров, приводится и такая: “суперинтеллект” сможет писать “триллионы строк кода (программного)”. Почему суперуровень интеллекта определяется количеством строк кода – не объяснено, но тут же указано, что код этот будет слишком сложным для понимания человеком. Человек, дескать, этот код не сможет понять, даже если ИИ потратит десятилетия на объяснения. Неожиданный поворот.
То есть, казалось бы, человек просто не успеет прочитать “триллионы строк” кода за десять лет. Хорошо. Тут и компилятору-то на многоядерном ЦПУ придётся поднапрячься. Зато весьма серьёзным проявлением “суперинтеллекта” можно было бы признать способность объяснить человеку эти “триллионы строк”. Но нет – почему-то в признаки “суперинтеллекта” ставится обратный эффект: именно невозможность объяснить, чего оно тут такого нагенерировало в триллионах. Может, там где-то и секретный сонет Шекспира затесался, в комментариях. Конечно, ведь под “триллионы строк” непонятного для человека кода можно замаскировать что угодно. (Кстати, проблема понимания программного кода, похоже, настолько глубокая, что именно она и является причиной возникновения знаменитой задачи P≟NP.)
Для чего может быть нужен непонятный код в таких несметных количествах строк? Почему “много кода”, который не может объяснить даже “суперинтеллект”, лучше короткой и понятной программы? Видимо, различные ответы на эти вопросы и определяют степень “понимания” реального положения дел с технологиями ИИ. Возможно, ещё посмотрим, где ИИ/LLM применят в 2030 году. А что обязательно применят, если останутся компьютеры, – в этом-то сомневаться не приходится.
Комментировать »
Если (вдруг) появился универсальный “квантовый компьютер” нужной мощности, то как именно будут выглядеть технические шаги по раскрытию ранее записанного TLS-трафика? Под “квантовым компьютером” здесь подразумевается аппарат, выполняющий алгоритм Шора. А в роли взламываемой криптосистемы выступает алгоритм Диффи-Хеллмана на эллиптической кривой (ECDH – типовой алгоритм для современного состояния TLS). Шаги потребуются следующие.
1.
В записи трафика присутствуют начальные TLS-сообщения, обеспечивающие распределение общего секрета. Если таких сообщений нет, то раскрыть трафик рассматриваемым способом – не выйдет. Выделить начальные сообщения нетрудно: они начинаются с байтового префикса с зафиксированным значением и имеют строгую структуру. В трафике всегда остаются ключи, согласованные сторонами по протоколу Диффи-Хеллмана (равно как и с использованием RSA или другого метода инкапсуляции). Я писал об этом ранее. Заметьте, что записываются и ключи, согласованные с использованием современных криптосистем, обладающих постквантовой стойкостью. Просто, считается, что их сложнее восстановить даже с использованием гипотетического квантового компьютера.
Из начального сообщения клиента выделяется блок с открытой частью клиентского обмена ECDH. Например, если используется ECDH на кривой P-256, то в специальном и легко обнаруживаемом блоке данных будет содержаться пара координат точки P на кривой P-256, представляющей собой открытую часть обмена ECDH. Это 64 байта (плюс дополнительные байты с указанием на используемый формат), то есть, два блока по 32 байта: координаты X и Y.
2.
Клиентская часть ECDH представляет собой результат умножения базовой точки на секретный скаляр – значение скаляра и есть секретная часть ECDH клиента: это натуральное число n. Открытая часть ECDH, точка P, это результат [n]G, где G – точка-генератор, заданная в параметрах криптосистемы.
Полученные координаты точки P “загружаются” в квантовый компьютер, который уже должен быть настроен на параметры соответствующей криптосистемы, то есть, содержать схему, реализующую алгоритм Шора (нахождения периода функции) для подгруппы кривой P-256, образованной точкой G. Как именно загружаются значения в квантовый компьютер – пока что не известно, поскольку никаких практических квантовых компьютеров создано пока что не было (ну или, если хотите, не было опубликовано их содержательных описаний). Вероятно, у этого компьютера должен быть некоторый классический интерфейс, воспринимающий значения байтов координат точек, и переходные схемы, которые преобразуют эти значения в коммутацию и состояния квантовых “гейтов/триггеров” и кубитов. Неизвестно даже, сколько этих “кубитов и гейтов” будет нужно: несколько миллионов или несколько триллионов. Что уж там говорить про схемы коммутации.
(Заметьте, впрочем, что так как задействованный математический аппарат квантовой механики манипулирует комплексными числами – или, что эквивалентно, синусами и косинусами, – то все открытые тексты и решения можно уместить в пространстве состояний одного “непрерывного” кубита. Континуум, свернувшийся окружностью за привычным синусом, позволяет это легко проделать. Ещё и места останется, как минимум, в два раза больше, чем займут все решения всех проблем со всеми возможными шифрами в степени всех ключей. Это, впрочем, другая история.)
После того, как квантовый компьютер настроен, запускается итерация квантовых вычислений. Если итерация успешна, то результат позволяет быстро вычислить n на классическом компьютере. Использование классического компьютера тут означает, что у компьютера квантового, – а он, даже если создан, то бесполезен без классического, – есть и другой интерфейс, который выводит итоговое измеренное состояние квантовых схем в виде набора байтов, интерпретируемого как целое число. В случае с ECDH это будет пара чисел, по которым классический компьютер быстро вычислит секретный клиентский параметр n.
Аналогичная схема сработает и для открытого параметра ECDH, который отправил сервер. Для раскрытия ключей защиты трафика – достаточно знать один из секретов: клиентский или серверный.
3.
Общий секрет в ECDH образуется путём умножения точки-генератора на произведение секретов сторон. В рассмотренном только что случае, достаточно умножить открытый параметр DH сервера Q на секрет клиента, вот так: [n]Q (именно эту операцию проделывает клиент в ходе штатного процесса установления TLS-соединения). Секрет клиента, как мы только что выяснили, был раскрыт при помощи квантового компьютера. Результат умножения даёт точку на кривой, координата X которой используется в качестве задающего значения для генерирования секретных симметричных сеансовых ключей защиты трафика TLS. Функция, генерирующая сеансовые ключи, известна. Так как защищённые записи содержат коды аутентификации, сторона, взломавшая секрет ECDH, может тут же легко проверить, что найденный секрет соответствует записанному трафику. Симметричные ключи позволяют расшифровать трафик сеанса, используя шифр, номер которого тоже записан в начальных сообщениях: эти сообщения как раз и нужны для того, чтобы, кроме прочего, стороны согласовали используемый шифр.
*
Несомненно, прогресс в разработке систем “квантовых вычислений” есть. Однако вот уже несколько лет существенная часть прогресса сосредоточена на генерировании хайпа, поэтому даже до минимально практических схем не только далеко, но даже и не ясно, возможны ли такие схемы в принципе. В описанной выше схеме квантовый компьютер используется в роли “оракула”. Так же будет происходить и на практике: в рассматриваемой схеме для работы квантового компьютера не нужны какие-то дополнительные байты TLS-трафика, какие-то шифры или, условно, “части ключей”. С тем же успехом секретный параметр Диффи-Хеллмана можно просто угадать: запись трафика позволяет однозначно определить правильность угаданного значения.
Комментировать »
Очередное подтверждение того, что “диагонализация” неустранима для сколь-нибудь сложного процессора: типовая манипуляция (вряд ли эту схему нужно считать уязвимостью или атакой) под названием TIKTAG, раскрывающая “соседние” метки указателей на процессорах ARM при помощи перебора, подкреплённого измерением состояния кеша. (Кстати, метод логически совпадает с некоторыми атаками из области TLS.) Я не так давно писал, что архитектура современных процессоров на самом базовом уровне гарантирует, что подобные манипуляции возможны:
Базовая логика таких атак – использование “оракулов”, сигналы которых пробивают границы, устанавливаемые (формально) аппаратной архитектурой процессора для фрагментов программного кода. Тут нужно учитывать, что границы эти только декларируются, а в реальности – сигналы через них проходят (иначе тут не было бы предмета), потому что процессору необходимо оптимизировать исполнение кода и доступ к памяти.
Комментировать »
В продолжение предыдущей записки, про имена и TLS на веб-серверах, – хороший практический пример. Веб-сервер Nginx использует, обычно, OpenSSL в качестве библиотеки, реализующей TLS. Из-за особенностей кода и библиотечных вызовов, данный веб-сервер не может применять ограничения по версиям TLS для виртуальных хостов, сконфигурированных по SNI в блоках server. Это буквально происходит потому, что не согласованы области видимости имён и статусы TLS-соединения. Другими словами: для того, чтобы начать сопоставлять описания TLS-конфигураций из блоков server, Nginx должен знать имя хоста, по которому выбирается блок. Данное имя передаётся в ClientHello (SNI). Однако к моменту, когда Nginx это имя получает через соответствующий (обратный) вызов функции, в OpenSSL уже создан контекст с поддерживаемой версией TLS – ведь эта версия и выбирается по ClientHello.
Что это означает? Например, означает, что с именами в TLS нужно тщательно всё проверить. Так, если TLS-клиент (браузер) приходит с указанием поддерживаемой версии TLS 1.3 и такая версия указана для сервера по умолчанию, то отключить эту версию директивами ssl_protocol для прочих виртуальных хостов не выйдет – будет согласовываться TLS 1.3, а если согласовать 1.3 не получится, то возникнет ошибка. При этом сама директива ssl_protocol допускается в блоке server, однако в документации её низкая избирательность и, таким образом, относительная бесполезность, тоже отражены: указывать ssl_protocol нужно только для сервера по умолчанию (default/default_server).
Предположим, что для сервера Nginx указана полезная опция ssl_reject_handshake в блоке виртуального хоста по умолчанию (default_server). Это предотвращает успешные TLS-подключения с неверными именами. Соответственно, если в такой конфигурации попытаться указать для прочих виртуальных хостов какие-то иные наборы допустимых версий TLS в ssl_protocol, то применяться они не будут: сервер всегда раньше упрётся в default_server, хоть для этого варианта и прямо указана отмена TLS-хендшейка, не указано никаких уникальных имён хостов и, предположим, даже, – как обычно, – не указана директива ssl_protocol, которая будет инициализирована значениями по умолчанию. Это, конечно, выглядит не слишком логично, но происходит потому, что данный веб-сервер и не пытается обрабатывать имена из контекста TLS-сессии, “выгружая” задачу в сторону TLS-библиотеки (OpenSSL – см. выше).
Вернёмся к конкретной версии TLS – к 1.3. Если при наличии default_server с ssl_reject_handshake в конфигурации присутствуют виртуальные хосты, для которых поддержка 1.3 невозможна (например, с ГОСТ-TLS без совместимых шифронаборов), то, даже если в блоке server для этих имён прямо указаны только версии ниже 1.3, всё равно возникнет довольно занимательная проблема: некоторые клиенты-браузеры смогут подключаться к серверу по проблемному имени хоста, а некоторые – нет. Потому что с клиентом, который заявляет в ClientHello поддержку 1.3, сервер всё равно будет пытаться установить соединение 1.3 и, определив недоступность этой версии, возвратит ошибку (Alert в терминах TLS). А с клиентом, который 1.3 не заявляет, соединение будет успешно и без всяких проблем устанавливаться по версии 1.2 (например). Всё по причине игнорирования блока ssl_protocol веб-сервером. Решением является отключение 1.3 для сервера по умолчанию, то есть – для всех имён хостов сразу.
Комментировать »
В TLS для веба важны имена. Например, при подключении по TLS для HTTPS серверу нужно отправить клиенту TLS-сертификат сервера. Если серверных сертификатов несколько, то сервер должен как-то выбрать подходящий по имени. Тут речь не про промежуточные сертификаты, которые добавляются в набор (bundle), а именно про оконечные сертификаты. Если TLS-cертификат не соответствует имени, которое ожидает клиент-браузер, то сессия не будет корректной на стороне клиента. Данный момент, вместе с желанием держать много виртуальных хостов на одном IP-адресе и под одним веб-сервером, является постоянным источником ошибок администрирования.
Посмотрим, что вообще происходит в ходе обработки HTTPS-сеанса и как сервер может определить соответствие имён. В начале TLS-сессии клиент (браузер) обычно передаёт имя сервера в специальном блоке данных – SNI (Server Name Indication). Сервер видит это имя и может использовать его для поиска нужного сертификата и нужного виртуального хоста. Однако клиент определяет имя из своих собственных соображений.
Типовой вариант: в контекст TLS-сессии имя узла SNI приходит из запроса пользователя через DNS. Почему “через DNS”? Потому что браузер должен был найти IP-адрес, к которому он подключается по TCP. При прочих равных, браузер получил IP-адрес из DNS по имени хоста из URL, заданного пользователем. Это действительно важный момент, который, почему-то, инженеры DevOps регулярно выкидывают из области рассмотрения, посчитав “элементарным”. Однако, во-первых, если пользователь ввёл имя, которое не удалось “разрезолвить” в IP-адрес, то и штатного TLS-соединения не случится, хоть бы такое имя и было указано в сертификате (такое вполне возможно). Это, предположим, “элементарная часть”, которая, тем не менее, никак не отменяет возможности указания в SNI имени без DNS-адреса, то есть, без A-, AAAA-записей. Во-вторых, кто угодно может настроить какое угодно допустимое DNS-имя так, чтобы оно показывало на заданный IP-адрес сервера, а потом отправить ссылки с этими именем хоста ничего не подозревающим пользователям. В этом случае, пользователь, штатно набрав это самое “какое угодно” имя хоста, успешно направит браузер на заданный сервер, но браузер обратится туда с “неожиданным” именем в SNI. Если это имя вдруг совпадёт с указанным в предоставленном по умолчанию валидном TLS-сертификате с успешным подтверждением, то браузер даже не выдаст предупреждения, а в сертификате может быть очень много разных имён. (Но, во многих случаях, можно смело считать валидный серверный TLS-сертификат с верным именем достаточной мерой защиты от “ложных” хостов для HTTPS. Особенно, если веб-сервер основного имени хоста доступен по выделенному IP-адресу.)
Основной вывод предыдущего абзаца такой: сервер ориентируется на SNI, но нельзя считать, что SNI, присланное клиентом, как-то привязано к серверным настройкам, соответствует DNS, “защищено сертификатом” и вообще укладывается в формат и ожидания, отражённые в конфигурационном файле веб-сервера. И SNI вообще может отсутствовать. (Кроме того, тут не рассматривается случай, когда данные SNI скрываются от третьей стороны – метод ECH и пр.) На стороне веб-сервера бывает возможность отклонить TLS-соединение, использующее “неожиданное” имя хоста.
На начальном этапе соединения, до получения HTTP-запроса, сервер видит только имя из сеанса TLS. Но это не означает, что и сами HTTP-запросы будут адресованы этому же имени хоста – в HTTP-запрос тоже можно вписать что угодно, TLS никак семантику HTTP не фиксирует. Конечно, ситуация не является типичной для браузеров, но она вполне возможна. Это тоже нужно учитывать при обработке разных внутренних переменных на серверной стороне: контекст TLS, контекст HTTP – разные контексты.
Комментировать »
Новый