Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
6_Курс лекций МиСЗИ.doc
Скачиваний:
94
Добавлен:
11.04.2015
Размер:
420.35 Кб
Скачать

Методы и средства защиты информации

Методы и средства защиты информации

Лекция 6

Симметричные криптосистемы.

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

1.Функции криптосистем

Краткое описание того, для чего все же предназначены криптосистемы.

Все исследования, которые мы проводили на предыдущих лекциях, касались только криптоалгоритмов, то есть методов преобразования небольшого блока данных (от 4 до 32 байт) в закодированный вид в зависимости от заданного двоичного ключа. Криптоалгоритмы несомненно являются "сердцем" криптографических систем, но, как мы сейчас увидим, их непосредственное применение без каких-либо модификаций для кодирования больших объемов данных на самом деле не очень приемлемо.

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

  1. усиление защищенности данных,

  2. облегчение работы с криптоалгоритмом со стороны человека;

  3. обеспечение совместимости потока данных с другим программным обеспечением.

Конкретная программная реализация криптосистемы называется криптопакетом.

    1. Алгоритмы создания цепочек

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

Первая задача, с которой сталкиваются при шифровании данных криптоалгоритмом – это данные с длиной, не равной длине 1 блока криптоалгоритма. Эта ситуация будет иметь место практически всегда.

Первый вопрос:

– Что можно сделать, если мы хотим зашифровать 24 байта текста, если используется криптоалгоритм с длиной блока 8 байт?

– Последовательно зашифровать три раза по 8 байт и сложить их в выходной файл так, как они лежали в исходном.

– А если данных много и некоторые блоки по 8 байт повторяются, это значит, что в выходном файле эти же блоки будут зашифрованы одинаково - это очень плохо.

Второй вопрос:

– А что если данных не 24, а 21 байт? Не шифровать последние 5 байт или чем-то заполнять еще 3 байта, – а потом при дешифровании их выкидывать.

– Первый вариант вообще никуда не годится, а второй применяется, но чем заполнять?

Для решения этих проблем и были введены в криптосистемы алгоритмы создания цепочек (англ. chaining modes). Самый простой метод мы уже в принципе описали. Это метод ECB (Electronic Code Book). Шифруемый файл временно разделяется на блоки, равные блокам алгоритма, каждый из них шифруется независимо, а затем из зашифрованных пакетов данных компонуется в той же последовательности файл, который отныне надежно защищен криптоалгоритмом. Название алгоритм получил из-за того, что в силу своей простоты он широко применялся в простых портативных устройствах для шифрования – электронных шифрокнижках. Схема данного метода приведена на рис.1.

Рис.1.

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

Введем некоторые определения.

Хэш (Hash) - блок данных фиксированного размера, полученный в результате хэширования массива данных.

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

Хэш-функция (Hash-function) - функция, осуществляющая хэширование массива данных посредством отображения значений из (очень) большого множества значений в (существенно) меньшее множество значений. Подробнее о хэшировании – в следующей лекции.

Указанным выше недостатком этой схемы является то, что при повторе в исходном тексте одинаковых символов в течение более, чем 2*N байт (где N – размер блока криптоалгоритма), в выходном файле будут присутствовать одинаковые зашифрованные блоки. Поэтому, для более "мощной" защиты больших пакетов информации с помощью блочных шифров применяются несколько обратимых схем "создания цепочек". Все они почти равнозначны по криптостойкости, каждая имеет некоторые преимущества и недостатки, зависящие от вида исходного текста.

Все схемы создания цепочек основаны на идее зависимости результирующего зашифровываемого блока от предыдущих, либо от позиции его в исходном файле. Это достигается с помощью блока "памяти" – пакета информации длины, равной длине блока алгоритма. Блок памяти (к нему применяют термин IV – англ. Initial Vector) вычисляется по определенному принципу из всех прошедших шифрование блоков, а затем накладывается с помощью какой-либо обратимой функции (обычно XOR) на обрабатываемый текст на одной из стадий шифрования. В процессе раскодирования на приемной стороне операция создания IV повторяется на основе принятого и расшифрованного текста, вследствие чего алгоритмы создания цепочек полностью обратимы.

Два наиболее распространенных алгоритма создания цепочек – CBC и CFB. Их структура приведена на рис.2 и рис.3. Метод CBC получил название от английской аббревиатуры Cipher Block Chaining – объединение в цепочку блоков шифра (или сцепление блоков шифротекста), а метод CFB – от Cipher FeedBack – обратная связь по шифроблоку (или шифрование с обратной связью).

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

