Ресурсы: техническое описание TLS, LaTeX - в картинки (img), криптографическая библиотека Arduino, шифр "Кузнечик" на ассемблере AMD64/AVX и ARM64
Предположим, имеется TLS-сертификат на веб-сайте, – то есть, выпущенный для доменного имени, – и соответствующий секретный ключ. Можно ли использовать этот секретный ключ для подписывания каких-то произвольных файлов, например, текстовых сообщений?
Да, это возможно. Технически, ключ вовсе не зафиксирован в роли “только для сайта”, так что ничего тут не мешает: достаточно взять какую-нибудь подходящую утилиту, которая может прочитать секретный ключ из файла (OpenSSL или другие варианты), и можно начать подписывать произвольные электронные документы. В процессе подписывания – открытый ключ и сертификат вообще не требуются. Естественно, каждое использование ключа (буквально – каждая операция) несёт с собой дополнительный риск утечки этого ключа. Однако, при штатной работе TLS, ключ “от сертификата” уже постоянно задействован именно для получения электронной подписи, так что тут нет места для каких-нибудь опасений относительно того, что ключ будет “использован не по назначению”. Однако для хитрых ошибок – место, всё же, появляется.
Так, если для подписи файлов применяется новая утилита, отличная от библиотеки, используемой TLS-реализацией веб-сервера, то возникает дополнительная вероятность утечки: либо непосредственно через эту утилиту, либо в процессе её вызова. Если этот дополнительный, относительно веб-сервера, процесс вдруг так устроен, что позволяет третьей стороне непосредственно подписывать произвольные данные, то даже без утечки ключа это ведёт к компрометации TLS-сервера: третья сторона может подменить сессию, перехватив соединение и подписав нужные параметры, которые подаст в виде файла на вход подписывающей утилиты. Так что осторожность – не помешает, а утечки ключей от одного TLS-сервера через другой, сконфигурированный с уязвимостью, – уже случались.
Проверка подписи для рассматриваемого способа использования проводится при помощи открытого ключа, опубликованного в TLS-сертификате. Тут возникает административный момент, связанный с политикой управления доверием, используемой приложением на проверяющей стороне. Это приложение может “верить в сертификат”, а может – “верить в сам ключ”.
В первом случае – допустимый контекст использования ключа задаёт сертификат и, так сказать, семантика применения этого сертификата. Например, приложение может в принципе принимать TLS-сертификаты (и ключи из них) только в контексте подписывания TLS-параметров при HTTPS-доступе к веб-серверу, и всё. Естественно, в сертификате предусмотрены дополнительные флаги и параметры, определяющие допустимые способы использования ключа. Их приложение тоже может учитывать каким-то своим способом (но, заметьте, использование ключа для подписи – в сертификате “от сайта” должно быть разрешено и так; см. выше).
Во втором случае, когда приложение “верит в сам ключ”, сертификат служит лишь контейнером для доставки этого ключа. “Верить в сам ключ” – означает, что доверие распространяется прежде всего на конкретное значение ключа, но при этом сам сертификат из рассмотрения может и не исключаться. Например, это обычная практика для корневых ключей в TLS для веб-сайтов и браузеров.
Впрочем, “побочное” использование ключей “от сайтов” для подписывания может подразумевать и то, что открытый ключ тоже используется вообще без учёта сертификата. Технически, опять же, ничего этому не мешает.
Комментировать »
Воскресное чтение манускриптов. В прошлом году, в заметке про кусочек папируса с фрагментом “Илиады”, я писал, что какие-то очень краткие фрагменты текстов, читаемые на папирусах, могли бы относиться к той же “Илиаде”, но их не получится так идентифицировать, поскольку недостаточно информации, а фрагменты выходят за пределы “эластичности” текста “Илиады”. Это довольно очевидное наблюдение. Тем не менее, посмотрим, можно ли на сканах манускриптов быстро найти что-то примерно подходящее, для иллюстрации. То есть, фрагменты текстов из нескольких слов, которые было бы не ясно куда отнести. Оказывается – найти можно. Для примера годится не слишком частая, но характерная фраза из записей сочинений Гомера. Скажем, кусочек из стиха, в котором упоминается Kubernetes – потому, что этот фрагмент тоже не так давно встречался на dxdt.ru, в записке про “Кибернетический след и цветовой сдвиг“. А именно: ἐνὶ οἴνοπι πόντῳ – “в тёмно-винном море”.
Ниже пара фрагментов манускриптов: Venetus A (“Илиада”, Гомер) и BnF.Grec.2771 (“Труды и дни”, Гесиод). Соответствующая фраза – выделена и там, и там.

(Venetus A, 23:316)

