Шифр Виженера (Реализация на C#)

C#   6 июня 2012  Автор статьи:  

Здесь представлена программа, которая производит шифрование алгоритмом Виженера всех символов алфавита кириллицы, остальные символы остаются без изменения.
Реализация данного алгоритма на C#:

//Программа шифрования/дешифрования символов кирилицы шифром Виженера
//Подключение библиотек
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Shifrovanie_Vizhener
{
class Program
{
static void Main()
{
//Цикл для повтора задачи
do
{
//Путь к файлу
string enpath = @"C:\Encrypt.txt";
string depath = @"C:\Decrypt.txt";
string repath = @"C:\Result.txt";
uint k = 0; //Переменная выбора шифрования/дешифрования
string s = ""; //Строка, к которой применяется шифрованияе/дешифрование
string result = ""; //Строка - результат шифрования/дешифрования
string key = ""; //Строка - ключ шифра
string key_on_s = ""; //Ключ длиной строки
int x = 0,y = 0; //Координаты нового символа из таблицы Виженера
int registr = 0; //Регистр символа
char dublicat; //Дубликат прописной буквы

//Формирование таблицы Виженера на алфавите кирилицы
int shift=0;
char[,] tabula_recta = new char[32, 32]; //Таблица Виженера
string alfabet = "абвгдежзийклмнопрстуфхцчшщьыъэюя";
//Формирование таблицы
for (int i = 0; i < 32; i++) for (int j = 0; j < 32; j++) { shift = j + i; if (shift >= 32) shift = shift % 32;
tabula_recta[i, j] = alfabet[shift];
}
//Вывод сообщения на экран
Console.WriteLine("Введите 1 для шифрования или 2 для дешифрования");
//Считывание переменной выбора, пока она не станет равной 1 или 2
while ((k != 1) && (k != 2))
{
//Считывание переменной k, если введенные данные имеют тип uint
uint.TryParse(Console.ReadLine(), out k);
//Вывод сообщения об ошибке, если k != 1 или k != 2
if ((k != 1) && (k != 2))
Console.WriteLine("Ошибка ввода, повторите попытку");
}
//Запрос ключа
Console.WriteLine("Введите ключ шифра");
//Считывание ключа
//Объявление флага, для считывания ключа
bool flag = false;
//Пока не будет введен ключ из разрешенных символов (прописные и строчные буквы кирилицы)
while (flag != true)
{
flag = true;
//Считывание строки
key = Console.ReadLine();
//Цикл по каждому элементу ключа
for (int i = 0; i < key.Length; i++) { //Если элемент ключа не принадлежит алфавиту кирилицы, изменить флаг if ((Convert.ToInt16(key[i]) < 1072) || (Convert.ToInt16(key[i]) > 1103))
flag = false;
}
//Если ключ имеет запрещенные символы, то сообщение об ошибке
if (flag == false)
Console.WriteLine("Ключ имеет запрещенные символы, повторите ввод");
}
//Если выбрано шифрование
if (k == 1)
{
//Вывод сообщения на экран
Console.WriteLine("Строка считывается из файла!");
//Считывание строки
s = File.ReadAllText(enpath, Encoding.Default);
//Выполение шифрования
//Формирование строки, длиной шифруемой, состоящей из повторений ключа
for (int i = 0; i < s.Length; i++) { key_on_s += key[i % key.Length]; } //Шифрование при помощи таблицы for (int i = 0; i < s.Length; i++) { //Если не кириллица if (((int)(s[i]) < 1040) || ((int)(s[i]) > 1103))
result += s[i];
else
{
//Поиск в первом столбце строки, начинающейся с символа ключа
int l = 0;
flag = false;
//Пока не найден символ
while ((l < 32) && (flag == false)) { //Если символ найден if (key_on_s[i] == tabula_recta[l, 0]) { //Запоминаем в х номер строки x = l; flag = true; } l++; } //Уменьшаем временно регистр прописной буквы if ((Convert.ToInt16(s[i]) < 1072) && (Convert.ToInt16(s[i]) >= 1040))
{
dublicat = Convert.ToChar(Convert.ToInt16(s[i]) + 32);
registr = 1;
}
else
{
registr = 0;
dublicat = s[i];
}
l = 0;
flag = false;
//Пока не найден столбец в первой строке с символом строки
while ((l < 32) && (flag == false)) { //Проверка совпадения if (dublicat == tabula_recta[0, l]) { //Запоминаем номер столбца y = l; flag = true; } l++; } //Увеличиваем регистр буквы до прописной if (registr == 1) { //Изменяем символ на первоначальный регистр dublicat = Convert.ToChar(Convert.ToInt16(tabula_recta[x, y]) - 32); result += dublicat; } else result += tabula_recta[x, y]; } } //Вывод на экран зашифрованной строки Console.WriteLine("Строка успешно зашифрована!"); File.WriteAllText(repath, result); //Вывод результата в файл } //Если было выбрано дешифрование if (k == 2) { //Вывод сообщения на экран Console.WriteLine("Строка считывается из файла!"); //Считывание строки s = File.ReadAllText(depath, Encoding.Default); //Выполение дешифрования //Формирование строки, длиной шифруемой, состоящей из повторений ключа for (int i = 0; i < s.Length; i++) { key_on_s += key[i % key.Length]; } //Дешифрование при помощи таблицы for (int i = 0; i < s.Length; i++) { //Если не кириллица if (((int)(s[i]) < 1040) || ((int)(s[i]) > 1103))
result += s[i];
else
{
//Поиск в первом столбце строки, начинающейся с символа ключа
int l = 0;
flag = false;
//Пока не найден символ
while ((l < 32) && (flag == false)) { //Если символ найден if (key_on_s[i] == tabula_recta[l, 0]) { //Запоминаем в х номер строки x = l; flag = true; } l++; } //Уменьшаем временно регистр прописной буквы if ((Convert.ToInt16(s[i]) < 1072) && (Convert.ToInt16(s[i]) >= 1040))
{
dublicat = Convert.ToChar(Convert.ToInt16(s[i]) + 32);
registr = 1;
}
else
{
registr = 0;
dublicat = s[i];
}
l = 0;
flag = false;
//Пока не найден столбец в первой строке с символом строки
while ((l < 32) && (flag == false)) { //Проверка совпадения if (dublicat == tabula_recta[x, l]) { //Запоминаем номер столбца y = l; flag = true; } l++; } //Увеличиваем регистр буквы до прописной if (registr == 1) { //Изменяем символ на первоначальный регистр dublicat = Convert.ToChar(Convert.ToInt16(tabula_recta[0, y]) - 32); result += dublicat; } else result += tabula_recta[0, y]; } } //Вывод на экран дешифрованной строки Console.WriteLine("Строка успешно дешифрована!"); File.WriteAllText(repath, result); } Console.WriteLine("Для выхода из программы нажмите Escape"); } while (Console.ReadKey(true).Key != ConsoleKey.Escape); } } }

  • qwerty

    Мои основные замечания к коду:
    — Зачем нужно создавать отдельную строку ключа длинной строки? Разве нельзя воспользоваться операцией % по длине ключа?
    — Какова необходимость написания кода в одну функцию? Вынесение кода в отдельные функции делает его предельно простым для вызова (код алгоритма сливается с кодом main, а это не есть хорошо).
    — ИМХО не люблю, когда коды символов вносятся непосредственно в код. Зачем знать / смотреть соответствие некоторого когда символу, если можно воспользоваться операцией вычисления кода символа (компилятор сам вычислит)?

  • qwerty

    «Вынесение кода в отдельные функции делает его предельно простым для вызова».

    А чем код проще, тем сложнее в нем допустить случайные ошибки, его проще отлаживать.

  • qwerty

    Зачем в код вставлять непосредственные значения длин каких-то структур данных? Зачем это? Ведь всё это может вычислить машина. Допустим, я захочу изменить алфавит. Мне, в вашем коде, придется заменять все ваши «числа в коде».

    • qwerty

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

      исходный алфавит:

      const char Alfabet_start_big = ‘A’;
      const char Alfabet_start = ‘a’;
      const char Alfabet_end = ‘z’;
      const int Alfabet_size = Alfabet_end — Alfabet_start;
      const int Alfabet_register_offset = Alfabet_start_big — Alfabet_start;

      Чувствуете разницу? При таком формате алфавита, его легко заменять (можно хоть с клавиатуры вводить + проверка на корректность введенных данных).

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

  • qwerty

    Зачем в код вставлять непосредственные значения длин каких-то структур данных? Зачем это? Ведь всё это может вычислить машина. Допустим, я захочу изменить алфавит. Мне, в вашем коде, придется заменять все ваши «числа в коде».

  • qwerty

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

    исходный алфавит:

    const char Alfabet_start_big = ‘A’;
    const char Alfabet_start = ‘a’;
    const char Alfabet_end = ‘z’;
    const int Alfabet_size = Alfabet_end — Alfabet_start;
    const int Alfabet_register_offset = Alfabet_start_big — Alfabet_start;

    Чувствуете разницу? При таком формате алфавита, его легко заменять (можно хоть с клавиатуры вводить + проверка на корректность введенных данных).

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

  • qwerty

    … у вас еще зачем-то дважды (и при шифровании дважды, и при дешифровании дважды) идут одинаковые циклы (отличаются только телом). А это — неэффективное расходование машинного времени.
    p.s. Проще написать новый код с нуля, чем пытаться скорректировать этот 🙁

  • qwerty

    … у вас еще зачем-то дважды (и при шифровании дважды, и при дешифровании дважды) идут одинаковые циклы (отличаются только телом). А это — неэффективное расходование машинного времени.
    p.s. Проще написать новый код с нуля, чем пытаться скорректировать этот 🙁

  • qwerty

    …извиняюсь, у меня уже мозг завис, в предыдущем комментарии я ошибся с циклами. Однако это не отменяет остальное, про написание с нуля.

  • Зачем писать 6 комментариев вместо одного?=) Это неэффективное расходование места нашего сайта=) На счет функций согласен, откройте реализацию на java и увидите все, что хотели… Зачем нужно вручную вводить алфавит? Для того, чтобы можно было использовать какую — нибудь перестановку на нем, потому что Виженер легко взламывается в наши дни. На счет памяти и времени. С точки зрения стилистика написания кода, я конечно согласен, функции это хорошо, но с точки зрения памяти и времени никаких проблем здесь нет, потому что алфавит всегда константа и не участвует в оценке сложности алгоритма, также как и в оценке памяти, т.е затраченная память линейная, также как и затраченное время. Сколько реально времени и памяти мы проигрываем на эту константу? Ну точно меньше секунды, даже на каком — нить стареньком IBM. Данная константа не учитывается потому, что я могу взять строку длинной в много много гигабайт и условно говоря инициализация алфавита будет работать секунду, а процесс шифрования займет день.

  • qwerty

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

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

    // Алфавит
    const char Alfabet_start_big = ‘A’;
    const char Alfabet_start = ‘a’;
    const char Alfabet_end = ‘z’;
    const int Alfabet_size = Alfabet_end — Alfabet_start;
    const int Alfabet_register_offset = Alfabet_start_big — Alfabet_start;

    // Шифрование символа нижнего регистра по таблице Виженера
    char TabulaRecta_Direct_LowCase(char cKey, char chData)
    {
    int i = cKey — Alfabet_start;
    int j = chData — Alfabet_start;
    return (char)(Alfabet_start + (i + j) % Alfabet_size);
    }

    // Шифрование символа верхнего регистра по таблице Виженера
    char TabulaRecta_Direct_UpCase(char cKey, char cData)
    {
    int i = cKey — Alfabet_start; // ключ в нижнем регистре
    int j = cData — Alfabet_start_big;
    return (char)(Alfabet_start_big + (i + j) % Alfabet_size);
    }

    // Дешифрование символа нижнего регистра по таблице Виженера
    char TabulaRecta_Back_LowCase(char cKey, char сCryptData)
    {
    int iKey = cKey — Alfabet_start;
    int iCryptCharOffsetFromAlfabetStart = сCryptData — Alfabet_start;
    return (char)(Alfabet_start + (iCryptCharOffsetFromAlfabetStart + Alfabet_size — iKey) % Alfabet_size);
    }

    // Дешифрование символа верхнего регистра по таблице Виженера
    char TabulaRecta_Back_UpCase(char cKey, char сCryptData)
    {
    int iKey = cKey — Alfabet_start; // ключ в нижнем регистре
    int iCryptCharOffsetFromAlfabetStart = сCryptData — Alfabet_start_big;
    return (char)(Alfabet_start_big + (iCryptCharOffsetFromAlfabetStart + Alfabet_size — iKey) % Alfabet_size);
    }

    • Не обязательно создавать таблицу. Можно и как вы, но сия таблица попадет в кеш, если вы ее создадите и обращения к ней будут в 10-100 раз быстрее, чем если каждый раз пересчитывать текущее значение из нее.

  • qwerty

    «Зачем нужно вручную вводить алфавит? Для того, чтобы можно было использовать какую — нибудь перестановку на нем».

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

Научиться программировать

  • на Delphi

  • на Java

  • на C++