Исходный текст разбивается на блоки, а затем обрабатывается по следующей схеме:

  1. Первый блок складывается побитно по модулю 2 (XOR) с неким значением IV - начальным вектором (Initial Vector), который выбирается независимо перед началом шифрования.

  2. Полученное значение шифруется.

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

CFB. Алгоритм CFB позволяет использовать алгоритм DES с блоками исходной информации менее 64 бит.

Остальные режимы алгоритма DES используются гораздо реже первых двух.

Рис.2.

Рис.3.

Еще один метод OFB (англ. Output FeedBack – обратная связь по выходу) имеет несколько иную структуру (она изображена на рис.4.): в нем значение накладываемое на шифруемый блок не зависит от предыдущих блоков, а только от позиции шифруемого блока (в этом смысле он полностью соответствует скремблерам), и из-за этого он не распространяет помехи на последующие блоки. Очевидно, что все алгоритмы создания цепочек однозначно восстановимы. Любой блочный шифр в режиме OFB представляет собой синхронный поточный шифр, т.е. режим OFB фактически превращает начальный стандарт в потоковый шифр.

ECB (электронная кодовая книга). Это наиболее простой вариант: каждый блок исходного текста (64 бита) кодируется с помощью одного и того же 56-битового ключа. Непосредственно этот режим применяется для шифрования небольших объемов информации, размером не более одного блока или для шифрования ключей. Это связано с тем, что одинаковые блоки открытого текста преобразуются в одинаковые блоки шифротекста, что может дать взломщику (криптоаналитику) определенную информацию о содержании сообщения. К тому же, если он предполагает наличие определенных слов в сообщении (например, слово "Здравствуйте" в начале сообщения или "До свидания" в конце), то получается, что он обладает как фрагментом открытого текста, так и соответствующего шифротекста, что может сильно облегчить задачу нахождения ключа.

Основным достоинством этого режима является простота реализации.

Рис. 4.

Сравним характеристики методов создания цепочек (таб. 1)

Таблица 1.

Метод

Шифрование блока зависит от

Искажение одного бита при передаче

Кодируется ли некратное блоку число байт без дополнения?

На выход криптосистемы поступает

ECB

текущего блока

портит весь текущий блок

нет

выход криптоалгоритма

CBC

всех предыдущих блоков

портит весь текущий и все последующие блоки

нет

выход криптоалгоритма

CFB

всех предыдущих блоков

портит один бит текущего блока и все последующие блоки

да

XOR маска с исходным текстом

OFB

позиции блока в файле

портит только один бит текущего блока

да

XOR маска с исходным текстом

    1. Методы рандомизации сообщений

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

      1. Обзор методик рандомизации сообщений

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

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

В этом случае необходимо введение какой-либо случайной величины в процесс шифрования. Это можно сделать несколькими способами:

  1. записью в начало файла данных псевдослучайной последовательности байт заранее оговоренной длины с отбрасыванием ее при дешифровании – этот метод будет работать только при применении алгоритмов создания цепочек с памятью (CBC,CFB,OFB);

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

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

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

      1. Генераторы случайных и псевдослучайных последовательностей

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

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

Рассмотрим проблему создания случайных и псевдослучайных чисел более детально. Наиболее часто в прикладных задачах результат формируют из счетчика тиков – системных часов. В этом случае данные о текущем часе несут примерно 16 бит информации, значение счетчика тиков – еще 16 бит. Это дает нам 32 бита информации – как вы помните, на сегодняшний день границей стойкой криптографии является значение в 40 бит, при реальных длинах ключей в 128 бит. Естественно, подобного метода крайне недостаточно. Идем дальше, к 32 битам можно добавить еще 16 бит из сверхбыстрого таймера, работающего на частоте 1,2 МГц в компьютерах архитектуры IBM PC AT и этого еще недостаточно. Кроме того, даже если мы сможем набрать длину ключа в 128 бит (что очень сомнительно), она будет нести псевдослучайный характер, поскольку основана на состоянии только лишь данной ЭВМ на момент начала шифрования. Источниками по-настоящему случайных величин могут быть только внешние объекты, например, человек.

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

По первому методу над самими введенными значениями производятся действия, повышающие случайность выходного потока. Так, например, обязательно удаляются верхние 3 бита введенного ASCII символа, часто удаляются еще один верхний и еще один нижний биты. Затем, объем полученной последовательности уменьшается еще в три раза наложением первого и второго бита на третий операцией XOR. Это, в принципе, генерирует достаточно случайную последовательность бит.

