Ресурсы: техническое описание TLS, LaTeX - в картинки (img), криптографическая библиотека Arduino, шифр "Кузнечик" на ассемблере AMD64/AVX и ARM64
Случайные числа, – которые, чаще, псевдослучайные, – сейчас нужны всюду. В том числе, при нормальном функционировании операционных систем, что порождает занимательные случаи. Например, мне приходилось сталкиваться со следующим “загадочным явлением”: после установки на достаточно старый, но с некоторыми аппаратными обновлениями (см. ниже, это важный момент), компьютер современной версии ОС на базе Linux (насколько помню, Debian 10), не удаётся зайти в только что сконфигурированную систему при помощи SSH с удалённого узла. SSH-сервер просто не отвечает. Локально, подключив монитор и клавиатуру, зайти можно и выглядит всё хорошо: конфигурация верная, всё работает. Самое загадочное: если после того, как кто-то повозился с локальной консолью, попробовать подключиться по SSH удалённо, то всё прекрасно работает.
Разгадка cледующая. SSH-серверу просто не хватало локальной случайности – то есть, системного источника случайных чисел (/dev/random). Дело в том, что ядро (Linux) собирает энтропию для процесса, генерирующего (псевдо)случайные числа, так сказать, с доступной аппаратуры. В более или менее современных системах проблем с обильными источниками аппаратной энтропии нет, так или иначе, а вот если в очень старую систему на процессоре Intel поставить вместо шпиндельного винчестера SSD-накопитель, да отключить клавиатуру и видеокарту, то энтропии становится мало и её съедает само ядро при загрузке себя и сопутствующих модулей (напомню, что там есть всякие хитрые методы “рандомизации адресации”, направленные, как бы, на запутывание атакующих). Так как SSH-сервер использовал блокирующий вызов для получения случайных чисел (/dev/random вместо неблокирующего /dev/urandom), то ему приходилось ждать, пока накопится достаточно энтропии. SSH-серверу случайные числа нужны для криптографических операций, поэтому он и не мог принять входящее соединение. А вот если кто-то подключил клавиатуру, да ещё повозился в консоли, то энтропии становилось больше, хватало и для SSH. Чинится это либо установкой специального пакета типа haveged, который генерирует дополнительную энтропию программно (или программно-аппаратно, если хотите), либо добавлением аппаратного источника энтропии. Сейчас проблема менее актуальна: в дистрибутивы для платформ, где с получением энтропии трудности, haveged или подобное решение стали включать автоматически.
Вообще, отсутствие в системе хорошего источника энтропии выглядит особенно пугающе, когда речь идёт о криптографических операциях. Так, в ECDSA критически важный случайный параметр используется при вычислении каждой подписи. Если ваша программная система работает, скажем, в виртуальной машине, то с качеством случайности могут быть проблемы. Эти проблемы, несколько неожиданным для неспециалиста образом, могут привести к утечке ключей (касается не только ECDSA, но и ГОСТ-подписи). Это одна из важных причин того, что уже существует более современная версия ECDSA, где параметр подписи определяется детерминированным способом (но это отдельная история). Поэтому обычно приходится применять всякие хитрости, позволяющие подмешивать дополнительную энтропию алгоритмически, например, при помощи симметричного шифра и счётчика. Лучший способ, конечно, это использовать клавиатурный ввод от человека. (Впрочем, степень детерминированности ударов по клавишам, выполняемых человеком, это вопрос дискуссионный – как на техническом, так и на философском уровне.)
Комментарии (2) »
В этой заметке, в качестве практического примера того, чем может быть полезен открытый исходный код, рассматривается реализация проверки (теоретико-числовых) свойств параметра DH в приложении Telegram, ну или в одной из версий этого приложения – детали тут не важны, а ссылки есть ниже по тексту. (На всякий случай, сразу отмечу – каких-то дефектов, что называется, “выявить не удалось”, да и цели такой не ставилось – это просто достаточно краткий разбор небольшой функции с комментариями из области прикладной криптографии, а не подробный анализ кода.)
DH обозначает протокол Диффи-Хеллмана. В мессенджере Telegram протокол Диффи-Хеллмана используется при создании “секретных чатов”, где он служит для получения общего секрета клиентами (то есть, ключа для зашифрования сообщений). Рассматривается самый простой вариант – обмен сообщениями между двумя пользователями в защищённом режиме.
Telegram использует “классический” (или “мультипликативный”) вариант DH, работающий в мультипликативной группе конечного поля (сейчас такой вариант принято обозначать FFDH – от Finite Field). Если обойтись без строгих научных терминов, то этот вариант DH не “эллиптический” (например), а “обычный”, работающий в арифметике остатков. Про “эллиптический” вариант многие слышали применительно к TLS – там он называется ECDH(E). То, что в Telegram не используется современный вариант на эллиптической кривой – всегда выглядело несколько странно. Скорее всего, этому есть очень простое объяснение, связанное с историей появления протокола MTProto, но, так или иначе, эти детали остаются за рамками данной заметки, которая посвящена свойствам модулей DH и небольшому фрагменту исходного кода приложения, связанному с проверкой этих свойств.
Чтобы определить конкретные параметры протокола DH (FFDH, но не только) – требуется задать достаточно большое простое число. В случае “классического” варианта битовая разрядность этого числа, по современным представлениям, должна быть хотя бы 2048 бит. Telegram требует строго 2048 бит (см. ниже). Данное простое число задаёт базовую структуру для арифметики протокола и называется модулем. От свойств модуля зависит надёжность реализации. Так, слишком маленькая разрядность, – например, 256 бит, – позволяет очень быстро решать обратную задачу (находить дискретный логарифм) и вычислять по открытой информации секретное значение, которым обмениваются стороны. (Дежурное замечание: пример про 256 бит – не относится к разрядности ECDH, там другие алгоритмы и структуры.)
В Telegram, модуль, используемый сторонами, передаётся им сервером. Плохая это практика или хорошая? Для точного ответа информации маловато: с одной стороны, самостоятельное генерирование модуля сторонами может приводить к использованию нестойких модулей (как преднамеренному, так и нет), а кроме того – добавляется вычислительная нагрузка; с другой стороны – использование неопределённого серверного модуля требует доверия серверу или, как минимум, доверия процессу выбора модуля. Так, FFDH всё ещё используется в TLS современной версии 1.3, а значения модулей там, в общем-то, зафиксированы спецификациями, однако для выбора параметров предписан опубликованный процесс. Другими словами: если модуль вам присылает сервер, то, в теории, сервер может прислать заранее тщательно подготовленный модуль, припрятав в рукаве нужные для быстрых вычислений структуры. Telegram присылает модуль с сервера и может присылать разным пользователям и разным “секретным чатам” разные значения модулей, вряд ли за этим кто-то следит. В качестве мер повышения доверия документация (в которой иногда встречаются опечатки) предлагает проводить хорошо известные проверки свойств присланного числа, эти проверки – присутствуют в коде приложения.
Перейдём к особенностям кода. Telegram – среди тех немногих приложений, разработчики которых заявляют так называемую “воспроизводимую сборку“: действительно, публикация исходного кода, сама по себе, не гарантирует, что исполняемое приложение, распространяемое в собранном виде, соответствует опубликованным исходникам. Telegram предлагает описание того, как можно самостоятельно проверить соответствие сборки исходникам. Это хорошо (если работает – я не проверял). Я рассматриваю некоторый исходный код, доступный на GitHub-е по опубликованной ссылке.
Простое число P, представляющее собой модуль DH, поступает с сервера в ответе на запрос getDhConfig, в виде массива байтов. Свойства проверяются в telegram/messenger/SecretChatHelper.java вызовом функции Utilities.isGoodPrime(P, G); (G – это генератор, второй параметр протокола.)
if (!Utilities.isGoodPrime(res.p, res.g)) {
acceptingChats.remove(encryptedChat.id);
declineSecretChat(encryptedChat.id, false);
return;
}
Вся содержательная проверка – внутри isGoodPrime() (telegram/messenger/Utilities.java). Эта функция начинается следующим фрагментом:
if (!(g >= 2 && g <= 7)) {
return false;
}
if (prime.length != 256 || prime[0] >= 0) {
return false;
}
BigInteger dhBI = new BigInteger(1, prime);
Первый if проверяет интервал значений генератора.
Следующий if – контролирует разрядность переданного модуля. 256 байтов – это 2048 бит. prime[0] >= 0 – тут проверяется, что старший бит установлен в единицу. Этот оборот может показаться не самым очевидным: тип byte в Java определён со знаком, соответственно, если значение больше либо равно нулю, это означает, что старший бит – нулевой (знак записи числа “плюс”); представление целых чисел большой разрядности (BigInteger – см. следующие строки) здесь использует запись, в которой старший байт – байт с нулевым индексом. Таким образом, prime[0] >= 0 проверяет, что получающееся число будет не меньше, чем 2^2047. new BigInteger(1, prime) – создаёт объект BigInteger и загружает в него значение модуля из массива prime. Единица в левом параметре конструктора – обозначает, что число положительное. Зачем нужен выше фрагмент с if, проверяющий длину и значение старшего бита? Например, сервер мог бы передать 256 байтов, в которых старшие значения были бы нулевыми, тогда длина массива соответствовала бы заданному требованию, но реальная разрядность получившегося в BigInteger числа оказалось бы меньше, так как нулевые байты слева не учитывались бы.
Дальше следует блок (здесь пропущен) из нескольких if..else if, которые, в соответствии со значением генератора, проверяют остатки по простым 3, 5, 7 и некоторым степеням 2. Этот фрагмент, наверное, можно рассмотреть в отдельной заметке из области занимательной математики. Цель проверки – контроль свойств полученного модуля (этим фрагментом вся проверка “доверия серверу” исчерпывается).
А следующая пара строк в telegram/messenger/Utilities.java довольно занимательная (приведено с сокращениями, см. детали ниже):
String hex = bytesToHex(prime);
if(hex.equals("C71CA...")) {
return true;
}
Полученное с сервера представление модуля (prime) преобразуется в hextext – то есть, в текстовую строку с записью шестнадцатеричными цифрами, – а получившаяся строка сравнивается с константой. Если значение совпало, то модуль считается “хорошим” (обратите внимание, что выше, тем не менее, уже были необходимые проверки по малым простым для того же числа).
Непосредственно в коде зашит вот такой модуль (переносы строк добавлены для удобства – это одно число):
C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F 48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37 20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64 2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4 A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754 FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4 E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F 0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B
Это простое число (ну, с точностью до детерминированной проверки в SAGE и вероятностной проверки в Mathematica, конечно; но это означает, что простое). То есть, в этом фрагменте – код строго верит в один конкретный модуль. Для других модулей предусмотрена проверка простоты (и статуса safe prime):
BigInteger dhBI2 = dhBI.subtract(BigInteger.valueOf(1)).divide(BigInteger.valueOf(2)); return !(!dhBI.isProbablePrime(30) || !dhBI2.isProbablePrime(30));
Здесь, с помощью вероятностного теста (это единственный способ – известные детерминированные алгоритмы слишком ресурсоёмкие), проверяется, что модуль P простое число и что (P-1)/2 – тоже простое. Обычная практика. На этом проверки заканчиваются (естественно, здесь не может быть никакой аутентификации и тому подобных дополнительных шагов).
Нужно отметить, что сравнение получившихся ключей в Telegram должны проводить сами пользователи, по отпечаткам, которые им выводит приложение. Это тоже важный момент.
Комментировать »
В радикалах корни алгебраических уравнений с рациональными коэффициентами возможно записать только в редких случаях – про это рассказывает отдельная записка. Интересно, что этот, – казалось бы, технический, – момент можно трактовать ещё строже, воспользовавшись теоретическими трудностями арифметики в действительных числах. То есть, коэффициенты уравнения – рациональные числа, которые можно точно записать любым привычным способом. Некоторые корни некоторых из этих уравнений тоже можно записать не менее привычным способом, поскольку данные корни – рациональные числа, как и коэффициенты. Но другие корни оказываются совершенно иной числовой природы (если вообще числовой – тут тоже возможны разные трактовки, связанные, как ни странно, с понятием вычислимости). Так, √2 точно выписать в десятичной системе нельзя. С общепринятой сейчас теоретической точки зрения такие действительные числа – это некоторые бесконечные процессы (даже не просто бесконечные, а “бесконечные в квадрате”; поэтому, собственно, точная практическая арифметика с ними и оказывается невозможной). Получается, что корнями уравнения с рациональными (целыми) коэффициентами могут быть и бесконечные процессы. Естественно, всё это работает, только если допустить в схему действительные числа, но звучит загадочно и чем-то напоминает квантовую механику, если задуматься.
Всё это, кстати, связано и с подсчётом количества корней уравнений. Можно принять, что “бесконечные процессы” в качестве корней уравнений данного типа не допускаются. Геометрически, – пусть и несколько неожиданным образом, – это означает, что не всякие кривые, которые “пересекают” рациональную числовую координатную ось, имеют с этой осью общие точки. Потому что иррациональное число √2, например, не принадлежит множеству точек оси. А на другом конце геометрической интерпретации оказывается бесконечный процесс, возникающий в ходе геометрического доказательства иррациональности √2.
Комментировать »
Открытый исходный код на языке высокого уровня нужно воспринимать как удобный комментарий к внутреннему устройству того или иного программного пакета. Речь здесь о пакетах, которые распространяются среди конечных пользователей. То есть не о сервисах, работающих где-то в Интернете.
В общем случае, если исходный код написан хорошо, то, действительно, лучший источник сведений о подробностях работы программы найти весьма трудно (естественно, желательно знакомство с соответствующим языком программирования). Однако с этим вот “общим случаем” связано немало неверных, излишних “обобщений”. Например, верно ли, что “открытый исходный код” позволяет легко находить уязвимости в ПО, а если исходный код скрыт, но предоставляется только исполняемый файл, то уязвимости найти гораздо сложнее? Поэтому, дескать, “закрытое ПО” более безопасно. Проблема тут в том, что такой вопрос подразумевает некорректное сравнение. Дело даже не в том, что некорректно оценивать “безопасность” программного пакета по “степени сложности” чтения исходников (ведь “бинарный исполняемый” код – такой же “исходник”; об этом – ниже). Просто, нет такой метрики, которая позволила бы универсальным способом сравнить “сложность” нахождения уязвимостей в ПО, когда такие уязвимости были найдены разными методами.
Поиск и практическое использование (“эксплуатация”) уязвимостей – процесс всё ещё творческий, некоторые считают, что более творческий, чем, собственно, написание кода. С этим, конечно, можно и нужно поспорить: например, и там, и там – есть сейчас развитые “автоматизации”, то есть “бездумный” кодинг, автоматическая генерация кода, и, скажем, “фаззинг” на стороне обнаружения (и нельзя забывать про современные анализаторы кода, но это тоже другая тема). Однако полностью исключить творческую составляющую не получится, как не получится корректно сравнить степени “сложности” в метрике, состоящей из параметров доступности и формата исходного кода. Нередко, после того как некоторая уязвимость была выявлена, а описание опубликовано, она тут же начинает почти всем казаться очевидной (относится не только к уязвимостям и не только к ПО, конечно). Преимущество “открытых исходников” тут в том, что можно, как говорится, ткнуть пальцем в код, показав, где проблема.
Действительно, наличие открытых исходников помогает находить типовые уязвимости, помогает быстрее исследовать подозрительные направления – это равно та документирующая роль, которая упомянута выше. Но это ни разу не гарантирует, что та же уязвимость не была бы обнаружена быстрее, если бы для анализа был представлен только исполняемый “бинарник”. Во-первых, средства анализа скомпилированного, исполняемого кода сейчас тоже мощные; во-вторых, “исходный код на языке высокого уровня”, с точки зрения анализа, не равно понятию “исполняемый код”. Да, и в компиляторах бывают “особенности”, и ошибка, приводящая к уязвимости, может возникать только на конкретной платформе, да и не все типы потенциальных уязвимостей легко увидеть в исходных кодах.
Другой момент, о котором постоянно забывают: а если в качестве открытых исходников опубликован код на ассемблере – это сильно помогает в поиске уязвимостей или уже меньше? Ассемблер, даже как явление, сейчас известен меньшему кругу специалистов, это да. Но можно же обфусцировать исходный код на С, да ещё так, что понять его будет посложнее, чем соответствующий кусок в машинных кодах x86.
Вообще, трудность в понимании записи произвольного, заранее не известного, алгоритма настолько фундаментальная, что, похоже, именно она и является причиной формулирования знаменитой проблемы P≟NP. Из этого, впрочем, следует вывод, что и в обратную сторону, – то есть, для превентивного выявления закладок/уязвимостей, – публикация исходников работает не так эффективно, как принято думать: дефекты не просто всегда есть, но они и могут долго оставаться незамеченными, к сожалению. Однако открытые исходники – там, где их возможно открыть – конечно, лучше, и публикация исходников, сама по себе, не делает программу или программную систему, которая распространяется по конечным пользователям, “более уязвимой”.
Комментировать »
Шуточное сравнение, но занимательное, поскольку показывает “почти экспоненциальные” (см. ниже) различия. А именно: одноплатный Raspberry Pi 4 (Model B) и “большой” процессор. Raspberry Pi бывает удобно использовать для некоторых вычислений, особенно четвёртую версию, поскольку она заметно более мощная, но тут я решил в качестве второго устройства взять систему на базе i9-10900K. В качестве алгоритмической основы применён пакет CADO-NFS – это реализация метода NFS (в русскоязычной терминологии, обычно, метод “решета числового поля”) факторизации чисел. Система на процессоре Intel работает под Debian 11, а Raspberry Pi – под Raspberry Pi OS (64 bit), которая тоже основана на Debian 11. CADO-NFS я собрал из исходных кодов, с типовыми настройками (за исключением параметра, задающего разрядность счётчиков, но это детали). У Raspberry Pi 4 – Broadcom BCM2711 и четыре ядра Cortex-A72, без разгона, а у i9-10900K – 10 ядер (Comet Lake, куда более мощных, понятно) на 20 потоков, без разгона.
Что получилось (RPi – Raspberry Pi 4, указано время, затраченное на факторизацию):
1) для полупростого числа разрядностью 200 бит: RPi – ~88 сек.; i9 – ~27 сек. RPi/i9 == ~3.26;
2) для полупростого числа разрядностью 319 бит: RPi – ~4879 сек.; i9 – ~262 сек. RPi/i9 == ~18.62;
3) для полупростого числа разрядностью 336 бит: RPi – ~9861 сек.; i9 – ~578 сек. RPi/i9 == ~17.06;
4) для полупростого числа разрядностью 353 бита: RPi – ~17812 сек.; i9 – ~751 сек. RPi/i9 == ~23.72.
Итак, с ожидаемо огромным преимуществом выиграла система с i9, но для чисел малой разрядности – разница не так уж велика, что, конечно, не менее ожидаемо. (Конечно, показатели зависят и от самого числа, но для шуточного сравнения это не так уж важно.) При этом, из-за меньшего объёма памяти, RPi в принципе сошла бы с дистанции значительно раньше, если бы соревнование продолжилось (в системе с i9 установлено 128 Gb ОЗУ, а в RPi – только четыре гигабайта).
Интересен и другой момент: система с i9 использует чипсет Z590, водяное охлаждение, занимает место на полке и суммарно потребляет при интенсивных вычислениях около 360 Вт (точность измерения мощности, впрочем, не очень высокая, но результат похож на правду); RPi – это одноплатный компьютер, который под нагрузкой потребляет менее 6 Вт. То есть, i9 требуется в 60 раз больше электрической мощности. Вот так.
Комментировать »
Разгадка к задаче про 2^255+19. В диалоге из “эпиграфа” к задаче говорится, что “очевидно, 2^255 + 19 делится на три”. Практически вся современная криптография так или иначе использует арифметику остатков. Поэтому понять, что 2^255 + 19 делится на три можно моментально: 2^255 при делении на 3 (mod 3) даёт остаток -1 (см. ниже); а 19 – это +1 (mod 3), потому что на 3 делится 18, соответственно, 19 == 6*3 + 1. Сумма остатков -1 + 1 == 0. Следовательно, число делится на три.
Почему 2^255 даёт остаток -1? Потому что 255 – нечётное число. Действительно, 2 в чётной степени будет давать остаток 1 (mod 3), а в нечётной остаток -1 (или 2, что здесь то же самое), так как 2 == 1*3 + (-1). Из этого можно вывести признак делимости на 3 для числа в двоичной записи, то есть, когда основание системы равно двум. А именно: биты со значением 1 (единица) на чётных позициях будут давать +1, на нечётных -1; если сумма (по единичным битам) делится на 3, то и само число делится. Пример (позиции считаем справа налево): 42 == 0b00101010; 1 + 1 + 1 == 3, следовательно, делится; 42 == 14*3; 43 == 0b00101011; 1 + 1 + 1 + (-1) == 2, следовательно, 43 не делится на 3.
Решение для 2^2023 + 2023. Доказываем, что делится на 3:
2^2023 (mod 3) == -1 (см. выше)
2023 = 2022 + 1; воспользуемся привычным признаком делимости для десятичной системы: 2 + 2 + 2 == 6, то есть, 0 (mod 3) => 2023 == 1 (mod 3)
-1 + 1 == 0
Естественно, возможны и другие, чем-то более привычные, способы. Например, в шестнадцатеричной системе, где 2^2023 это будет восьмёрка со множеством нулей, а 2023 == 0x07E7, поэтому в записи нашего числа, кроме нулей, встречаются только шестнадцатеричные цифры 8, 7, E и 7. В шестнадцатеричной системе действует тот же признак делимости на 3, что и в десятичной (“если сумма значений цифр делится на 3”), потому что основание системы 16 == 15 + 1, то есть, остаток 1. Шестнадцатеричное E это 14: 8 + 7 + 14 + 7 == 36; 36 == 12*3.
Комментировать »
Небольшое продолжение прошлогодней записки о том, считал ли Аристотель, что “тяжёлые тела падают быстрее лёгких”. В этом контексте нередко можно услышать про эксперимент на Луне, когда астронавт демонстрирует, что тяжёлый молоток и легкое перо, будучи брошенными с равной высоты, достигают лунной поверхности одновременно.
Интересно, что в “Физике” Аристотеля падение в вакууме описано так: “Конкретная скорость движения тела в среде определяется формой и силой, придавшей импульс. Выходит, что в пустоте все тела были бы одинаково быстры. Но это невозможно”. То есть, Аристотель прямо допускает, что в пустоте скорость (точнее – “быстрота”, см. ниже) может быть одинаковой, но этим он обосновывает невозможность существования пустоты. Так, в том же тексте объясняется, что нет “соизмеримых с нулём” чисел, с помощью которых можно было бы обозначить скорость движения тела в пустоте: так как при отсутствии сопротивления среды максимальная скорость получилась бы бесконечно большой. А из-за того, что эта (сколь-угодно большая, “неизмеримая”) скорость универсальна, всякая пустота должна была бы всё равно мгновенно заполниться окружающим веществом.
Вообще, Аристотель не просто рассматривает падение тел строго через некоторую среду, но и предлагает классифицировать быстроту движения по пропорциям веса и сопротивления среды. А для “пустоты” такая классификация не работает, поскольку “в пустоте все тела были бы одинаково быстры, но без причины”, либо их максимальная скорость оказывается сколь угодно большой. Соответственно, пустоту (вакуум) Аристотель отвергает. И нужно учитывать, что Аристотель оперирует древнегреческими терминами и понятиями. Так, “полнота” (“заполненное”) и “пустота” (“пустое”) – πλῆρες и κενόν – это “первичные составляющие”, но Аристотель утверждает, что “полнота” должна быстро заполнять всякую пустоту, иначе возникают трудности с классификацией движения. А “скорость” в соответствующем фрагменте у Аристотеля обозначается словом τάχος, которое можно перевести и как “быстрота”, то есть, это, конечно, “скорость”, но в смысле минимального затрачиваемого времени (конкретно, “одинаково быстры” – ἰσοταχῆ; где знакомая приставка “изо-“/ἰσο как раз и обозначает одинаковость). (Другие сходные значения для той же основы: скорый, проворный и т.д.). Да и строит соответствующее понятие Аристотель на базе соизмеримости проходимых интервалов пути, интервалов времени. То есть, речь явно идёт о максимальной скорости (“быстроте движения”), достигаемой телом.
Так что, во-первых, Аристотель прямо пишет, что в пустоте скорость всех тел была бы одинаковой (а это тот самый эксперимент с пером и молотком в вакууме, который, якобы, опровергает представления Аристотеля); во-вторых, Аристотель утверждает, что пустоты быть не может (ну или он отказывается её допускать в рамках своей модели), а поэтому практическая максимальная скорость будет всегда разной у тел разного веса, при прочих равных параметрах (а это как раз хорошо подтверждается экспериментом, если пух и свинцовый шарик бросать в воздухе или, предположим, разные камни – в воде или в масле, как, возможно, делал Аристотель).
Комментировать »
Сейчас популярна история с чат-ботом ChatGPT, который даёт пространные ответы на вопросы из самых разных областей. Мне доступ к данному сервису получить не удалось (там хотят слишком много реквизитов), но это не важно: понять, о чём речь, не так уж трудно по многочисленным цитатам. Всё это увлечение соответствует попыткам найти смысл непосредственно в тексте. Проблема в том, что текст, как набор символов с определённой структурой, тем не менее, самостоятельного смысла не несёт. Смысл образуется (или не образуется) после того, как результат прочтения текста вкладывается в некоторую большую структуру; и чтобы говорить об “интеллекте”, давать оценки, данная структура должна быть заметно мощнее конкретного текста. Это, впрочем, хорошо известное явление, про которое написано очень много и подробно. Несомненно, письменность очень важна не только для древних языков, но и для современных. Однако переход автоматического генератора текстов от фильтрации совсем примитивных конструкций к “наследованию” чуть более развитой “семантической” структуры, свойственной многим текстам по заданной теме, скорее свидетельствует об успешной оптимизации использования вычислительной аппаратуры с целью удивить пользователей Интернета, чем о чём-то ещё.
В советском мультфильме “Трое из Простоквашино” (1978 года) есть эпизод с Галчонком и потальоном. Почтальон Печкин стучится в дверь, но дома только специально обученная птичка – Галчонок, который на стук реагирует одной и той же фразой.
– Кто там? – спрашивает Галчонок. (Это единственная фраза, которую он знает на тот момент.)
– Это я, почтальон Печкин, принёс заметку про вашего мальчика, – отвечает Печкин. Ничего не происходит, поэтому почтальон стучит снова.
– Кто там? – спрашивает Галчонок.
– Это я, почтальон Печкин, принёс заметку про вашего мальчика, – отвечает Печкин.
Цикл “запрос-ответ-пояснение” повторяется многократно. В какой-то момент Галчонок замечает муху на оконной раме и клюёт её, производя тем самым стук.
– Кто там? – спрашивает, вместо Галчонка, заскучавший Печкин.
– Это я. Почтальон Печкин. Принёс заметку. Про вашего мальчика, – будто телетайпом отбивает ответ Галчонок.
И тут столкновение могучих интеллектов оканчивается поражением Печкина: эмоционально потрясённый, почтальон теряет сознание.
Комментарии (3) »
– Почему криптосистема называется X25519, откуда это 25519?
– Потому что там присутствует 2^255 и 19. Вот только “плюс” или “минус”? А, понятно, конечно, “минус”, поскольку 2^255+19 делится на 3, что очевидно.
Число, являющееся “модулем” в данной криптосистеме, должно быть простым. Соответственно, 2^255 – 19. Но выбор в большей степени обусловлен тем, что такое число имеет удобное двоичное представление: там много единичных битов подряд. Тем не менее, из наблюдения про 2^255 + 19 можно сделать задачу к Новому году: докажите, что 2^2023 + 2023 делится на три.
(Решение: записка и комментарии ниже.)
Комментарии (5) »
Иногда открытый ключ нужно буквально вводить руками, например, через “воздушный зазор”. Открытый ключ в ECDSA (и, кстати, в ГОСТ-подписи, но не только там) – это точка на кривой, заданной в параметрах. Речь тут про кривые, используемые в криптосистемах на практике, например, P-256. Координаты точки – это два числа X, Y (сторого говоря, два элемента соответствующего конечного поля, но это технические детали). Если разрядность 256 бит, то может показаться, что для передачи ключа придётся руками вводить 2*32 == 64 байта, что в hextext составит аж 128 знаков. Однако обычно можно ограничиться одной координатой X, а Y – вычислить из уравнения кривой. Такой способ кодирования называется сжатым представлением ключа. Единственная хитрость в том, что, так как в уравнение кривой Y входит в квадрате, координат, соответствующих данному X, пара. Это обратные по сложению элементы (всем привычные +/-). Поэтому нужно передать знак элемента, но для передачи знака достаточно одного дополнительного бита. Поэтому сжатое представление в два раза короче, но “разрядность ключа” при этом никак не страдает (страдают вычислительные затраты на принимающей ключ стороне, но этим часто можно пренебречь; иногда, впрочем, приключаются проблемы с обработкой заведомо неверных значений). Кстати, по этим же причинам ключи криптосистем с ed25519 короче в записи – они изначально “в сжатой форме”.
Комментировать »
Некоторое время назад я написал утилиту, кодирующую произвольные байтовые значения в строки англосаксонских рун. Используется кодирование, эквивалентное Base32, но с алфавитом из рун. Алфавит, понятно, может быть любым, но в случае рун для отображения в привычных компьютерных системах потребуется поддержка Unicode.
Таблицы Unicode – весьма богатое нововведение. Unicode позволяет записывать в тексте числа древними шумерскими цифрами, вот так: 𒁹 𒌍𒐋 𒌋𒌋𒐈. (Если на вашем устройстве эти цифры не отображаются, то, вероятно, устройство не содержит подходящих шрифтов; такое всё ещё возможно; более того, мне пришлось столкнуться с существенными трудностями при размещении этой записки в WordPress – стандартный редактор данной CMS отказывался принимать соответствующие символы, пришлось применять некоторые хитрости.)
В принципе, шумерская (вавилонская) система записи – позиционная, по основанию 60, но имеет свои особенности, создающие неоднозначности при интерпретации: там используется плавающая “шестидесятеричная точка”, которая не обозначается; кроме того, в классическом варианте, нет цифры для нуля. 𒁹 𒌍𒐋 𒌋𒌋𒐈, в зависимости от контекста, можно интерпретировать не только очевидным способом, как 5783 (1*60^2 + 36*60 + 23), но и, например, как 96.38333(3) (1*60 + 36 + 23/60). Клинописные цифры, соответствующие числам от 1 до 59, записывались засечками; так, 𒐈 – это 3, а 𒌋𒌋 – это 2*10, то есть, 20 (𒌋 обозначает 10). Unicode, – по крайней мере, в теории, – позволяет все эти засечки напечатать и вывести на экран компьютера в виде текста, благодаря наличию разнообразных кодовых таблиц, среди которых есть и таблица с шумерскими древними цифрами.
Именно засечки, штрихи и прочие дополнительные “чёрточки” (умляуты и тому подобные знаки) Unicode создают исключительные проблемы при преобразовании символов. Рассмотрим в качестве примера кириллическую букву “И” с “краткой”, то есть, “Й” (“кратка” – это чёрточка над “И”). Правила Unicode позволяют обозначить данную букву как одним кодом (Й), так и комбинацией из двух – из сочетания кода буквы “И” (без “кратки”) и отдельного кода, обозначающего “кратку”, который предусмотрен в Unicode. То есть, одна буква расщепляется на два представления! Фольклорное фонетическое восприятие букв сталкивается с универсальным “чисто топологическим” и с треском прогрывает последнему. Результат, вообще говоря, может доставить неожиданных проблем разработчику программного кода, выполняющего преобразования кодировок. Поэтому в тех случаях, когда нужно запись Unicode приводить к другим кодировкам, которые не сохраняют разделение по принципам начертания, используются те или иные соглашения о нормализации, предназначение которых состоит в том, чтобы согласованным способом привести наборы кодов к единому символу. Это один из самых нетривиальных моментов в практике Unicode.
Комментировать »
Новый