программирование :: разное

Подход к созданию трудноанализируемых шифров


Подход к созданию трудноанализируемых шифров

В данной статье рассматривается подход к созданию криптоалгоритмов, преобразующих исходный текст таким образом, чтобы трудоемкость его анализа была не ниже полного перебора всего множества ключей, а также невозможна осмысленная модификация зашифрованного сообщения без точного знания ключа.

В отличие от некоторых других статей, описывающих “доисторические” криптоалгоритмы, единственное достоинство которых в том, что их использование не надо лицензировать у спецслужб, рассматриваемый симметричный криптоалгоритм базируется на современной теории двоичных полиномов и обеспечивает невозможность анализа шифрованного текста существующими технологиями. Тут уже никакой “хакер” не сможет взломать шифр ни на бумажке, ни на своем энном пентиуме, а при выборе соответствующей длины ключа, шифр невозможно будет взломать всеми вычислительными средствами планеты в разумные сроки. Описываемый алгоритм прост и легко реализуем, работает достаточно быстро, поскольку не использует трудоемких операций вроде умножения и деления, время его выполнения линейно зависит от объема текста и несущественно зависит от длины ключа (но существенно зависит от вида полинома). Алгоритм легко реализуем аппаратно.

1. Зачем это надо?
1.1 Да, это надо

А зачем надо шифроваться вообще? Есть ли что скрывать обычному законопослушному гражданину от "всесильных" спецслужб (или разных темных личностей)? Представляет ли ценность для кого-нибудь банальная личная жизнь обычных людей?

Думаю, лишний раз трогать хакеров и кракеров не надо, это, наверно, всем порядком поднадоело, а вот вспомнить недавний скандал в Европарламенте, связанный с системой тотального электронного шпионажа “Эшелон”, стоит (статья в одном из номеров журнала “Эхо Планеты” за этот год). В Европарламенте обсуждался вопрос о том, что же все-таки делать дальше, ведь никто не собирался выкидывать “Эшелон” на свалку только из-за того, что Европарламенту это не понравилось! И одним из выходов было признание необходимости тотального использования криптосистем.

А насчет того, интересна ли жизнь обычных людей спецслужбам, стоит вспомнить статью, промелькнувшую на страницах одной из компьютерных газет (автора не помню, извиняйте;). В ней высказывалось предположение о том, что личная переписка и тому подобное запоминается и хранится в архивах спецслужб, и когда этот человек становится чем-то интересен, анализируется вся его личная жизнь от и до и становятся понятными особенности его психики, поведения, выбор оптимальных воздействий, чтобы вынудить человека совершить нужный поступок. Не сомневаюсь, доля истины в таком предположении есть.

В локальных сетях прослушать весь трафик своего сегмента сможет даже начинающий ламер;).

Да, еще. Про существование всяких средств перехвата излучения мониторов и т.п. читал много, а вот про реальное их использование для отлова преступников не слышал. Все сводилось к банальному взводу ОМОНА (или что у них там) и “Руки от клавы!” с выносом сервера. Да и то, насколько помню, проваливались такие преступники из-за своей жадности, а не из-за того, что кто-то что-то у них подслушал. Вполне может быть, что такие случаи не афишируются, и поэтому информации минимум.

1.2 Основная идея