(BnF.Grec.2771, st. 622)
Первый манускрипт (Venetus A) датируется началом девятого (поправка, 2025/06/04: десятого) века, второй – десятым (всё н.э, понятно). То есть, между соответствующими манускриптами больше ста лет, согласно датировкам (поправка, 2025/06/04: меньше ста лет, конечно). При этом структура, оформление, а кроме того, что называется, “типографика” и начертания букв, очень и очень близки, что хорошо видно на скриншотах. Удачно, что интересующие нас слова в обоих случаях приходятся на конец строки. Попадись кому-то достаточно небольшой кусочек с записью только этих слов (как выделено на картинках, вместе с частями диакритических знаков и буквы из верхней строки) – различить источники по составу и по буквам текста вряд ли было бы возможно. Однако, если удалось бы прицепить какие-то ещё буквы (строки выше и ниже), то результат уже мог бы быть более избирательным.
Гесиод – из гомеровского периода, так что, наверное, ничего удивительного. Тем более, что приведённый фрагмент текста “про море” находится очень близко к описанию в “Трудах и днях” путешествия на “состязание поэтов”, которое нередко считают упоминанием состязания между самим Гесиодом и самим Гомером.
Комментировать »
В продолжение записки про ИИ Google и “серебряную медаль” Международной математической олимпиады. Там исходная задача, которую потом “решает” ИИ, прежде переводится людьми на входной язык системы машинных доказательств – то есть, на некоторый формальный язык, описывающий в определённых логических формулах целевое состояние, соответствующее задаче. Это не программа, как иногда пишут, а запись, грубо говоря, теоремы, соответствующей задаче, в формулах, которые возможно (при некоторых ограничениях) доказать в данной системе. Да, доказательство выполняется при помощи компьютерных вычислений, тем не менее, формальная запись задачи-теоремы не является программой, реализующей некий алгоритм – иначе не было бы смысла в поиске записей доказательств. И вот в “популярном изложении” необходимость такого перевода обосновывают тем, что нужно “переписать на языке, понятном системе”. Это, конечно, искажение. Но с ним связаны два важных момента.
Момент первый, – достаточно очевидный, – в том, что этот “суперпродвинутый” искусственный интеллект даже не способен прочитать условие задачи и, предположим, перевести его на формальный язык самостоятельно, хоть это и могло бы быть типичным вариантом запроса к ChatGPT. Так как для корректного перевода нужно хорошо понимать не только задачу, но и принципы построения формальных языков, выдача подобного ИИ-перевода для входа “системы ИИ” просто не подходит. Это понятно.
Есть и второй момент, который “замыливают” посильнее: перевод на формальный язык необходим для того, чтобы предлагаемые системой решения могли быть проверены на соответствие этому описанию машинным способом. То есть, так как предполагается, что будут подбираться тексты “доказательств” на формальном языке, необходим автоматический, машинный “проверятор” для этих доказательств – иначе, если решения потребуется согласовывать с человеком, то и работать это всё будет слишком медленно, поскольку образуется непреодолимый затык с проверкой решений. Так что необходима запись на формальном языке и проверяющая компьютерная система, а такая система в принципе не может проверять записи на естественном языке. Так что, понятно, это не решение олимпиадной задачи в том смысле, который тут принято присваивать слову “решение”.
Что же касается применения систем машинного (автоматического) доказательства в области теоретической (чистой) математики, то тут мнения, как говорится, сильно расходятся. Прежде всего потому, что теоретическая математика это не только не наука, но и вовсе не является такой уж “точной и строгой” областью, как, почему-то, нередко предполагают. Однако системы машинного доказательства очень полезны в некоторых прикладных математических направлениях. Например, для формальной проверки корректности (соответствия задаче) программного кода. Автоматические системы на этом направлении уже используются. Наверное, и автоматический генератор доказательств с перебором тут мог бы тоже пригодиться, как инструмент “фаззинга”. Но нужно учитывать, что во всех случаях речь, всё же, идет о компьютерных вычислениях, а не о математических операциях, поэтому соответствующие методы имеют существенные ограничения, как фундаментальные, так и вполне “локальные”, обусловленные ошибками на разных уровнях реализации.
Комментировать »
Исследователи, сравнивая в автоматизированном режиме (fuzzing) поведение различных микропроцессоров семейства RISC-V, обнаружили дефекты команд в линейке распространённых ЦПУ T-Head C910. Дефекты относятся к конкретной реализации и расширениям RISC-V, которые могут использовать разработчики совместимой аппаратуры.
Особенно интересно выглядит команда, позволяющая реализовать прямую запись данных в физическую память (ОЗУ) по произвольному адресу, минуя не только внутренний кеш, но и вообще все аппаратные ограничения на уровне процессора. Такой “гаджет”, конечно, позволяет добиться уверенной эскалации привилегий (и не только), в том числе, из любых контейнеров и прочих систем виртуализации. В качестве примера в исходной работе приводятся линуксы и модификация системного вызова getuid() таким образом, чтобы он всегда возвращал значение 0 (это, соответственно, root в линуксах). Очевидно, от типа ОС такой результат не зависит, так как речь про произвольный и прямой доступ в память.
(Новость OpenNet.)
Комментировать »
Нередко можно прочитать, что “сверхразумный ИИ” сгенерирует такие “решения и идеи”, которые человек понять не сможет, а ИИ – не сможет объяснить человеку (ну, настолько вот сложные и непонятные результаты). Это очень популярная характеристика, которую часто обозначают как одно из ключевых свойств “сверхразумного” ИИ (хотя таким свойством должна бы быть как раз способность объяснить сложное, но сейчас не об этом).
С другой стороны, в популярной литературе по теме ничуть не реже утверждается, что вот на пути к уровню “сверхразумности” – ИИ должен быстро переходить из статуса просто “сильного ИИ”, в статус “сверхразумного”, используя для этого поэтапные самоулучшения: то есть, вот такой ИИ доработал собственную модель, обновился, перешёл на более мощную версию, и тут же опять улучшил показатели, снова обновился, и так далее. Так утверждается.
Между тем, подобный “рекурсивный ИИ” мог бы работать в обратную сторону, с целью объяснения тех самых “сложных идей” из первого абзаца: вот “сверхразумный” ИИ объяснил свои идеи своей предыдущей итерации – интеллекту не менее искусственному, но который попроще, пусть и близок по “уровню понимания”, тот – предыдущей итерации, и, – опять же, – так далее, вплоть до уровня, близкого уже тем самым человекам.
Комментарии (1) »
Официальная новость ТЦИ про добавление определения X25519Kyber768 на audit.statdom.ru (САБИУ). Цитата:
На серверной стороне поддержка реализована, например, на узлах Google и веб-фронтендах Cloudflare, одного из крупнейших мировых провайдеров веб-доступа. То есть существенная часть веб-трафика в Интернете уже защищена с помощью данной криптосистемы.
Вообще, этот момент почему-то сейчас упускают из виду: может, оно и прошло незаметно, но сейчас, если вы используете Google или Youtube через более или менее современный браузер из распространённых, TLS-трафик уже защищён при помощи ключей, полученных с использованием Kyber768 – то есть, постквантовой криптосистемы. А если сюда добавить, что очень многие мало-мальски популярные сайты используют Cloudflare, – в том числе, в Рунете этот сервис очень распространён, – то окажется, что немалая часть обычного TLS-трафика в Вебе, – точно больше трети, – уже перешла, так сказать, на “постквантовые ключи”. А момент довольно интересный, но почему-то про него не особо-то пишут даже технические СМИ.
(Кстати, по наличию поддержки на стороне сервера можно, – примерно, – судить, у кого насколько “свой” TLS-бэкенд. Тоже занимательно.)
Комментировать »
Код, написанный на C, можно транслировать на другие языки высокого уровня при помощи строгого и детерминированного транслятора. Казалось бы. Тем не менее, DARPA тоже подключается к хайпу и предлагает реализовать такой транслятор при помощи LLM. Конечно, перевод должен осуществляться на Rust, потому что нужно же “исключить уязвимости при работе с памятью”, но, конечно, нельзя исключать и влияние моды – об этом, кстати, прямо сказано в исходном сообщении.
Комментировать »
Отмечу отдельно некоторые заметки, вышедшие в июле 2024 года:
Машинный ИИ в книгах прошлого века – о книге Giant Brains, or Machines That Think, опубликованной в 1949 году.
Сетевая геолокация передатчиков – обзор методов, которые применяются для определения координат радиопередатчиков.
“Сверхмашинный” интеллект – непонятные возможности “сверхразмуных” машин.
ИИ Google и олимпиадные задачи – перебор исходных текстов системы “машинных доказательств” это не “решение математических задач”.
Оптимизирующие компиляторы, микроконтроллер и ассемблер – практический пример с картинками, иллюстрирующими особенности оптимизации кода для конкретной аппаратуры.
Комментировать »
Сейчас всё популярнее становятся различные детекторы контента, сгенерированного ИИ-системами. Например, для детектирования сгенерированных видеозаписей, которые претендуют на “реальность”. Забавно будет, когда такие детекторы начнут классифицировать как “поддельные” результаты работы камер смартфонов. Ведь к этим камерам не менее активно добавляют “улучшатели”, генерирующие то, чего в оригинальной сцене могло и не быть.
Понятно, конечно, что к результатам, полученным “сертифицированными смартфонами”, добавят метки, по которым такой классификатор ИИ-генераторов будет уверенно отличать “подлинные” сгенерированные видеопотоки и статичные фотоснимки от “поддельных” сгенерированных фотоснимков и видеопотоков (чистые технологии постструктурализма).
Кстати, вопрос о том, что, в случае работы электронных схем и видеопроцессоров, считать подлинником – довольно занимателен сам по себе.
Комментировать »
Посмотрим на совсем небольшой фрагмент кода.
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
Теперь импульсы стали короче в два раза, что и подтверждается очередной фотографией экрана осциллографа.

Конечно, реальная ситуация может быть сложнее, а компилятор может “догадаться” о какой-то лучшей оптимизации. Если рассматривать современные мощные микропроцессоры, то там, например, вообще сложно судить о реальном количестве тактов, затрачиваемых той или иной машинной командой, даже на уровне ассемблера. Ассемблер сейчас не всегда нужен, даже если нужно программировать микроконтроллеры, но знакомство с ассемблерами и принципами работы той аппаратуры, на которой программа будет исполняться, часто помогает осознать причины несоответствия ожиданий тексту программы на языке высокого уровня.
Комментировать »
Новый