По второму методу на введенные символы алгоритм не обращает никакого внимания, зато конспектирует интервалы времени, через которые произошли нажатия. Запись моментов производится по отсчетам быстрого системного таймера (частота 1,2 МГц) или внутреннему счетчику процессора, появившемуся в процессорах, начиная с Intel Pentium (частота соответствует частоте процессора). Так как верхние и младшие биты имеют определенную корреляцию между символами (первые из-за физических характеристик человека, вторые из-за особенностей операционной системы), то они отбрасываются (обычно удаляются 0-8 старших бита и 4-10 младших).

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

В мощных криптосистемах военного применения используются действительно случайные генераторы чисел, основанные на физических процессах. Они представляют собой платы, либо внешние устройства, подключаемые к ЭВМ через порт ввода-вывода. Два основных источника белого Гауссовского шума – высокоточное измерение тепловых флуктуаций и запись радиоэфира на частоте, свободной от радиовещания.

    1. Архивация

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

Алгоритмы сжатия данных очень хорошо подходят для совместного использования с криптографическими алгоритмами. На это есть две причины:

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

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

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

      1. Общие принципы архивации. Классификация методов

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

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

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

Все алгоритмы сжатия данных качественно делятся на:

1) алгоритмы сжатия без потерь, при использовании которых данные на приемной восстанавливаются без малейших изменений;

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

Алгоритмы обратимого сжатия данных можно разделить на две группы:

1) Алгоритмы частотного анализа – подсчет частоты различных символов в данных и преобразование кодов символов с соответствии с их частотой.

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

Существует два основных метода архивации без потерь:

  • алгоритм Хаффмана (англ. Huffman), ориентированный на сжатие последовательностей байт, не связанных между собой,

  • алгоритм Лемпеля-Зива (англ. Lempel, Ziv), ориентированный на сжатие любых видов текстов, то есть использующий факт неоднократного повторения "слов" – последовательностей байт.

Практически все популярные программы архивации без потерь (ARJ, RAR, ZIP и т.п.) используют объединение этих двух методов – алгоритм LZH.

Основными техническими характеристиками процессов сжатия и результатов их работы являются:

- степень сжатия (compress rating) или отношение (ratio) объемов исходного и результирующего потоков;

- скорость сжатия - время, затрачиваемое на сжатие некоторого объема информации входного потока, до получения из него эквивалентного выходного потока;

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

      1. Алгоритм Хаффмана

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

Алгоритм основан на том факте, что некоторые символы из стандартного 256-символьного набора в произвольном тексте могут встречаться чаще среднего периода повтора, а другие, соответственно, – реже. Следовательно, если для записи распространенных символов использовать короткие последовательности бит, длиной меньше 8, а для записи редких символов – длинные, то суммарный объем файла уменьшится.

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

В теории кодирования информации показывается, что код Хаффмана является префиксным, то есть код никакого символа не является началом кода какого-либо другого символа.

Пусть задан текст, в котором бурва 'А' входит 10 раз, буква 'В' - 8 раз, 'С'- 6 раз , 'D' - 5 раз, 'Е' и 'F' - по 4 раза. Тогда один из возможных вариантов кодирования по алгоритму Хаффмана приведен в таблицы 2.

Таблица 2.

Символ

Частота вхождения

Битовый код

A

10

00

B

8

01

C

6

100

D

5

101

E

4

110

F

4

111

Как видно из таблицы 1, размер входного текста до сжатия равен 37 байт, тогда как после сжатия - 93 бит, то есть около 12 байт (без учета длины словаря). Коэффициент сжатия равен 32%.

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

В качестве примера рассмотрим сообщение, составленное из шести символов A, B, C, D, E и F. Числа повторений следующие:

A

B

C

D

E

F

60

25

30

5

10

20

Возьмем два узла с самыми маленькими весами (D и E), образуем из них новый узел с весом 15 и добавим его в список. Узлы D и E исключим:

A

B

C

F

60

25

30

20

15

/

\

E

D

10

5

Возьмем два узла с наименьшими весами (только что созданный и F), образуем из них новый узел с весом 35 и добавим его в список. Взятые узлы исключим:

A

B

C

60

25

30

35

/

\

|

15

|

/

\

F

E

D

20

10

5

Возьмем два узла с наименьшими весами (B и C), образуем из них новый узел с весом 55 и добавим его в список. Взятые узлы исключим:

A

60

35

55

/

\

/

\

|

15

|

|

|

/

\

|