Некоторыми из требований, предъявляемых к криптосистемам, являются [1]:

  • число операций, необходимых для определения использованного ключа шифрования по фрагменту шифрованного сообщения и соответствующего ему открытого текста, должно быть не меньше общего числа возможных ключей;

  • незначительное изменение ключа должно приводить к существенному изменению вида зашифрованного сообщения даже при использовании одного и того же ключа.

  • То есть криптоалгоритм должен обеспечить невозможность анализа зашифрованного текста. Задачи криптоанализа, в частности, состоят в установлении зависимостей между зашифрованным и исходным текстом (или его фрагментом) без знания ключа. Криптоалгоритм же надо строить таким образом, чтобы трудоемкость анализа была не ниже полного перебора всего множества возможных ключей.

    Идея такова: обеспечить влияние любого бита текста на все остальные таким образом, чтобы, имея два одинаковых исходных текста, изменив любой бит (или группу битов) одного текста, на выходе получить два совершенно различных текста (в идеале отличающихся между собой как две случайные последовательности). Аналогичные требования предъявляются к обратному преобразованию и к изменению ключа.

    2. Немного теории

    Одними из этапов шифрования в симметричных криптосистемах является гаммирование и гаммирование с обратной связью.
    Гаммирование — наложение гаммы (обычно псевдослучайной последовательности) на текст обратимым образом.
    Гаммирование с обратной связью — значение зашифрованного символа зависит не только от гаммы, но и от предыдущих символов.
    Остальные этапы рассматривать не будем, поскольку для них ничего нового предложено не будет, и их без проблем можно будет добавить к предлагаемому алгоритму.
    Наша задача состоит в том, чтобы обеспечить такое гаммирование с обратной связью, которое бы приводило к полному изменению текста на выходе даже при несущественном изменении входного. Рассмотрим немного теории (излагается сокращенно по [2]).

    2.1 М-последовательности

    Одним из наиболее широко применяемых способов формирования псевдослучайных последовательностей является способ, основанный на использовании соотношения:

    (2.1)

    где k — номер такта; a {0, 1}-символы последовательности;
    a {0, 1}-постоянные коэффициенты;

    — операция суммирования по модулю два m логических переменных.

    При соответствующем выборе коэффициентов a i , на основании характеристического полинома j (x)=1Å a 1 x 1Å a 2 x 2a 3 x 3 ...a m-1 x m-1a m x m , который должен быть примитивным, последовательность бит {a k } имеет максимальную длину, равную 2 m-1 . Такая последовательность называется M-последовательностью.

    Главное преимущество метода формирования псевдослучайных последовательностей по соотношению (2.1) — простота его реализации как программной, так и аппаратурной.

    Некоторыми из наиболее важных для нас свойств М-последовательностей являются:

  • максимальная близость М-последовательности по характеристикам к случайной;

  • период последовательности, формируемой по выражению (2.1), определяется старшей степенью порождающего полинома j (x) и равен L = 2 m -1;

  • для заданного полинома j (х) существует L различных
    M-последовательностей, отличающихся фазовым сдвигом.

  • 2.2 Сжатие двоичной последовательности в сигнатуру

    Сигнатура — это небольшой объем информации, максимально характеризующей длинную двоичную последовательность. Сущность сигнатуры в том, что для двух различных последовательностей, сигнатуры, характеризующие их, с очень большой вероятностью различаются.

    Для сжатия двоичной последовательности в сигнатуру используем следующее соотношение:

    (2.2)

    где y(k) ({0,1} — k-й символ сжимаемой последовательности {y(k)}, k = 1..l; a {0,1} — коэффициенты порождающего полинома j (x); a i (k-1)Î {0,1} — содержимое i-го элемента памяти в k-1-й такт. Процедура сдвига информации описывается соотношением

    a j (k) = a j-1 (k-1), j = 2..m.

    Для описания процедуры сжатия информации, основанной на применении примитивных полиномов, используются различные математические модели и алгоритмы. Одной из наиболее часто применяемых является модель, реализующая идею представления процедуры сжатия информации как операцию деления полиномов над полем GF(2). При этом в качестве делимого используется поток сжимаемых данных, описываемых полиномом c (x) степени l-1, где l-количество бит в последовательности. Так, например, последовательность 10011 имеет вид полинома c (х) = x xÅ 1. Делителем служит примитивный полином y (х), в результате деления на который получается частное q(х) и остаток S(х), связанные классическим соотношением вида

    c (x) = q(x)y (x) (S(x), (2.3)

    где остаток S(х), представляющий собой полином степени, меньшей, чем
    m = deg y (x), называется сигнатурой.

    Результат сжатия C(x) (m-разрядное число, содержащееся в a 1 ..a m ), получающийся по выражению (2.2), не совпадает с S(x), C(x)¹ S(x), но линейно с ним связан.

    Каковы же вероятности того, что сигнатуры различаются для двух различных последовательностей? Сигнатуры различаются для двух последовательностей любой длины l, отличающихся на один бит, и для всех последовательностей длиной l£ 2 m -1, отличающихся на два любых бита [2]. Для n-кратных изменений (l=2 m -2), при достаточно большом m, сигнатуры различаются с вероятностью (1-1/2 m , что при m>7 практически равно единице. Единственный параметр, влияющий на значения вероятности, — старшая степень m полинома j (х). Дальнейшие упоминания о таких вероятностях будут менее точны, поэтому когда надо будет уточнить, обращайтесь к этому абзацу.

    3. Общий вид алгоритма

    Для того чтобы изменение одного бита (или группы битов) воздействовало на все остальные, поступим следующим образом: будем вычислять сигнатуру двоичной последовательности и одновременно некоторой функцией преобразовывать биты, сигнатура для которых уже рассчитана. Естественно, эта функция должна иметь соответствующую ей обратную функцию для восстановления исходного текста. Преобразовывать можно как один бит, так сразу и группу битов. Шаг алгоритма может быть длиной как в один бит, так и в несколько бит.

    Для простоты понимания рассмотрим алгоритм, с функцией, преобразующий один бит, и шагом в один бит.
    k — длина последовательности, бит;
    m — старшая степень полинома для расчета сигнатуры, а также количество бит в элементах памяти (prev и next), хранящих сигнатуру;
    сигнатура (предыдущая сигнатура, новый бит) — функция, осуществляющая сжатие последовательности в сигнатуру;
    f(x, a i ) — функция, осуществляющая преобразование бита a i некоторым числом x;
    a i — i-ый бит последовательности.

    Базовое преобразование (прямой ход) будет выглядеть так: (3.1)
    prev = 0;
    для i от 1 до k
    {
    next = сигнатура (prev, a i );
    a i = f (prev, a i );
    prev = next;
    }

    Обратное преобразование (прямой ход) будет выглядеть так: (3.2)
    prev = 0;
    для i от 1 до k
    {
    a i = f -1 (prev, a i );
    prev = сигнатура(prev, a i );
    }

    Как видим, изменение бита a i воздействует на все последующие биты, и вероятность того, что изменение этого бита отразится на последующих, очень близка к единице. А что же делать с битами, предшествующими a i , ведь изменение их не затронет? Поступим просто — совершим такое же преобразование повторно, но в обратном направлении.

    Базовое преобразование, обратный ход: (3.3)
    prev = 0;
    для i от k до 1
    {
    next = сигнатура(prev, a i );
    a i = f(prev, a i );
    prev = next;
    }

    Обратное преобразование, обратный ход: (3.4)
    prev = 0;
    для i от k до 1
    {
    a i = f -1 (prev, a i );
    prev = сигнатура(prev, a i );
    }

    Преобразования (3.1) и (3.3) разносят изменения в любой точке последовательности на всю последовательность целиком таким образом, что каждый бит становится связанным со всеми остальными и его изменение повлечет изменение остальных битов с вероятностью очень близкой к единице. Для восстановления исходного текста надо применить последовательно преобразования (3.2) и (3.4).

    Но вышеописанное обеспечивает полноценное воздействие изменений только при преобразовании исходного текста в зашифрованный, при обратном преобразовании воздействие изменений в зашифрованном тексте на восстанавливаемый исходный будет слабо. Решается это просто — вводим дополнительно после (3.1) и (3.3) преобразования, аналогичные (3.2) и (3.4), только используем не f —1 (x, y), а f(x, y). Выглядеть это будет так:

    Зашифровка

    фаза 1 — защита от изменений в исходном тексте (3.5)
    // прямой ход
    prev = 0;
    для i от 1 до k
    {
    next = сигнатура(prev, a i );
    a i = f(prev, a i );
    prev = next;
    }
    // обратный ход (3.6)
    prev = 0;
    для i от k до 1
    {
    next = сигнатура(prev, a i );
    a i = f(prev, a i );
    prev = next;
    }

    фаза 2 — защита от изменений в зашифрованном тексте (3.7)
    // прямой ход
    prev = 0;
    для i от 1 до k
    {
    a i = f(prev, a i );
    prev = сигнатура(prev, a i );
    }
    // обратный ход (3.8)
    prev = 0;
    для i от k до 1
    {
    a i = f(prev, a i );
    prev = сигнатура(prev, a i );
    }

    Расшифровка (3.8) -1
    // обратный ход
    prev = 0;
    для i от k до 1
    {
    next = сигнатура(prev, a i );
    a i = f -1 (prev, a i );
    prev = next;
    }
    // прямой ход (3.7) -1
    prev = 0;
    для i от 1 до k
    {
    next = сигнатура(prev, a i );
    a i = f -1 (prev, a i );
    prev = next;
    }
    // обратный ход (3.6) -1
    prev = 0;
    для i от k до 1
    {
    a i = f -1 (prev, a i );
    prev = сигнатура(prev, a i );
    }
    // прямой ход (3.5) -1
    prev = 0;
    для i от 1 до k
    {
    a i = f -1 (prev, a i );
    prev = сигнатура(prev, a i );
    }

    Одной из ранних идей преобразований подобного рода была такая: текст разбивается на две половинки, вычисляется сигнатура первой половины и с помощью этой сигнатуры преобразуется вторая половина, затем наоборот. Каждая из этих половинок опять разбивается на две, и преобразование повторяется рекурсивно, пока длина половинки не станет меньше одного бита (или байта). Время такого преобразования — порядка двоичного логарифма от длины последовательности.

    Следует отметить, что для каждого из четырех базовых преобразований можно использовать различные функции преобразования и полиномы для расчета сигнатур, что может повысить криптостойкость.

    Как можно было заметить, преобразование осуществляется без какого-либо привлечения ключа, и имеет место однозначное соответствие исходной и преобразованной последовательности. Изменив вид функции на f (сигнатура i , g i , a i ), где g — псевдослучайная последовательность, сгенерированная на основе ключа (или сам ключ, но это снизит криптостойкость), получим зависимость преобразованной последовательности от ключа. Для каждого из преобразований можно использовать различные участки гаммы. Причем из вышеописанного следует то, что малейшее изменение ключа приведет к глобальному изменению всей преобразованной последовательности.

    По криптостойкости прямое и обратное преобразование симметричны, поэтому можно пользоваться обратным преобразованием для зашифровки и прямым для расшифровки.

    Такое преобразование уже не назовешь гаммированием с обратной связью, как это описано в [1], это уже скорее свертка.

    Итак, данный алгоритм обеспечивает следующие возможности: если взять две идентичные последовательности, два идентичных ключа и внести изменение (даже очень малое) в ключ или в исходную или в преобразованную последовательности, то после соответствующего преобразования две полученные последовательности будут различаться между собой как две случайные. Соответственно анализ зашифрованного и соответствующего ему исходного текста с целью определить ключ мало что даст.

    Данный алгоритм можно применить для потокового шифрования данных. Поскольку конечная часть последовательности в реальном времени недоступна, преобразования (3.6) и (3.8) следует исключить, а преобразования (3.5) и (3.7) объединить. С одной стороны, это повлияет на криптостойкость, поскольку нет влияния последующих битов на предыдущие, но с другой стороны, конечная часть последовательности также недоступна для криптоанализа. Если процесс передачи данных допускает буферизацию хотя бы нескольких бит, то преобразования (3.6) и (3.8) можно модифицировать таким образом, чтобы они осуществлялись в пределах буфера. Следует предусмотреть процесс восстановления сигнатур в случае утери или искажения части сообщения, но это уже выходит за рамки статьи. Простейший способ — установка сигнатур в начальные (константные или определяемые по некоторому закону) значения после прохождения определенных объемов данных и вставка синхронизирующей метки в последовательность. Выглядеть все это будет приблизительно так:

    // зашифровка
    prev = prev2 = 0;
    пока (не конец последовательности)
    {
    next = сигнатура(prev, a i );
    a i = f(prev, g i , a i );
    prev = next;
    a i = f(prev2, g ’ i , a i );
    prev2 = сигнатура(prev2, a i );
    }
    // расшифровка
    prev = prev2 = 0;
    пока (не конец последовательности)
    {
    next2 = сигнатура(prev2, a i );
    a i = f -1 (prev2, g ’ i , a i );
    prev2 = next2;
    a i = f -1 (prev, g i , a i );
    prev = сигнатура(prev, a i );
    }

    4. Программная реализация (язык С)

    Примечание: предполагается что типы int и unsigned имеют размер 4 байта.
    Пример функции, генерирующей побитно псевдослучайную последовательность:

    // примитивный неприводимый полином для формирования
    // М-последовательности с периодом (2^11)-1
    // fi(x) = 1(+)x^2(+)x^11
    unsigned NextMValue(unsigned prev)
    {
    register unsigned c = 0;

    if(prev & (1<<1))
    c++;

    if(prev & (1<<10))
    c++;
    return (prev<<1)|(c&1);
    }

    Начальное значение параметра prev может быть любым, отличным от нуля, новый бит последовательности хранится в нулевом бите возвращаемого значения.

    Пример функции, побитно сжимающей двоичную последовательность в сигнатуру:
    // примитивный неприводимый полином
    // для вычисления 32-битной сигнатуры
    // fi(x) = 1(+)x^1(+)x^27(+)x^28(+)x^32
    unsigned NextSignature32
    (unsigned prev, unsigned newbit)
    {
    if(prev&(1<<0))
    newbit++;
    if(prev&(1<<26))
    newbit++;
    if(prev&(1<<27))
    newbit++;
    if(prev&(1<<31))
    newbit++;
    return (prev<<1)|(newbit&1);
    }

    Для генерации гаммы на основании ключа будем применять следующую функцию:

    unsigned char * generate_gamma(unsigned char *pw, unsigned pwlen, unsigned gamma_len, unsigned char * gamma = NULL)
    {
    const
    gb_len = 256;
    static int
    polynomial[] = { 1, 5, 23, 171, 243, 1057, 2047, -1 };
    //polynomial[] = { 1, 18, 20, 39, -1 }; // период 2^40 — 1
    static unsigned char
    buf[gb_len+1];

    memset(buf, 0, sizeof(buf));
    if(pwlen)
    for(int i = 0; i < gb_len; i++)
    buf[i+1] = pw[i%pwlen];
    else
    buf[1] = 1;

    if(!gamma)
    gamma = new unsigned char [gamma_len];

    for(int i = 0; i < gamma_len; i++) {
    buf[0] = 0;
    for(int j = 0; j < 8; j++) {
    register unsigned char newbit = 0;
    for(int k = 0; polynomial[k] >= 0; k++) {
    register unsigned bit = (polynomial[k]+8-j);
    if(buf[bit>>3]&(1<<(bit&7)))
    newbit++;
    }
    buf[0] |= (newbit&1)<<(7-j);
    }
    memmove(buf+1, buf, gb_len);
    gamma[i] = buf[0];
    }
    return gamma;
    }

    Эта функция может оперировать любым полиномом. Приведен полином степени 2048, или 256 байт. Задавать пароль длиннее 256 байт не имеет смысла, поскольку это никак не отразится на генерируемой гамме. Конечно, можно предварительно подвергнуть пароль свертке, но ведь тогда для вскрытия можно будет перебирать только 256 байт из свертки, которые и используются для генерации гаммы. Полином взят “с потолка”, поэтому для надежной генерации гаммы следует использовать примитивный неприводимый полином. В комментариях указан примитивный полином степени 40, для которого вручную была проверена правильность работы процедуры. Правда, максимальная длина ключа для него всего лишь пять байт :(.

    В [1] указывались недостатки в применении М-последовательностей для генерации гаммы, и предлагалось использовать последовательности Голда, образующиеся суммированием нескольких М-последовательностей.

    Для простоты наш алгоритм будет работать с байтами, и сигнатура будет вычисляться сразу для всего байта.

    Для сжатия последовательности в сигнатуру используем известную и оптимизированную функцию crc32 (прилагается с исходными текстами):
    unsigned crc32(unsigned crc, unsigned char *buf, unsigned buflen);

    Функции зашифровки/расшифровки:

    void encrypt(unsigned char * buf, unsigned buflen, unsigned char * gamma)
    {
    unsigned prev, next;
    unsigned char * pgamma;

    // фаза 1
    // защита от изменений в исходном тексте
    // свертка 1 — прямой ход
    prev = 0xffffffff;
    pgamma = gamma;
    for(int i = 0; i < buflen; i++) {
    next = crc32(prev, buf+i, 1);
    next = crc32(next, pgamma+i, 1); // гаммирование
    buf[i] += prev;
    prev = next;
    }

    // свертка 2 — обратный ход
    prev = 0xffffffff;
    pgamma = gamma+buflen;
    for(int i = buflen-1; i >= 0; i—) {
    next = crc32(prev, buf+i, 1);
    next = crc32(next, pgamma+i, 1); // гаммирование
    buf[i] += prev;
    prev = next;
    }

    // фаза 2
    // защита от изменений в шифрованном тексте
    // свертка 3 — прямой ход
    prev = 0xffffffff;
    pgamma = gamma+(buflen<<1);
    for(int i = 0; i < buflen; i++) {
    buf[i] += prev;
    prev = crc32(prev, buf+i, 1);
    prev = crc32(prev, pgamma+i, 1); // гаммирование
    }

    // свертка 4 — обратный ход
    prev = 0xffffffff;
    pgamma = gamma+(buflen<<1)+buflen;
    for(int i = buflen-1; i >= 0; i—) {
    buf[i] += prev;
    prev = crc32(prev, buf+i, 1);
    prev = crc32(prev, pgamma+i, 1); // гаммирование}
    }

    void decrypt(unsigned char * buf, unsigned buflen, unsigned char * gamma)
    {
    unsigned prev, next;
    unsigned char * pgamma;

    // развертка 4 — обратный ход
    prev = 0xffffffff;
    pgamma = gamma+(buflen<<1)+buflen;
    for(int i = buflen-1; i >= 0; i—) {
    next = crc32(prev, buf+i, 1);
    next = crc32(next, pgamma+i, 1); // гаммирование
    buf[i] -= prev;
    prev = next;
    }

    // развертка 3 — прямой ход
    prev = 0xffffffff;
    pgamma = gamma+(buflen<<1);
    for(int i = 0; i < buflen; i++) {
    next = crc32(prev, buf+i, 1);
    next = crc32(next, pgamma+i, 1); // гаммирование
    buf[i] -= prev;
    prev = next;
    }

    // развертка 2 — обратный ход
    prev = 0xffffffff;
    pgamma = gamma+buflen;
    for(int i = buflen-1; i >= 0; i—) {
    buf[i] -= prev;
    prev = crc32(prev, buf+i, 1);
    prev = crc32(prev, pgamma+i, 1); // гаммирование
    }

    // развертка 1 — прямой ход
    prev = 0xffffffff;
    pgamma = gamma;
    for(int i = 0; i < buflen; i++) {
    buf[i] -= prev;
    prev = crc32(prev, buf+i, 1);
    prev = crc32(prev, pgamma+i, 1); // гаммирование
    }
    }

    Функция шифрования файла:
    int cryptfile(int encrypt, char *fn, unsigned char *pw, unsigned pwlen)
    {
    FILE * f = fopen(fn, "r+b");
    unsigned char * buf, * gamma;
    unsigned buflen;

    if(!f)
    return -1;

    buflen = filelength(f->fd);
    buf = new char[buflen];
    gamma = generate_gamma(pw, pwlen, buflen<<2);

    fread(buf, buflen, 1, f);
    if(encrypt)
    encrypt(buf, buflen, gamma);
    else
    decrypt(buf, buflen, gamma);

    rewind(f);
    fwrite(buf, buflen, 1, f);

    memset(buf, 0xff, buflen);
    memset(buf, 0, buflen);
    memset(gamma, 0xff, buflen<<2);
    memset(gamma, 0, buflen<<2);

    delete buf;
    delete gamma;

    fclose(f);
    return 0;
    }

    Проводился небольшой анализ на совпадение байт в преобразованных таким образом файлах. Для четырех файлов, исходного, с измененным одним битом в начале, конце и середине файла, совпадение байт (попарно, каждый с каждым) составило менее 0.5%. После сжатия архиватором RAR всех четырех файлов в один solid-архив размер каждого файла увеличился (вот так;). В прилагаемой программе можно самостоятельно поэкспериментировать с шифровкой различных файлов. Имеется команда сравнения двух файлов. При преобразовании двух файлов они автоматически сравниваются. Попробуйте изменить несколько бит, посмотрите что будет. Кому мало, может усовершенствовать исходные тексты и экспериментировать дальше.

    Заключение

    Описанный алгоритм достигает поставленные цели.

    Для использования описанного алгоритма в серьезных целях необходимо строгое доказательство того, что любой анализ зашифрованного таким образом текста по трудоемкости будет не ниже перебора всего множества ключей, а также оценка зависимости времени перебора от длины ключа. Как уже упоминалось, для генерации гаммы необходимо выбрать примитивный неприводимый полином соответствующей степени.

    Сама программа и исходные тексты прилагаются.

    Спасибо за внимание. Ваше мнение?

    Литература

  • 1. Баpичев Сеpгей. Kpиптогpафия без секpетов (где искать не знаю, у самого только электронный вариант).

  • 2. Ярмолик В. Н. Контроль и диагностика цифровых узлов ЭВМ. — Минск: Наука и техника, 1988. — 240 с.

  • 3. Ярмолик В. Н., Демиденко С. Н. Генерирование и применение псевдослучайных сигналов в системах испытаний и контроля. — Минск: Наука и техника, 1986. — 200 с.

  • Дмитрий Брилюк
    bdv78@newmail.ru

    (c) Компьютерная газета



    © компьютерная газета