Пару месяцев назад, имея за плечами многолетний опыт программирования на ассемблере, Delphi и PHP, я решил освоить также язык C.
Говорят, что C — переносимый ассемблер. Собственно, он именно так и возник — чтобы перенести на другую платформу коды операционной системы, изначально написанной на ассемблере (речь идет об архитектуре PDP, с которой и мне в 80-е годы прошлого века, в начальный период моей сознательной деятельности, приходилось сталкиваться — я тогда писал для нее в академическом НИИ программы для автоматизации физического эксперимента в области прикладной ядерной физики на ассемблере и Фортране).
Было это очень давно, еще где-то в районе 1970-го года, но все равно — интересно. Ведь это стало частью всемирной истории IT. И этот факт, я считаю, должны знать все, кто занимается программированием.
Короче говоря, именно аспект переносимости, или иными словами — кросс-платформенности, меня во всем этом деле в первую очередь и заинтересовал. И, забегая вперед, могу сказать — ни коим образом не обманул моих ожиданий. Все, что я сейчас пишу на C — превосходным образом компилируется и исполняется (при этом — совершенно идентично) и на Linux, и на Windows. Других платформ у меня нет, но в идентичности результатов на них я тоже не сомневаюсь.
Начал я, конечно, как и положено, с «Hello, world», но достаточно быстрыми темпами пошел вперед — что и не удивительно, учитывая объем моего предыдущего опыта.
Для начала, я написал программу поиска простых чисел через решето Эратосфена (взяв за основу ранее написанную мной программу на ассемблере). Потом приделал туда многопоточность, которая работала и под Linux, и под Windows. Причем, исключительно опираясь на возможности самой операционной системы, без использования каких бы то ни было чужих библиотек (конечно, для этого через #define пришлось вызывать разные сервисы на разных платформах).
Это была моя первая программа на C. По сути дела, я просто повторил то, что ранее у меня было написано на NASM. Цель, которую я перед собой ставил, заключалась в том, чтобы просто освоить новый для себя язык. Набить руку, вжиться, сродниться с ним. Мне нужно было его ощутить. С ассемблером, Delphi и PHP я давно на «ты». Этим трем языкам я, как Маугли джунглям, могу смело сказать «мы с тобой одной крови». Я пишу на них совершенно свободно, но я никак не могу пока сказать это про язык С. Пройдет какое-то время, я напишу на нем определенное количество тысяч строк кода — и тогда я тоже смогу сказать ему эту фразу. Но это время пока не пришло.
Так что, программа поиска простых чисел была лишь ступенькой на пути моего обучения. Она, кстати сказать, работала с целыми числами, целиком умещавшимися в одном регистре процессора — всего 64 бита (unsigned long в Линуксе и unsigned long long в Windows — тут тоже не обошлось дело без #define). Но моей целью был алгоритм RSA (который я, для тренировки, тоже на тот момент реализовал на ассемблере в рамках 64-х бит). При этом надо отметить, что имея в своем распоряжении 64 бита, я мог позволить себе работать только с ключами длиной 32 бита — такова особенность RSA, поскольку там числа постоянно перемножаются, а произведение двух 32-битных чисел дает как раз 64 бита. Конечно, это никуда не годилось, кроме как для целей просто изучить программирование на C.
Поэтому я понял, что нужно написать модуль длинной математики. Когда-то я уже таким занимался, и поэтому решил взять за основу свои прошлые разработки (как всегда, на ассемблере). Суть в том, что в массиве байтов (в C это обозначается типом char) просто располагаются цифры, из которых состоит число. Так, как они пишутся на бумаге. И я написал на C такой модуль, отладил его и убедился, что все отлично работает. Длина чисел могла быть любой (максимальный размер просто задавался через #define). Модуль обеспечивал реализацию сложения, вычитания, умножения и деления чисел фактически неограниченной длины. Это было превосходно, и я пел от счастья.
Однако, музыка играла недолго. Дело в том, что в RSA есть две ключевые операции Extended Euclidean Algorithm (расширенный алгоритм Евклида) и Power Modulo (возведение в степень по модулю). Так вот, если для первой операции мой модуль длинной математики полностью годился, то для второй, где нужно было делить на два (то есть, по сути, сдвигать вправо на один бит) — совершенно не подходил, поскольку длинные целые числа были записаны у меня в десятичной форме. А делить на два десятичное число без сдвига на один бит конечно можно, но уж очень долго. Такая реализация была бы совершенно непрактичной, поскольку работала бы слишком медленно. Поэтому я загрустил, потому что понял, что мой математический модуль для криптографии не годится. Ему может найтись место только в финансовой сфере — точно считать большие деньги он вполне в состоянии. А шифровать — никак.
Однако, посыпая голову пеплом и разглядывая код своего неудачного детища, я заметил, что на самом деле можно легко изменить основание системы исчисления простым #define. Я определил два параметра — RADIX и MAX_DIGIT и везде в коде заменил 10 на RADIX, а 9 на MAX_DIGIT. И тут мне стало хорошо. Ведь это дало мне возможность перейти от десятичной к шестнадцатиричной системе исчисления — а в этой системе я уже получил настоящий массив двоичных битов, которые можно было бы легко двигать туда и сюда. То есть, очень быстро умножать и делить на два. Иными словами, проблемы Power Modulo больше не существовало.
Я также освоил генерацию больших простых чисел с помощью OpenSSL на своем Линуксе, и таким образом полностью реализовал RSA (ключи генерировал уже сам). Испытания прошли успешно: после шифрования и расшифровки текст совпадал с исходным. Это была победа. Я использовал ключи размером 512 бит (произведение двух полученных у OpenSSL простых чисел размером 256 бит каждое), но моя технология, как я уже отмечал выше, сама по себе, никаких ограничений не имела — я мог использовать простые числа и ключи любого размера.
Но, как известно, для полноценной шифросвязи этого недостаточно. Секретная связь не использует RSA для передачи информации — для этого RSA слишком медленный алгоритм. RSA используется только для согласования сеансового ключа, а дальше используется RC5, RC6, AES. Вот для этого я последовательно и реализовал на C все эти три алгоритма. Для того, чтобы попрактиковаться, и «что бы было». Пригодится на всякий случай.
Теперь о ключе. Как я это устроил. Каждая из сторон, после обмена открытыми ключами, генерирует свою случайную последовательность бит (используя при этом криптостойкий алгоритм). Бит этих ровно столько, сколько требуется для ключа выбранного алгоритма. Например, для AES-128 генерируется 128 бит (на каждой стороне свои). Далее каждая из сторон шифрует через RSA свою секретную последовательность бит открытым ключом партнера и посылает шифротекст ему. Получив шифротекст от партнера, каждая из сторон расшифровывает его своим закрытым ключом. Фокус здесь состоит в том, что никто больше этого сделать не может. Таким образом, каждый из партнеров имеет в своем распоряжении и свою, и чужую случайную последовательность. А злоумышленник, перехватывающий пакеты, не имеет ни одной из них. Далее партнеры складывают эти две последовательности по модулю два и получают одно и то же значение (поскольку эта операция коммутативна). Это и есть сеансовый ключ, который затем используется в соответствующем алгоритме (RC5, RC6 или AES).
Система проверена, отлажена, прекрасно работает с высокой скоростью (и в Linux, и на Windows, имея один и тот же код на чистом C). Я счастлив и доволен.