|

F

E

D

C

B

20

10

5

30

25

Возьмем два узла с наименьшими весами (35 и 55), образуем из них новый узел с весом 90 и добавим его в список. Взятые узлы исключим:

A

60

90

/

\

55

35

/

\

/

\

|

|

|

15

|

|

|

/

\

C

B

F

E

D

30

25

20

10

5

Возьмем два узла с наименьшими весами (A и 90) и образуем из них новый узел с весом 150 и добавим его в список. Взятые узлы исключим. В списке остался один узел - дерево построено:

150

/

\

90

|

/

\

|

55

35

|

/

\

/

\

|

|

|

|

15

|

|

|

|

/

\

|

C

B

F

E

D

A

30

25

20

10

5

   

60

Чтобы получить код Хаффмана для любого из символов нужно пройти от корня дерева к соответствующему этому символу листу, каждый переход влево - 0, вправо - 1. Так получим все биты кода. Реально приходится двигаться в обратном направлении - от листа к корню. Процедура получения кода похожа на преобразование числа в строку - последний символ строки получается первым. Следует заметить, что длина кода может превосходить длину простой переменной (16 или 32 бит). В данном примере коды следующие:

A:

1

B:

001

C:

000

D:

0111

E:

0110

F:

010

Декодирование символа начинается с корня дерева. Сообщение читается по одному биту, если прочитан 0 - переход по дереву влево, если 1 - вправо. По достижению листа определяется конец кода символа (и сам этот символ).

Другой пример- сравнение размера до и после архивации.

C = 00 (2 бита) A=1 (1 бит)

A = 0100 (4 бита) B=001 (3 бита) для

D = 0101 (4 бита) C=000 (3 бита)  нашего

F = 011 (3 бита) D=0111 (4 бита) примера

B = 10 (2 бита) E=0110 (4 бита)

E = 11 (2 бита) F=010 (3 бита)

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

Таблица 3

Частота

первоначально

уплотненные биты

уменьшено на

C 30

30 x 8 = 240

30 x 2 = 60

180

A 10

10 x 8 = 80

10 x 3 = 30

50

D 5

5 x 8 = 40

5 x 4 = 20

20

F 10

10 x 8 = 80

10 x 4 = 40

40

B 20

20 x 8 = 160

20 x 2 = 40

120

E 25

25 x 8 = 200

25 x 2 = 50

150

Первоначальный размер файла : 100 байт - 800 бит;

Размер сжатого файла : 30 байт - 240 бит;

240 - 30% из 800 , так что мы сжали этот файл на 70%.

Для нашего примера соответственно:

Таблица 4

Частота

первоначально

уплотненные биты

уменьшено на

C 30

30 x 8 = 240

30 x 3 = 90

150

A 60

60 x 8 = 480

60 x 1 = 60

420

D 5

5 x 8 = 40

5 x 4 = 20

20

F 20

20 x 8 = 160

20 x 3 = 60

100

B 25

25 x 8 = 200

25 x 3 = 75

125

E 10

10 x 8 = 80

10 x 4 = 40

40

Если первоначальный размер файла 1200 бит, размер сжатого файла 345 бит, сжатие на 71,25%.

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

      1. Метод Шеннона-Фано

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

      1. Алгоритм Лемпеля-Зива

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

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

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

Алгоритм Лемпеля-Зива преобразует один поток исходных символов в два параллельных потока длин и индексов в таблице (length + distance). Очевидно, что эти потоки являются потоками символов с двумя новыми алфавитами, и к ним можно применить один из методов сжатия, например, кодирование Хаффмена.

Классический алгоритм Лемпеля-Зива – LZ77, названный так по году своего опубликования, предельно прост. Он формулируется следующим образом: "если в прошедшем ранее выходном потоке уже встречалась подобная последовательность байт, причем запись о ее длине и смещении от текущей позиции короче чем сама эта последовательность, то в выходной файл записывается ссылка (смещение, длина), а не сама последовательность". Так фраза "КОЛОКОЛ_ОКОЛО_КОЛОКОЛЬНИ" закодируется как "КОЛО(-4,3)_(-5,4)О_(-14,7)ЬНИ".

Распространенный метод сжатия RLE (англ. Run Length Encoding), который заключается в записи вместо последовательности одинаковых символов одного символа и их количества, является подклассом данного алгоритма. Рассмотрим, например, последовательность "ААААААА". С помощью алгоритма RLE она будет закодирована как "(А,7)", в то же время ее можно достаточно хорошо сжать и с помощью алгоритма LZ77: "А(-1,6)". Действительно, степень сжатия именно такой последовательности им хуже (примерно на 30-40%), но сам по себе алгоритм LZ77 более универсален, и может намного лучше обрабатывать последовательности вообще несжимаемые методом RLE.

