- •Введение
- •1. ТИПЫ ДАННЫХ И ОПЕРАТОРЫ
- •1.1. Переменные и базовые типы данных
- •1.2. Операции и выражения
- •1.3. Символические константы
- •1.5. Несколько слов о функции main()
- •2. ВВОД И ВЫВОД В СИ
- •2.2. Форматный ввод-вывод
- •3. ЦИКЛЫ И ОПЕРАТОРЫ СРАВНЕНИЯ
- •3.1. Условный оператор
- •3.2. Оператор выбора switch
- •3.3. Операторы цикла
- •3.4. Операторы break и continue
- •3.5. Примеры
- •3.6. Вычисление значений элементарных функций
- •3.7. Задачи
- •4. ОБРАБОТКА ПОСЛЕДОВАТЕЛЬНОСТЕЙ
- •4.1. Примеры
- •4.2. Задачи
- •5. ОДНОМЕРНЫЕ МАССИВЫ
- •5.1. Начальные сведения о массивах
- •5.2. Примеры работы с массивами
- •5.3. Задачи
- •6. МНОГОМЕРНЫЕ МАССИВЫ
- •6.1. Определение и инициализация двумерных массивов
- •6.2. Примеры с двумерными массивами
- •6.3. Задачи
- •7. УКАЗАТЕЛИ И МАССИВЫ
- •7.1. Указатели и адреса
- •7.2. Указатели и аргументы функций
- •7.3. Указатели и массивы
- •7.4. Операции с указателями
- •7.5. Указатели с типом void
- •7.6. Модификатор const
- •7.7. Массивы переменного размера
- •7.8. Массивы указателей
- •7.9. Двумерные массивы переменного размера
- •8. СИМВОЛЫ И СТРОКИ
- •8.1. Представление символьной информации в ЭВМ
- •8.2. Библиотека обработки символов
- •8.3. Строки в языке Си
- •8.4. Функции обработки строк
- •8.5. Функции преобразования строк
- •8.6. Примеры работы со строками
- •8.7. Разбиение строки на лексемы
- •8.8. Задачи
- •9. СТРУКТУРЫ
- •9.1. Основные сведения о структурах
- •9.2. Объединения
- •10. ДИРЕКТИВЫ ПРЕПРОЦЕССОРА
- •10.1. Директива #include
- •10.2. Директива #define
- •10.3. Директива #undef
- •10.4. Условная компиляция
- •11. ФУНКЦИИ
- •11.1. Основные сведения о функциях
- •11.2. Прототипы функций
- •11.3. Классы памяти
- •11.4. Указатели на функции
- •11.5. Рекурсия
- •11.6. Примеры с использованием рекурсии
- •11.7. Метод «разделяй и властвуй»
- •11.8. Задачи на применение рекурсии
- •12. РАБОТА С БИТАМИ ПАМЯТИ
- •12.1. Битовые операции
- •12.2. Примеры с использованием битовых операций
- •12.3. Задачи
- •13. РАБОТА С ФАЙЛАМИ
- •13.1. Файлы и потоки
- •13.2. Текстовые файлы
- •13.3. Двоичные файлы
- •13.4. Шифрование файлов
- •13.5. Задачи на текстовые файлы
- •13.6. Задачи на двоичные файлы
- •14. СТРУКТУРЫ ДАННЫХ
- •14.1. Односвязные списки
- •14.2. Примеры работы с односвязными списками
- •14.3. Задачи на односвязные списки
- •14.4. Стеки, очереди
- •14.5. Задачи на стеки и очереди
- •14.6. Двусвязные списки
- •14.7. Задачи на двусвязные списки
- •14.8. Бинарные деревья
- •14.9. Примеры с использованием бинарных деревьев
- •14.10. Задачи на бинарные деревья
- •Приложение 1. АЛГОРИТМЫ ПОИСКА
- •1. Линейный поиск
- •2. Поиск с барьером
- •3. Двоичный поиск
- •Приложение 2. АЛГОРИТМЫ СОРТИРОВКИ
- •Несколько слов о сложности алгоритмов
- •1. Метод прямого выбора
- •2. Метод прямого включения
- •3. Пузырьковая сортировка
- •4. Шейкерная сортировка
- •5. Быстрая сортировка
- •6. Сортировка подсчетом
- •Приложение 3. СОРТИРОВКА ИНДЕКСОВ И УКАЗАТЕЛЕЙ
- •1. Сортировка индексов на основе метода прямого выбора
- •2. Сортировка индексов на основе пузырьковой сортировки
- •3. Сортировка индексов на основе быстрой сортировки
- •4. Сортировка двумерных массивов
- •5. Сортировка строк
- •Приложение 4. СОРТИРОВКА ФАЙЛОВ И СПИСКОВ
- •1. Сортировка двоичных файлов
- •2. Сортировка линейных списков
- •Приложение 5. СОРТИРОВКА С УСЛОВИЕМ
- •1. Сортировка с условием на базе пузырьковой сортировки
- •2. Сортировка с условием на базе быстрой сортировки
- •3. Сортировка с условием двоичных файлов
- •4. Сортировка с условием линейного списка на базе пузырьковой сортировки
- •5. Сортировка с условием линейного списка на базе быстрой сортировки
- •ЛИТЕРАТУРА
13.4. Шифрование файлов
Напомним, что в языке Си любой файл рассматривается как последовательность байт, заканчивающаяся маркером конца файла. Каждый байт такой последовательности может принимать любое целое значение в диапазоне от 0 до 255. Будем считать, что каждый элемент (байт) последовательности принадлежит кольцу вычетов по модулю 256 (Z256). Сложение и вычитание, которые будут использоваться в дальнейшем, являются соответствующими операциями в кольце вычетов Z256.
Рассмотрим алгоритм шифрования, носящий название шифра Виженера (о современных шифрах, особенно невзламываемых, можно почитать, например, в [14]). Пусть x = x1x2…xn – последовательность байт (файл), каждый элемент (xi, i=1,…,n) которой принадлежит Z256. Для шифрования используется ключ – слово (в общем случае, хаотичный набор символов) в алфавите Z256. Пусть key = k1k2…kd – некоторое ключевое слово длины d, где все ki принадлежат кольцу Z256. Шифрование происходит следующим образом. Считываем первый байт исходного файла и шифруем его с помощью первого байта ключа k1: y1 = x1 + k1 (mod 256), затем считываем второй байт исходного файла и шифруем его с помощью второго байта ключа k2: y2 = x2 + k2 (mod 256) и т.д. (d+1)-ый считанный байт шифруется вновь ключом k1, то есть, в целом, файл f разбивается на блоки длины d, на которые накладывается ключевое слово key. Полученная последовательность y = y1y2…yn и будет являться зашифрованным файлом. Чтобы из зашифрованного файла y1y2…yn обратно получить исходный файл (то есть расшифровать зашифрованный файл y1y2…yn), необходимо проделать обратные
операции: x1 = y1 – k1 (mod 256), x2 = y2 - k2 (mod 256) и т.д.
Случай 1. Предположим, что необходимо зашифровать файл, не удаляя шифруемую информацию. Например, вы хотите зашифровать какой-то файл и отправить его кому-то по электронной почте. В этом случае нет необходимости удалять исходный файл. Ниже приведен алгоритм шифрования методом Виженера, в котором информация из файла шифруется и записывается в новый файл. Чтобы алгоритм работал очень быстро, используем буфер
226
обмена buf, куда будем считывать информацию из файла целыми блоками.
#include<stdio.h>
typedef unsigned char UCHAR; typedef unsigned long ULONG;
#define LENGTH 1024 /* размер буфера обмена */
/* функция шифрует содержимое файла fileName1 и результат шифрования записывает в новый
файл fileName2, оставляя файл fileName1 без изменения
*/
int Shifr(char *fileName1, char *fileName2, char *key)
{
FILE *f, *g; |
/* входной и выходной файлы */ |
ULONG i_key, |
|
i, j, |
/* счетчики */ |
keyLen, |
/* длина ключа */ |
n_buf; |
/* длина считанного блока в буфер */ |
UCHAR buf[LENGTH]; /* буфер обмена */
if ((f = fopen(fileName1, "rb")) == NULL) return 1;
if ((g = fopen(fileName2, "wb")) == NULL)
{
fclose(f); return 1;
}
keyLen = strlen(key); /* вычисляем длину ключа key */ i_key = 0;
while (n_buf = fread(buf, sizeof(UCHAR), LENGTH, f))
{
for (j = 0; j < n_buf; j++)
{
buf[j] += key[i_key]; i_key++;
if (i_key >= keyLen)
227
i_key = 0;
}
fwrite(buf, sizeof(UCHAR), n_buf, g);
}
fclose(f);
fclose(g); return 0;
}
Функция Shifr() вызывается очень просто, например,
Shifr(“c:\\a.data”, “c:\\b.data”, “password”). Чтобы прописать функ-
цию расшифрования, достаточно скопировать функцию Shifr() и поменять в ней строку
buf[j] += key[i_key];
на строку
buf[j] -= key[i_key];
и назвать эту функцию, например DeShifr().
Случай 2. Предположим, что вы работаете за чужим компьютером и вам необходимо зашифровать ваш файл (зашифрованный файл можно оставить на данном компьютере до следующего вашего прихода или записать на флешку). Рассмотренный выше случай 1 здесь уже не подойдет, так как если вы создадите новый шифрованный файл, а старый удалите, то удаленный файл возможно восстановить. Поэтому в следующем алгоритме шифрование происходит не в новый файл, а путем перезаписывания (замещения) исходного файла шифрованным. При этом считывается очередной блок в буфер buf исходного файла, шифруется, и шифрованный блок записывается вместо исходного.
/* функция шифрования */
int Shifr(char *fileName, char *key)
{
FILE *f; ULONG i_key,
i, j, /* счетчики */ keyLen, /* длина ключа */
228
n_buf, /* длина считанного блока в буфер */
n; /* количество блоков в файле длины LENGTH */
UCHAR buf[LENGTH]; /* буфер обмена */
if ((f = fopen(fileName, "r+b")) == NULL) return 1;
keyLen = strlen(key);
n = Size(fileName)/LENGTH + 1; i_key = 0;
for (i = 0; i < n; i++)
{
fseek(f, i*LENGTH, SEEK_SET);
n_buf = fread(buf, sizeof(UCHAR), LENGTH, f); for (j = 0; j < n_buf; j++)
{
buf[j] += key[i_key]; i_key++;
if (i_key >= keyLen) i_key = 0;
}
fseek(f, i*LENGTH, SEEK_SET); fwrite(buf, sizeof(UCHAR), n_buf, f);
}
fclose(f); return 0;
}
Как и в предыдущем случае, чтобы прописать функцию расшифрования, достаточно скопировать функцию Shifr() и поменять в ней строку
buf[j] += key[i_key];
на строку
buf[j] -= key[i_key];
и, естественно, назвать эту функцию по-другому, например
DeShifr().
229
Уничтожение информации. Предположим, что у вас имеется некоторый секретный файл, который необходимо уничтожить. Естественно, что просто удалить данный файл (например, с помощью функции remove()) недостаточно, так как его возможно будет восстановить. Целесообразнее в данном случае сначала на место удаляемого файла записать последовательность нулей (такого же размера, что и прежний файл), а потом уже использовать функцию remove().
#include<stdio.h>
typedef unsigned char UCHAR; typedef unsigned long ULONG;
#define LENGTH 1024 /* размер буфера обмена */
int DestroyFile(char *fileName)
{
FILE *f;
ULONG i, size, q, r;
UCHAR buf[LENGTH] = {0};
if ((f = fopen(fileName, "r+b")) == NULL) return 1;
size = Size(fileName); /* вычисляем размер файла */
/* далее необходимо разложение size = q*LENGTH + r,
где r < LENGTH
*/
/* вычисляем количество блоков длины LENGTH:*/ q = size/LENGTH;
r = size%LENGTH; /* вычисляем остаток */
for (i = 0; i < q; i++)
fwrite(buf, sizeof(UCHAR), LENGTH, f); fwrite(buf, sizeof(UCHAR), r, f);
fclose(f);
remove(fileName); return 0;
230