Одним из древнейших шифров является шифр Цезаря. Этот шифр назван в честь римского императора Гая Юлия Цезаря, использовавшего его для секретной переписки со своими генералами. Идея данного шифрования заключается в замене всех букв послания на буквы, находящиеся на n позиций от текущей в алфавите. Наглядно этот процесс можно изобразить следующим образом:

Шифрование методом Цезаря
К слову сказать, Цезарь использовал этот шифр со сдвигом 3. Что ж, рассмотрим теперь математическую модель данного шифра. Она не представляет ничего сложного:


Здесь X представляет собой позицию исходной буквы в алфавите, K — сдвиг, которым шифруется сообщение, n — количество букв в алфавите и Y — положение зашифрованной буквы в алфавите. Данная процедура выполняется для каждой буквы послания. Переходим к реализации данного алгоритма шифрования. Весь процесс реализации алгоритма можно разбить на 3 части:
- Написание класса Alphabet, предоставляющего различные алфавиты.
- Написание класса Caesar, предоставляющего методы шифрования и дешифрования.
- Проверка работы на тестовых данных.
Итак, приступим.
Класс Alphabet
В первую очередь разработаем класс, который предоставляет различные алфавиты символов. «Зачем это нужно?», спросите вы. Ведь достаточно лишь задать строку, к примеру, с перечислением строчных русских букв и шифровать все послания ей. По этому поводу я могу выдвинуть следующие аргументы. Во-первых, вы не можете точно утверждать, что послание, которое необходимо зашифровать будет состоять только из русских строчных букв. Ведь если в этом послании будут другие символы, то вы столкнетесь со следующей проблемой: шифрование будет происходить успешно, на первый взгляд, но дешифрование может вывести интересный результат. Что бы не быть голословным, приведу пример реакции программы шифрования по Цезарю с алфавитом только из строчных русских букв. Обратите внимание, что послание намеренно состоит не только из строчных русских букв:

С чем связано то, что все символы, не входящие в алфавит заменились на буквы я? Вернемся к математической форме и остановим наше внимание на X — позицию исходной буквы в алфавите. А в нашем алфавите нет ни заглавных букв, ни пробелов, ни знаков препинания. Следовательно, определить положение данных символов в алфавите попросту невозможно! В таком случае, если вы пользуетесь методом IndexOf класса String, то получаете -1. Можно конечно запретить пользователю использовать иные символы или же удалять эти символы при считывании, но в это случае либо пользователь будет очень зол, что может использовать только строчные буквы, либо послание при расшифровке может получиться обрывочным. Во-вторых, все-таки мы живем в развивающимся мире, где программы должны быть гибкими (кроссплатформенными, мультиязычными, настраиваемыми), на случай, если вы например решили зашифровать сообщение на английском. Довольно слов, давайте рассмотрим реализацию данного класса:
static class Alphabet
{
static string alphabet;
///
/// Заглавные буквы английского алфавита
///
public static string EngU
{
get
{
alphabet = "";
for (char ch = 'A'; ch <= 'Z'; ch++)
{
alphabet += ch.ToString();
}
return alphabet;
}
}
///
/// Строчные буквы английского алфавита
///
public static string EngL
{
get
{
alphabet = "";
for (char ch = 'a'; ch <= 'z'; ch++)
{
alphabet += ch.ToString();
}
return alphabet;
}
}
///
/// Заглавные буквы русского алфавита
///
public static string RusU
{
get
{
alphabet = "";
for (char ch = 'А'; ch <= 'Я'; ch++)
{
alphabet += ch.ToString();
}
return alphabet;
}
}
///
/// Строчные буквы русского алфавита
///
public static string RusL
{
get
{
alphabet = "";
for (char ch = 'а'; ch <= 'я'; ch++)
{
alphabet += ch.ToString();
}
return alphabet;
}
}
///
/// Числа
///
public static string Numbers
{
get
{
alphabet = "";
for (char ch = '0'; ch <= '9'; ch++)
{
alphabet += ch.ToString();
}
return alphabet;
}
}
///
/// Скобки и знаки препинания
///
public static string SpecialCharacters
{
get
{
alphabet = "";
for (char ch = ' '; ch <= '/'; ch++)
{
alphabet += ch.ToString();
}
for (char ch = ':'; ch <= '?'; ch++)
{
alphabet += ch.ToString();
}
return alphabet;
}
}
///
/// Все доступные символы
///
public static string Everything
{
get
{
alphabet = "";
alphabet+=RusL;
alphabet+=RusU;
alphabet+=EngL;
alphabet+=EngU;
alphabet+=Numbers;
alphabet+=SpecialCharacters;
return alphabet;
}
}
}
}
public class Alphabet {
static String alphabet;
/**
* Метод, возвращающий алфавит заглавных
* английских букв
* @return Алфавит заглавных английских букв
*/
public static String getEnglishU(){
alphabet = "";
for(char ch='A';ch<='Z';ch++){
alphabet+=ch;
}
return alphabet;
}
/**
* Метод, возвращающий алфавит строчных
* английских букв
* @return Алфавит строчных английских букв
*/
public static String getEnglishL(){
alphabet = "";
for(char ch='a';ch<='z';ch++){
alphabet+=ch;
}
return alphabet;
}
/**
* Метод, возвращающий алфавит заглавных
* русских букв
* @return Алфавит заглавных русских букв
*/
public static String getRussianU(){
alphabet = "";
for(char ch='А';ch<='Я';ch++){
alphabet+=ch;
}
return alphabet;
}
/**
* Метод, возвращающий алфавит строчных
* русских букв
* @return алфавит строчных русских букв
*/
public static String getRussianL(){
alphabet = "";
for(char ch='а';ch<='я';ch++){
alphabet+=ch;
}
return alphabet;
}
/**
* Метод, возвращающий строку цифр
* @return строку цифр
*/
public static String getNumbers(){
alphabet = "";
for(char ch='0';ch<='9';ch++){
alphabet+=ch;
}
return alphabet;
}
/**
* Метод, возвращающий скобки и знаки препинания
* @return строка скобок и знаков препинания
*/
public static String getSpecialCharacters(){
alphabet = "";
for(char ch=' ';ch<='/';ch++){
alphabet+=ch;
}
for(char ch=':';ch<='?';ch++){
alphabet+=ch;
}
return alphabet;
}
/**
* Метод, возвращает набор всех возможных символов
* @return строка всех символов
*/
public static String getEverything(){
alphabet="";
alphabet+=Alphabet.getRussianL();
alphabet+=Alphabet.getRussianU();
alphabet+=Alphabet.getEnglishL();
alphabet+=Alphabet.getEnglishU();
alphabet+=Alphabet.getNumbers();
alphabet+=Alphabet.getSpecialCharacters();
return alphabet;
}
}
Как вы можете заметить, это статический класс, членами которого являются свойства, доступные только для чтения. Каждое свойство содержит в себе некоторый алфавит или другой однотипный набор символов. Данный данный класс довольно просто расширяется. Итак, этот этап закончен.
Класс Ceasar
Теперь давайте обсудим из чего должен состоять класс Caesar предоставляющий шифрование методом цезаря. Очевидно, что у данного класса должно быть два метода: зашифровать и дешифровать, соответственно. На вход первого метода подаются исходный текст и величина сдвига, на которую будет производится шифрование. На вход второго — зашифрованный текст и величина сдвига, на которую текст был зашифрован. Очевидно, для того, чтобы шифрование-дешифрование произошло успешно, необходимо, что бы величина сдвига при дешифровании была такой же как и при шифровании. Сами методы представляют собой простую реализацию математической модели для каждой буквы послания (шифра). Это реализовано в форме цикла. Вот так выглядит готовый класс:
class Caesar
{
string alphabet; // Алфавит, заданный пользователем
/// <summary>
/// Конструктор класса
/// </summary>
/// <param name="_alphabet">Алфавит шифрования</param>
public Caesar(string _alphabet)
{
alphabet = _alphabet;
}
/// <summary>
/// Метод Цезаря шифрование текста
/// </summary>
/// <param name="text">Исходный текст</param>
/// <param name="position">Количество позиций для сдвига</param>
/// <returns>Зашифрованный текст</returns>
public string getEncryption(string text, int position)
{
string cipher = "";
for (int i = 0; i < text.Length; i++)
{
cipher += alphabet[(alphabet.IndexOf(text[i]) + position) % alphabet.Length].ToString();
}
return cipher;
}
public string getDecryption(string cipher, int position)
{
string text = "";
for (int i = 0; i < cipher.Length; i++)
{
if (alphabet.IndexOf(cipher[i]) - position < 0)
{
text += alphabet[alphabet.Length - 1 +
(alphabet.IndexOf(cipher[i]) - position + 1) % alphabet.Length];
}
else
{
text += alphabet[(alphabet.IndexOf(cipher[i]) - position) % alphabet.Length];
}
}
return text;
}
}
public class Caesar {
String alphabet; // Алфавит, заданный пользователем
/**
* Конструктор класса
* @param Алфавит шифрования
*/
public Caesar(String _alphabet){
alphabet=_alphabet;
}
/**
* Метод Цезаря шифрование текста
* @param Исходный текст
* @param Количество позиций для сдвига
* @return Зашифрованный текст
*/
public String getEncryption(String text, int position){
String cipher ="";
for(int i=0;i<text.length();i++){
cipher+=alphabet.charAt((alphabet.indexOf(text.charAt(i))+position)%alphabet.length());
}
return cipher;
}
/**
* Метод Цезаря де шифрование текста
* @param Зашифрованный текст
* @param Количество позиций для сдвига
* @return Исходный текст
*/
public String getDecryption(String cipher, int position){
String text ="";
for(int i=0;i<cipher.length();i++){
if(alphabet.indexOf(cipher.charAt(i))-position<0){
text+=alphabet.charAt(alphabet.length() - 1 +
(alphabet.indexOf(cipher.charAt(i))-position + 1)%alphabet.length());
}else{
text+=alphabet.charAt((alphabet.indexOf(cipher.charAt(i))-position)%alphabet.length());
}
}
return text;
}
}
Следующее изображение дает отличное пояснение, что есть что:

Осталось только пояснить условие, находящееся в методе дешифрования. Дело в том, что положение буквы зашифрованного послания в алфавите может оказаться гораздо меньше величины сдвига. Таким образом, величина, представленная в математической модели, как Y — K может принимать сколь угодно большое отрицательное значение. Однако, если значение отрицательное, то мы можем уверенно сказать, что его абсолютная величина обозначает положение буквы в алфавите относительно его конца. Следовательно нам только остается прибавить величину алфавите и мы получим положение необходимой буквы.
Тестовый запуск
Теперь нам только проверить работу нашего класса. Что ж, прописываем следующий код в главном методе программы:
static void Main(string[] args)
{
Console.WriteLine("Введите текст послания:");
string text = Console.ReadLine();
Console.WriteLine("Введите величину сдвига:");
int K = int.Parse(Console.ReadLine());
// Получаем необходимый алфавит
string alphabet = Alphabet.Everything;
// Создать объект класса шифр Цезаря над данным алфавитом
Caesar caesar = new Caesar(alphabet);
// Получить шифр Цезаря для заданной строки cipher со сдвигом K
string cipher = caesar.getEncryption(text, K);
// Распечатать полученный шифр полученный шифр
Console.WriteLine(cipher);
// Распечатать дешифровку для cipher со сдвигом K
Console.WriteLine(caesar.getDecryption(cipher, K));
Console.ReadKey();
}
public static void main(String[] args) throws UnsupportedEncodingException {
String alphabet = ""; // Определить алфавит
alphabet+=Alphabet.getEverything(); // Задать алфавит из всех используемых символов
String charsetName = "Cp1251";
BufferedReader in = new BufferedReader(new InputStreamReader(System.in,charsetName));
try{
System.out.println("Введите текст послания:");
String text = in.readLine();
System.out.println("Введите величину сдвига:");
int K = Integer.parseInt(in.readLine());
//Создать объект класса шифр Цезаря над данным алфавитом
Caesar caesar = new Caesar(alphabet);
// Получить шифр Цезаря для заданной строки cipher со сдвигом 10
String cipher = caesar.getEncryption(text, K);
// Распечатать полученный шифр полученный шифр
System.out.println(cipher);
// Распечатать дешифровку для cipher со сдвигом 10
System.out.println(caesar.getDecryption(cipher, K));
}catch(Exception ex){
System.out.println(ex);}
}
Запускаем и убеждаемся, что программа работает корректно. Правда, все равно можно найти такой символ, который не попадет в наш алфавит, т. к. существует еще множество символов, которые мы не учли. Но это просто сделать, добавив в классе Alphabet метод, который будет возвращать все возможные символы. Я этого не делаю, т. к. считаю, что это не относится к теме данной статьи, а для нормальной передачи сообщений достаточно и предоставленных алфавитов.
Выводы
Вы можете скачать реализацию на следующих языках программирования:
Итак мы рассмотрели один из самых простейших методов шифрования: шифр Цезаря. Следует сказать, что на практике данный метод не используется уже очень давно в силу следующих причин:
- Легко взламывается методом анализа по частоте;
- Легко взламывается методом тотального перебора.
Однако его модификация еще недавно активно использовалась на практике. Это шифр Виженера. О нем и пойдет речь в нашей следующей статье. Спасибо за внимание и не стесняйтесь оставлять комментарии, так как именно они заставляют совершенствовать контент и добиваться полного понимания материала.
Статья класс! Хочу почитать продолжение!
я чет не понял особо понял что например привет то оно шифруется рсйгеу типа здвиг всех букв на одно вправо …… я правильно понял ?
Просто здорово! Очень красивый сайт, описано-объянено-разжовано и в рот положено! Спасиб огромное! Сама реализовывала программку шифратор-дешифратор на основе шифра Цезаря, но было приятно посмотреть на ваше исполнение..)
Единственное, что некоторые спец символы оно не распознает, на и перенос каретки вместе табуляцией он не разбирает..
В классе Дешифратора есть лишние строчки, которые в принципе не нужны.)) Это условие нужно выкинуть, тк оно роли не играет и оставить вот так:
public string getDecryption(string cipher, int position)
{
string text = «»;
for (int i = 0; i < cipher.Length; i++)
{
text += alphabet[(alphabet.Length +
(alphabet.IndexOf(cipher[i]) — position)) % alphabet.Length];
}
return text;
}
У меня лично работает без этого if ))) Хотя и не тестила эту прогу, но думаю, что ничего не поменяется.
Проверил программу, к сожалению заметил, что одну ошибочку.
Если в тексте выбрать написать, например, автор, сдвиг сделать минус 2, то программа закроется с ошибкой, хотя должно получиться юармо
Спасибо за обнаруженную ошибку. Действительно, будет выброшено исключение, т. к. значение выражения (alphabet.IndexOf(text[i]) + position) % alphabet.Length будет отрицательным. Проблема решается очень просто. Что бы индекс (это выражение) не принимал отрицательное значение, необходимо в скобках прибавить размер алфавита (т. е. (alphabet.Length + alphabet.IndexOf(text[i]) + position) % alphabet.Length). Текст статьи с соответствующими корректировками будет обновлен в ближайшее время.
еще одна поправочка кода, так как это шифрование и это процесс не из быстрых, относительно, то код существенно ускорит использование вместо String — StringBilder, а вместо += — .append();
PS подскажите как называется плагин комментариев на сайте