Последовательность S, содержащая N символов ({S(0),… S(N-1)}), подвергается N циклическим сдвигам (вращениям), лексикографической сортировке, а последний символ при каждом вращении извлекается. Из этих символов формируется строка L, где i-ый символ является последним символом i-го вращения. Кроме строки L создается индекс I исходной строки S в упорядоченном списке вращений. Существует эффективный алгоритм восстановления исходной последовательности символов S на основе строки L и индекса I. Процедура сортировки объединяет результаты вращений с идентичными начальными символами. Предполагается, что символы в S соответствуют алфавиту, содержащему K символов.

Для пояснения работы алгоритма возьмем последовательность S= “abraca” (N=6), алфавит X = {‘a’,’b’,’c’,’r’}.

Первый шаг. Формируем матрицу из N*N элементов, чьи строки представляют собой результаты циклического сдвига (вращений) исходной последовательности S, отсортированных лексикографически. По крайней мере одна из строк M содержит исходную последовательность S. Пусть I является индексом строки S. В приведенном примере индекс I=1, а матрица M имеет вид:

Номер строки

 

0

aabrac

1

abraca

2

acaabr

3

bracaa

4

caabra

5

racaab

Второй шаг. Пусть строка L представляет собой последнюю колонку матрицы M (символы, выделенные жирным шрифтом) с символами L[0],…,L[N-1] (соответствуют M[0,N-1],…,M[N-1,N-1]). Формируем строку последних символов вращений. Окончательный результат характеризуется (L, I). В данном примере L=’caraab’, I =1.

Процедура декомпрессии использует L и I. Целью этой процедуры является получение исходной последовательности из N символов (S).

1. Сначала вычисляем первую колонку матрицы M (F). Это делается путем сортировки символов строки L. Каждая колонка исходной матрицы M представляет собой перестановки исходной последовательности S. Таким образом, первая колонка F и L являются перестановками S. Так как строки в M упорядочены, размещение символов в F также упорядочено. F=’aaabcr’.

2. Рассматриваем ряды матрицы M, которые начинаются с заданного символа ch. Строки матрицы М упорядочены лексикографически, поэтому строки, начинающиеся с ch упорядочены аналогичным образом. Определим матрицу M’, которая получается из строк матрицы M путем циклического сдвига на один символ вправо. Для каждого i=0,…, N-1 и каждого j=0,…,N-1,

M’[i,j] = m[i,(j-1) mod N]

В рассмотренном примере M и M’ имеют вид:

Строка

M

M’

0

aabrac

caabra

1

abraca

aabraс

2

acaabr

racaab

3

bracaa

abraca

4

caabra

acaabr

5

racaab

bracaa

Подобно M каждая строка M’ является вращением S, и для каждой строки M существует соответствующая строка M’. M’ получена из M так, что строки M’ упорядочены лексикографически, начиная со второго символа. Таким образом, если мы рассмотрим только те строки M’, которые начинаются с заданного символа ch, они должны следовать упорядоченным образом с учетом второго символа. Следовательно, для любого заданного символа ch, строки M, которые начинаются с ch, появляются в том же порядке что и в M’, начинающиеся с ch. В нашем примере это видно на примере строк, начинающихся с ‘a’. Строки ‘aabrac’, ‘abraca’ и ‘acaabr’ имеют номера 0, 1 и 2 в M и 1, 3, 4 в M’.

Используя F и L, первые колонки M и M’ мы вычислим вектор Т, который указывает на соответствие между строками двух матриц, с учетом того, что для каждого j = 0,…,N-1 строки j M’ соответствуют строкам T[j] M.

Если L[j] является к-ым появлением ch в L, тогда T[j]=1, где F[i] является к-ым появлением ch в F. Заметьте, что Т представляет соответствие один в один между элементами F и элементами L, а F[T[j]] = L[j]. В нашем примере T равно: (4 0 5 1 2 3).

3. Теперь для каждого i = 0,…, N-1 символы L[i] и F[i] являются соответственно последними и первыми символами строки i матрицы M. Так как каждая строка является вращением S, символ L[i] является циклическим предшественником символа F[i] в S. Из Т мы имеем F[T[j]] = L[j]. Подставляя i =T[j], мы получаем символ L[T(j)], который циклически предшествует символу L[j] в S.

Индекс I указывает на строку М, где записана строка S. Таким образом, последний символ S равен L[I]. Мы используем вектор T для получения предшественников каждого символа: для каждого i = 0,…,N-1 S[N-1-i] = L[Ti[I]], где T0[x] =x, а Ti+1[x] = T[Ti[x]. Эта процедура позволяет восстановить первоначальную последовательность символов S (‘abraca’).

Последовательность Ti[I] для i =0,…,N-1 не обязательно является перестановкой чисел 0,…,N-1. Если исходная последовательность S является формой Zp для некоторой подстановки Z и для некоторого p>1, тогда последовательность Ti[I] для i = 0,…,N-1 будет также формой Z’p для некоторой субпоследовательности Z’. Таким образом, если S = ‘cancan’, Z = ‘can’ и p=2, последовательность Ti[I] для i = 0,…,N-1 будет [2,4,0,2,4,0].

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

Возьмем в качестве примера букву “t” в слове ‘the’ и предположим, что исходная последовательность содержит много таких слов. Когда список вращений упорядочен, все вращения, начинающиеся с ‘he’, будут взаимно упорядочены. Один отрезок строки L будет содержать непропорционально большое число ‘t’, перемешанных с другими символами, которые могут предшествовать ‘he’, такими как пробел, ‘s’, ‘T’ и ‘S’.

Аналогичные аргументы могут быть использованы для всех символов всех слов, таким образом, любая область строки L будет содержать большое число некоторых символов. В результате вероятность того, что символ ‘ch’ встретится в данной точке L, весьма велика, если ch встречается вблизи этой точки L, и мала в противоположном случае. Это свойство способствует эффективной работе локально адаптивных алгоритмов сжатия, где кодируется относительное положение идентичных символов. В случае применения к строке L, такой кодировщик будет выдавать малые числа, которые могут способствовать эффективной работе последующего кодирования, например, посредством алгоритма Хаффмана.

Еще пример.

Возьмем набор символов

АБВАБВЯЯЯЯЯЯ

В нем дважды встречается сочетание АБВ, поэтому его можно запасти в так называемом словаре, а в исходном тексте только оставить ссылочку на словарь. Тогда исходный текст можно преобразовать, например, в

11Яяяяяя

и отдельно запомнить, что за символом 1 на самом деле скрывается троица АБВ. Ясно, что если в тексте сочетание АБВ встретилось не 2, а 100 или еще лучше 1000 раз, то сжатие было бы весьма ощутимым.

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

Посмотрим, много ли мы выгадали в рассмотренном примере. Текст ужался на 4 символа, но и, как минимум, 4 символа оказалось в словаре. Кроме того, построение словаря потребует введения разделителей и пр.

Но и это еще не все. А если в тексте уже есть символы 1? Как понять, что это именно 1, а не ссылка на словарь? В общем случае мы вообще не можем рассчитывать, что какие-то символы останутся незадействованными в тексте и их можно будет использовать в качестве ссылок. Забегая вперед, скажем, что при грамотном подходе даже одно повторение двухсимвольного сочетания может быть использовано для сжатия текста.

Как же поступить наиболее грамотно? Вот тут ответ далеко неоднозначен. Он и не может быть однозначен, потому что сжатие – это не таблица умножения. Один текст лучше сжимается одним методом, другой - совершенно другим способом. Для начала мы рассмотрим очень упрощенный вариант, чтобы было от чего отталкиваться в дальнейшем.

Для определенности будем полагать, что каждый символ в тексте - это упорядоченный набор из 8 битов, т.е. байт, или, что то же самое, целое число от 0 до 255. (Иногда бывает полезнее разбить текст на цепочки битов иной, даже переменной, длины, но это уже отдельный вопрос.)

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

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

Но как оформить это указание? Если указание - это адрес в тексте, то оно (16 битов при не самом большом тексте в 64 КБ) может оказаться слишком громоздким и не даст никакого сжатия для двухсимвольных сочетаний. Конечно, можно было бы вообще забросить возню с повторяющимися парами, но специфика реальных текстов любого происхождения такова, что именно двухсимвольные повторения встречаются намного чаще трехсимвольных и прочих, и именно от них в итоге получается один из крупнейших вкладов в сжатие.

Можно ограничить адреса, скажем, десятью битами, но тогда мы сможем делать ссылки только на первый килобайт исходного текста.

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

В нашем примере можно обыграть малую длину текста и от второй пары АБ можно было бы пустить на выход два бита 01 и двубитовое число 3, т.е. всего 4 бита: 01 11. Число 3 означает, что аналогичную пару надо искать на 3 позиции левее. Но в таком случае очередной символ В пришлось бы выдавать открытым текстом, и с учетом однобитового признака он потянул бы на 9 битов.

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

Пока же в нашем примере достаточно двубитовых ссылок. Так что на выход пошлем: 001 11.

Далее от буквы Я по известным правилам пойдет 9 битов. Остаток текста шифруется семью битами:

00001 01

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

Таким образом, исходный текст в сжатом виде при несколько вольном изображении предстанет в виде:

1 А 1 Б 1 В 001 11 00001 01

Правильнее (но менее наглядно) было бы вписать вместо А, Б и В их восьмибитовые представления.

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

Чтобы программа расшифровки годилась не только для рассмотренного примера, еще к сжатому тексту надо приложить некоторые параметры: длину сжатого или развернутого текста, а также длину ссылок. Само дерево прикладывать не надо, если оно подчиняется простым широко употребительным правилам.

В рассмотренном примере правило простое: длина группы нулей с единичкой равна количеству перемещаемых символов. Но уже здесь видно, что одним простым правилом не обойтись. Так, у нас оказались не задействованы сочетания: 01, 0001, 000001 и т.д., т.е. мы израсходовали битов значительно больше, чем необходимо для идентификации возникших ситуаций. Повторяющуюся тройку можно было обозначить через 01, а пятерку - через 001 (а можно и через 00), но тогда пришлось бы хранить таблицу соответствия, в которой каждому количеству перемещаемых символов ставился некий битовый код.

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

Тем не менее, выбранная в примере кодировка ситуаций далеко не случайна. Разбор текстов самого разного происхождения показывает, что одиночные символы составляют примерно половину ситуаций, пары - одну четверть, тройки - одну восьмую и т.д. Поскольку мы все равно вынуждены делать кодировку только целым количеством битов, то даже при заметном нарушении указанных пропорций обычно не удается увильнуть от стандарта: N символов - N битов.

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

Так что дерево Лемпеля-Зива может быть достаточно сложным и развесистым. Причем мы еще не упомянули всех направлений его развития, необходимых для разработки мощного алгоритма сжатия.

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

В настоящее время, в различных модификациях и сочетаниях, два алгоритма – метод Хаффмана (или арифметическое кодирование) и метод Лемпеля-Зива – составляют основу всех коммерческих алгоритмов и программ.

В 1984 году Терри Уэлч (Terry Welch) предложил вариант LZ78, в котором в упакованные данные выводятся только коды_фрагмента_в_словаре. А для того, чтобы в словаре всегда существовал подходящий фрагмент, Терри Уэлч предложил предварительно заполнить словарь всеми односимвольными (элементарными) фрагментами – всеми возможными значениями байта от 0 до 255. Это гарантирует, что при поступлении на вход очередной порции данных будет найден в словаре хотя бы однобайтовый фрагмент. Алгоритм известен под названием LZW.

Рассмотрим алгоритм заполнения словаря на примере кодирования фрагмента. Для примера используем данные, состоящие только из символов (букв), кавычки «» не входят в данные:

«abcdabce-abcd-abceabcabcabcd-----abcaabcdabce-abceca»,

где символ «-» обозначает пробел. Пример кодировки приведен в табл. 5

Таблица 5

Пример работы кодировки LZ78

Данные на входе –байты

Текущий фрагмент

Данные на выходе –коды

Комментарий

Добавление фрагмента в словарь –«фрагмент»:код

1.

256

сброс словаря при начале кодирования данных

… «⋅»:32; … «a»:97; «b»:98; «c»:99; «d»:100; «e»:101 …

2. a

фрагмент «a» есть в слова-ре

3. b

a

97

фрагмента «ab» нет в сло-варе

«ab»: 257

4. c

b

98

фрагмента «bc» нет в сло-варе

«bc»:258

5. d

c

99

-/-

«cd»:259

6. a

d

100

-/-

«da»:260

7. b

a

фрагмент «ab» есть в сло-варе

8. c

ab

257

фрагмента «abc» нет в сло-варе

«abc»:261

9. e

c

99

-/-

«ce»:262

10. ⋅

e

101

«e⋅»:263

11. a

32

«⋅a»:264

12. b

a

13. c

ab

14. d

abc

261

«abcd»:265

15. ⋅

d

100

«d⋅»:266

16. a

⋅а

17. b

⋅а

264

«⋅ab»:267

18. c

b

19. e

bc

258

«bce»:268

20. a

e

101

«ea»:269

21. b

а

22. c

аb

23. a

abc

261

«abca»:270

24. b

а

25. c

аb

26. a

аbc

27. b

аbca

270

«abcab»:271

28. c

b

29. d

bc

258

«bcd»:272

30. ⋅

d

31. ⋅

d⋅

266

«d⋅⋅»:273

32. ⋅

32

«⋅⋅»:274

33. ⋅

34. ⋅

274

«⋅⋅⋅»:275

35. a

36. b

⋅a

37. c

⋅ab

267

«⋅abc»:276

38. a

c

99

«ca»:277

39. b

a

40. c

ab

41. d

abc

42. a

abcd

265

«abcda»:278

43. b

a

44. c

ab

45. e

abc

261

«abce»:279

46. ⋅

e⋅

47. a

e⋅

263

«e⋅a»:280

48. b

a

49. c

ab

50. e

abc

51. c

abce

279

«abcec»:281

52. a

с

фрагмент «сa» есть в сло-варе

53.

ca

277

закончились данные –завершен процесс кодирования –надо вывести код последнего фрагмента

если бы данные были длиннее, то процесс продолжался бы до заполнения словаря…

256

словарь заполнен –сброс

… «⋅»:32; … «a»:97;

«b»:98; «c»:99;

«d»:100; «e»:101 …

процесс продолжается снова

В исходных данных 52 байта или 52.8=416 бит. В кодированных данных 27 кодов по 9 бит или 27.9=243 бита. Уменьшение размера данных составило 1,71 раза или кодированные данные составляют 0,58 исходного размера.

Таблица 6

Пример работы декодировки LZ78

Данные на входе – коды

Текущий фрагмент

Данные на выходе – байты

Комментарий

Добавление фрагмента в словарь, «фрагмент»:код

1. 256

сброс словаря при начале декодирования данных

… «⋅»:32; … «a»:97; «b»:98; «c»:99; «d»:100; «e»:101 …

2. 97

a

a

код 97 есть в словаре

3. 98

b

b

фрагмента «ab» нет в сло-варе

«ab»: 257

4. 99

c

c

фрагмента «bc» нет в сло-варе

«bc»:258

5. 100

d

d

-/-

«cd»:259

6. 257

ab

ab

-/-

«da»:260

7. 99

c

c

«abc»:261

8. 101

e

e

«ce»:262

9. 32

«e⋅»:263

10. 261

abc

abc

«.a»:264

11. 100

d

d

«abcd»:265

12. 264

⋅a

⋅a

«d-»:266

13. 258

bc

bc

«-ab»:267

14. 101

e

e

«bce»:268

15. 261

abc

abc

«ea»:269

16. 270

abca

abca

кода 270 нет в словаре!!! Конструируем фрагмент для него из текущего фраг-мента+первый байт теку-щего фрагмента.

«abca»:270

17. 258

bc

bc

«abcab»:271

18. 266

d⋅

d⋅

«bcd»:272

19. 32

«d--»:273

20. 274

кода 274 нет в словаре!!!

«--»:274

21. 267

⋅ab

⋅ab

«---»:275

22. 99

c

c

«-abc»:276

23. 265

adcd

adcd

«ca»:277

24. 261

abc

abc

«abcda»:278

25. 263

e⋅

e⋅

«abce»:279

26. 279

aвсе

aвсе

«e-a»:280

27. 277

«abcec»:281

Таким образом, алгоритм упаковки и распаковки методом LZ78 доста-точно прост. Основную проблему, при реализации этого метода, представляет устройство словаря.

Можно хранить фрагменты в виде строк, но экономнее хранить фрагменты посимвольно в виде многосвязного дерева, например так, как это показано на рис. 5. Начало фрагмента (первый символ) содержит ссылку на таблицу-продолжение, которая содержит список символов-продолжений фрагмента (все существующие вторые символы). Каждый следующий символ тоже может иметь таблицу-продолжение или не иметь, если продолжения фрагмента нет. И так далее до бесконечности.

Приведенное дерево определяет 13 фрагментов (ячейки с «...» не учитывались): a, b, ab, ac, ad, aj, aje, ajf, ajg, ajz, bc, bd, bj. Для хранения их в виде «простого словаря» необходимо 28 байт, при хранении в дереве — 13 байт. При увеличении числа ветвлений дерева, разница еще более возрастает.

Рис. 5. Хранение фрагментов в виде дерева

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]