Файл: Лабораторная работа 3 Программная реализация классических алгоритмов шифрования и их криптоанализа Цель.pdf
ВУЗ: Не указан
Категория: Не указан
Дисциплина: Не указана
Добавлен: 03.02.2024
Просмотров: 170
Скачиваний: 3
ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
return
CipherText
Соответственно функция расшифрования будет использовать вычитание из кода символа шифротекста значение смещения
Key и дальнейшего преобразования кода в символ исходного текста:
Задание.
По аналогии с описанием функции шифрования, напишите самостоятельно обратную ей функцию - функцию дешифрования
decryption, принимающую на вход два аргумента (ключ шифрования
Key, зашифрованный текст
CipherText
)
и возвращающей исходный текст
PlainText.
Теперь, когда готовы к вызову функции шифрования и расшифрования, скорректируем основной модуль
main.py. При вызове функций
encryption() и decryption () из числа аргументов необходимо исключить
alpha, а также в первой строке вместо модуля functions.py прописать
functions_v2.py.
Теперь все готово для тестирования программы. В командной строке запускаем скрипт
main.py. Текст для шифрования должен содержать русские и латинские буквы, имеющие разный регистр, а также числа и знаки препинания. В указанном на рис. Х примере выбран исходный текст «ноутбук с Windows 10 Pro (x64)». Повторите действия согласно данному примеру. В результате расшифрования должен получиться исходный текст. Для того, чтобы скопировать из консоли шифротекст необходимо в поле консоли нажать правую кнопку мыши, из контекстного меню выбрать команду «Пометить», выделить левой кнопкой мыши нужный фрагмент текста и нажать «Enter».
Затем установить курсор в том месте, где нужно вставить текст, снова вызвать контекстное меню и выбрать команду «Вставить».
CipherText
Соответственно функция расшифрования будет использовать вычитание из кода символа шифротекста значение смещения
Key и дальнейшего преобразования кода в символ исходного текста:
Задание.
По аналогии с описанием функции шифрования, напишите самостоятельно обратную ей функцию - функцию дешифрования
decryption, принимающую на вход два аргумента (ключ шифрования
Key, зашифрованный текст
CipherText
)
и возвращающей исходный текст
PlainText.
Теперь, когда готовы к вызову функции шифрования и расшифрования, скорректируем основной модуль
main.py. При вызове функций
encryption() и decryption () из числа аргументов необходимо исключить
alpha, а также в первой строке вместо модуля functions.py прописать
functions_v2.py.
Теперь все готово для тестирования программы. В командной строке запускаем скрипт
main.py. Текст для шифрования должен содержать русские и латинские буквы, имеющие разный регистр, а также числа и знаки препинания. В указанном на рис. Х примере выбран исходный текст «ноутбук с Windows 10 Pro (x64)». Повторите действия согласно данному примеру. В результате расшифрования должен получиться исходный текст. Для того, чтобы скопировать из консоли шифротекст необходимо в поле консоли нажать правую кнопку мыши, из контекстного меню выбрать команду «Пометить», выделить левой кнопкой мыши нужный фрагмент текста и нажать «Enter».
Затем установить курсор в том месте, где нужно вставить текст, снова вызвать контекстное меню и выбрать команду «Вставить».
Таким образом, мы избавились от необходимости вручную задавать алфавит и расширили возможности программы в части используемых символов для шифрования и расшифрования.
Но по-прежнему текст вводили вручную с консоли. Добавим программе возможность шифрования текста из какого-либо файла с расширением .txt и запись результата шифрования и расшифрования в этот же или другой файл, при этом сохранив возможность работы с консолью. Для этого нам необходимо создать дополнительные функции
reading() и writing().
Функция
reading() будет сначала запрашивать у пользователя способ получения исходного текста (из консоли или файла). Если пользователь выберет чтение из файла, то функция дополнительно запросит у пользователя имя файла с расширением .txt, из которого она получит текст вернет его для дальнейших преобразований. Если пользователь выберет чтение с консоли, то функция вернет текст, введенный с консоли.
Функция
writing(data, operation) принимает на вход текст и вид операции, который предшествовал вызову функции, т.е. шифрование или расшифрование. Подобно функции
reading() здесь также будет предусмотрена возможность работы с консолью (вывод данных в консоль).
Приведем код функции
reading() def reading(): source = input("Источник данных: \n 'к' - консоль \t 'ф' - файл \n") if source == 'к': data = input("Введите текст: ").strip()
return data elif source == 'ф': filename = input("Название файла с данными в формате '*.txt': \n") with open(filename, 'r') as f: data = f.read() data = data.encode('cp1251').decode('cp1251') return data else:
print("[-] Выбрана неверная операция")
Выбор способа получения данных реализован в конструкции условного перехода после того, как пользователь ввел в консоль букву, соответствующую нужному источнику.
Первая часть приведенной здесь конструкции условного перехода, начинающаяся после команды
if,необходима для предоставления возможности ввода данных в консоль. После выполнения строки кода
«
return data»
введенные пользователем данные возвращаются для дальнейших преобразований.
Во второй части конструкции условного перехода, начинающейся после команды
elif и заканчивающейся также строкой «return data», реализована возможность чтения данных из файла. Сперва нам необходимо получить название файла, из которого будет происходит чтение, и записать его в переменную
filename: filename = input("Название файла с данными в формате '*.txt': \n")
Далее необходимо открыть данный файл, прочитать из него текст, и записать текст в переменную
data, чтобы функция reading() могла передать его далее в качестве аргумента функциям шифрования или дешифрования.
Наиболее подходящей конструкцией для чтения данных из файла будет контекстный менеджер
with … as, в котором задействована встроенная функция
open(). with open(filename, 'r') as f: data = f.read()
У функции
open() много параметров, нам пока важны 3 аргумента: первый, это имя файла. Путь к файлу может быть относительным или абсолютным.
Второй аргумент, это режим, в котором мы будем открывать файл.
print("[-] Выбрана неверная операция")
Выбор способа получения данных реализован в конструкции условного перехода после того, как пользователь ввел в консоль букву, соответствующую нужному источнику.
Первая часть приведенной здесь конструкции условного перехода, начинающаяся после команды
if,необходима для предоставления возможности ввода данных в консоль. После выполнения строки кода
«
return data»
введенные пользователем данные возвращаются для дальнейших преобразований.
Во второй части конструкции условного перехода, начинающейся после команды
elif и заканчивающейся также строкой «return data», реализована возможность чтения данных из файла. Сперва нам необходимо получить название файла, из которого будет происходит чтение, и записать его в переменную
filename: filename = input("Название файла с данными в формате '*.txt': \n")
Далее необходимо открыть данный файл, прочитать из него текст, и записать текст в переменную
data, чтобы функция reading() могла передать его далее в качестве аргумента функциям шифрования или дешифрования.
Наиболее подходящей конструкцией для чтения данных из файла будет контекстный менеджер
with … as, в котором задействована встроенная функция
open(). with open(filename, 'r') as f: data = f.read()
У функции
open() много параметров, нам пока важны 3 аргумента: первый, это имя файла. Путь к файлу может быть относительным или абсолютным.
Второй аргумент, это режим, в котором мы будем открывать файл.
Режим
Расшифровка 'r' открытие на чтение (является значением по умолчанию)
'w' открытие на запись, содержимое файла удаляется, если файла не существует, создается новый 'x' открытие на запись, если файла не существует, иначе исключение.
'a' открытие на дозапись, информация добавляется в конец файла 'b' открытие в двоичном режиме 't' открытие в текстовом режиме (является значением по умолчанию)
'+' открытие на чтение и запись
Режимы могут быть объединены, то есть, к примеру,
'rb' - чтение в двоичном режиме. По умолчанию режим равен
'r'.
И последний аргумент,
encoding, нужен только в текстовом режиме чтения файла. Этот аргумент задает кодировку.
После открытия файла необходимо прочитать из него информацию. Для этого будем использовать метод
read(), читающий весь файл целиком, если был вызван без аргументов, и n символов, если был вызван с аргументом (целым числом n).
Строка data = data.encode('cp1251').decode('cp1251') предназначена для представления данных из файла в кодировке Windows 1251.
Теперь рассмотрим функцию записи в файл
writing(data, operation).
Ниже представлен код данной функции. def writing(data, operation): op = operationtype(operation) exit = input("Вид вывода данных: \n 'к' - консоль \t 'ф' - файл: \n") if exit == 'к': print("Результат: ", data) elif exit == 'ф': string = f"Файл для вывода результата {op} в формате 'имя.txt': \n" filename = input(string) with open(filename, 'w') as f: for simbol in data: try:
f.write(i.encode("cp1251").decode("cp1251")) except:
print(i) pass print("\n[+]
Результат
%s записан в файл
%s"
%(operationtype(operation), filename)) else:
print("[-] Выбрана неверная операция")
Функция
writing(data, operation) принимает на вход два аргумента:
-
data – данные для записи в файл, например после шифрования
- operation – вид криптографического преобразования (шифрование или расшифрование)
Аналогично функции
reading() здесь используется условная конструкция, позволяющая выбрать способ вывода данных (в консоль или в файл). Во второй части условной конструкции в контекстном менеджере
with … as
функция
open() работает в режиме записи. with open(filename, 'w') as f:
В отличие от метода
read(), где файл считывался целиком, запись будем производить посимвольно с помощью цикла
for: for
symbol in
data:
Это позволяет избежать остановки программы из-за ошибок декодирования некоторых символов. Для того, чтобы пропустить символы, декодирование которых сопровождается исключением UnicodeDecodeError в программе использован обработчик исключений
try…except… В блоке try мы выполняем инструкцию, которая может породить исключение, а в блоке
except мы перехватываем их и на месте пропущенного символа ставим пробел или любой другой символ, далее переходим к следующей итерации, т.е. к следующему символу. try:
f.write(i.encode("cp1251").decode("cp1251")) except:
f.write(‘ ‘)
В процессе выполнения программы пользователю нужно вводить имя файла, в который будет произведена запись результата шифрования или расшифрования. Чтобы пользователь по ошибке не перепутал файлы,
Результат
%s записан в файл
%s"
%(operationtype(operation), filename)) else:
print("[-] Выбрана неверная операция")
Функция
writing(data, operation) принимает на вход два аргумента:
-
data – данные для записи в файл, например после шифрования
- operation – вид криптографического преобразования (шифрование или расшифрование)
Аналогично функции
reading() здесь используется условная конструкция, позволяющая выбрать способ вывода данных (в консоль или в файл). Во второй части условной конструкции в контекстном менеджере
with … as
функция
open() работает в режиме записи. with open(filename, 'w') as f:
В отличие от метода
read(), где файл считывался целиком, запись будем производить посимвольно с помощью цикла
for: for
symbol in
data:
Это позволяет избежать остановки программы из-за ошибок декодирования некоторых символов. Для того, чтобы пропустить символы, декодирование которых сопровождается исключением UnicodeDecodeError в программе использован обработчик исключений
try…except… В блоке try мы выполняем инструкцию, которая может породить исключение, а в блоке
except мы перехватываем их и на месте пропущенного символа ставим пробел или любой другой символ, далее переходим к следующей итерации, т.е. к следующему символу. try:
f.write(i.encode("cp1251").decode("cp1251")) except:
f.write(‘ ‘)
В процессе выполнения программы пользователю нужно вводить имя файла, в который будет произведена запись результата шифрования или расшифрования. Чтобы пользователь по ошибке не перепутал файлы,
приглашение для ввода их имен нужно формулировать по-разному. Например, когда нужно произвести запись данных после шифрования, сообщение для пользователя будет сформулировано следующим образом:
Файл для вывода результата шифрования в формате 'имя.txt':
А после расшифрования:
Файл для вывода результата расшифрования в формате 'имя.txt':
В данной строке изменяется только одно слово. Напишем вместо него переменную
op в круглых скобках: string = f"Файл для вывода результата {op} в формате 'имя.txt': \n"
Буква f впереди строки позволяет подставлять значение строковой переменной
op чтобы, строка имела нужную нам формулировку.
Как вы могли заметить, в теле функции
writing() используется вспомогательная функция
operationtype(operation), которая возвращает строку
«шифрования» если была выбрана операция шифрования
(пользователь ввел в консоль «ш»), и «расшифрования» - когда пользователь ввел «р». Результат функции записывается в переменную
op. Код данной функции приведен ниже: def operationtype(operation): sh = "шифрования" rsh = "расшифрования" if operation == "ш": return sh elif operation == "р": return rsh
Теперь необходимо внести изменения в основной модуль
main.py. Код данного модуля приведен ниже: from functions_v2 import encryption, decryption, reading, writing,
Key = int(input("Введите ключ шифрования: ")) operation = str(input("Введите 'ш' для шифрования, 'р' для расшифрования: ")) if operation == 'ш':
PlainText = reading()
CipherText = encryption(Key, PlainText) print("\n[+] Шифрование завершено\n")
writing(CipherText, operation)
elif operation == 'р': # если необходимо расшифровать текст
CipherText = reading()
PlainText = decryption(Key, CipherText) print("\n[+] Расшифрование завершено\n")
writing(PlainText, operation)
Файл для вывода результата шифрования в формате 'имя.txt':
А после расшифрования:
Файл для вывода результата расшифрования в формате 'имя.txt':
В данной строке изменяется только одно слово. Напишем вместо него переменную
op в круглых скобках: string = f"Файл для вывода результата {op} в формате 'имя.txt': \n"
Буква f впереди строки позволяет подставлять значение строковой переменной
op чтобы, строка имела нужную нам формулировку.
Как вы могли заметить, в теле функции
writing() используется вспомогательная функция
operationtype(operation), которая возвращает строку
«шифрования» если была выбрана операция шифрования
(пользователь ввел в консоль «ш»), и «расшифрования» - когда пользователь ввел «р». Результат функции записывается в переменную
op. Код данной функции приведен ниже: def operationtype(operation): sh = "шифрования" rsh = "расшифрования" if operation == "ш": return sh elif operation == "р": return rsh
Теперь необходимо внести изменения в основной модуль
main.py. Код данного модуля приведен ниже: from functions_v2 import encryption, decryption, reading, writing,
Key = int(input("Введите ключ шифрования: ")) operation = str(input("Введите 'ш' для шифрования, 'р' для расшифрования: ")) if operation == 'ш':
PlainText = reading()
CipherText = encryption(Key, PlainText) print("\n[+] Шифрование завершено\n")
writing(CipherText, operation)
elif operation == 'р': # если необходимо расшифровать текст
CipherText = reading()
PlainText = decryption(Key, CipherText) print("\n[+] Расшифрование завершено\n")
writing(PlainText, operation)
else: print("[-] Выбрана неверная операция") # если введена неверная операция
Протестируем созданную программу. Текст «ноутбук с Windows 10 Pro (x64)».
Для этого создадим в папке
lab_rabota текстовый файл исходный
текст.txt и запишем в него текст «ноутбук с Windows 10 Pro (x64)».
В командной строке запускаем скрипт
main.py. Вводим ключ шифрования, выбираем тип преобразования – шифрование, в качестве источника данных выбираем файл и указываем его имя с расширением.
Далее нам необходимо ввести имя файла для записи зашифрованных данных.
Введем шифротекст.txt.
Результат выполнения программы представлен на рис. Х. Также откроем файл шифротекст.txt для просмотра его содержимого.
Протестируем созданную программу. Текст «ноутбук с Windows 10 Pro (x64)».
Для этого создадим в папке
lab_rabota текстовый файл исходный
текст.txt и запишем в него текст «ноутбук с Windows 10 Pro (x64)».
В командной строке запускаем скрипт
main.py. Вводим ключ шифрования, выбираем тип преобразования – шифрование, в качестве источника данных выбираем файл и указываем его имя с расширением.
Далее нам необходимо ввести имя файла для записи зашифрованных данных.
Введем шифротекст.txt.
Результат выполнения программы представлен на рис. Х. Также откроем файл шифротекст.txt для просмотра его содержимого.
Далее протестируем обратную операцию – расшифрование. Результат расшифрования запишем в файл расшифровка.txt
Файл расшифровка.txt будет иметь следующее содержимое:
Таким образом, была разработана и протестирована программа для шифрования и расшифрования методом Цезаря, предусматривающая возможность работы с консолью и содержимым файлов.
Пример программной реализации криптоанализа шифра Цезаря методом
протяжки вероятного слова на языке программирования Python.
В данном разделе напишем программу для реализации атаки на шифр Цезаря методом протяжки вероятного слова. Данный метод заключается в том, что на основании дополнительных данных, предполагается наличие в одном из открытых текстов конкретного слова. После фиксации слова, последовательно начиная с первой позиции оно подставляется в текст, при этом однозначно восстанавливается отрезок второго текста, который (в случае правильного позиционирования слова в первом тексте) можно продолжить по соображениям читаемости, в этом случае в первом тексте восстанавливается новый отрезок, который также можно продлить и т.д. Проиллюстрируем вышесказанное с помощью следующей схемы:
Рис. Х
В качестве вероятных слов могут быть наиболее распространенные слова языка, на котором был написан исходный текст (такими словами как правило являются предлоги), а также термины или понятия, соответствующие
Ц
24
Р
18
У
21
Ж
8
У
21
О
16
Т
20
К
12
З
9 19
С
13
Л
16
О
3
В
16
О
У
21
Х
23
У
21
Ж
8
К
12 5
5 5
5 5
По
зи
ц
и
я
в
ал
ф
ав
и
те
Буквы шифротекста
Вероятное слово
19
С
13
Л
16
О
3
В
16
О
1
-1
-7 18 7
Разность
и другие служебные части речи
вероятной тематике исходного текста. Вычисляя разность между индексами символов (позициями в алфавите) шифротекста и вероятного слова, необходимо находить закономерности для нескольких подряд идущих символов. Если для двух и более символов смещение одинаково, можно сделать предположение о размере ключа и применить его для расшифрования.
Очевидно, что если предположение сделано верно, то в результате операции расшифрования мы получим исходный текст. В противном случае необходимо перейти к следующему фрагменту шифротекста или новому вероятному слову.
Модульный подход, использованный нами при разработке предыдущих программ, позволит использовать готовые функции расшифрования текстов
decryption(), чтения reading() и записи writing() их в файл. Поэтому в первую очередь импортируем их в новый модуль
attack.py: from functions import decryption, reading, writing
Получим из внешнего файла зашифрованный текст: ciphertext = reading()
Составим список наиболее распространенных слов: voc = [' и ',' в ',' не ',' на ',' что ',' по ',' к ',' но ',' у ',' как ']
Этот список можно значительно расширить для повышения эффективности атаки на шифр, но для данной работы мы ограничимся только самыми распространенными предлогами.
Далее нам нужен алгоритм, который перебирает слова из списка
voc для передачи их в качестве аргумента в некоторую функцию
finding()(в программном коде ниже вместо описания функции пока поставим команду- заглушку
pass), возвращающей значение предполагаемого смещения, а после запускает функцию расшифрования. Данный алгоритм можно реализовать следующим образом: def finding(word, ciphertext): pass for word in voc: key = finding(word, ciphertext) if key != None: print(decryption(key, ciphertext)) break служебными частями речи
Очевидно, что если предположение сделано верно, то в результате операции расшифрования мы получим исходный текст. В противном случае необходимо перейти к следующему фрагменту шифротекста или новому вероятному слову.
Модульный подход, использованный нами при разработке предыдущих программ, позволит использовать готовые функции расшифрования текстов
decryption(), чтения reading() и записи writing() их в файл. Поэтому в первую очередь импортируем их в новый модуль
attack.py: from functions import decryption, reading, writing
Получим из внешнего файла зашифрованный текст: ciphertext = reading()
Составим список наиболее распространенных слов: voc = [' и ',' в ',' не ',' на ',' что ',' по ',' к ',' но ',' у ',' как ']
Этот список можно значительно расширить для повышения эффективности атаки на шифр, но для данной работы мы ограничимся только самыми распространенными предлогами.
Далее нам нужен алгоритм, который перебирает слова из списка
voc для передачи их в качестве аргумента в некоторую функцию
finding()(в программном коде ниже вместо описания функции пока поставим команду- заглушку
pass), возвращающей значение предполагаемого смещения, а после запускает функцию расшифрования. Данный алгоритм можно реализовать следующим образом: def finding(word, ciphertext): pass for word in voc: key = finding(word, ciphertext) if key != None: print(decryption(key, ciphertext)) break служебными частями речи
Возвращаемое функцией
finding() значение записывается в переменную
key и используется далее для расшифрования текста с помощью функции
decryption(key, ciphertext). Команда
break останавливает цикл
for…in при наличии хотя бы одного полученного ключа шифрования.
Рассмотрим подробнее функцию
finding(word, ciphertext).
Данная функция принимает на вход два аргумента:
word – вероятное слово из списка voc,
ciphertext – шифротекст, полученный путем шифрования исходного текста методом одноалфавитной замены (шифр Цезаря)
Программный код функции:
1 def finding(word, ciphertext):
2 for i in range(len(ciphertext)-len(word)):
3 g = [ord(ciphertext[i+j])-ord(word[j]) for j in range(len(word))]
4 s = 0 5 for k in range(len(g)):
6 if g[k] == g[0]:
7 s +=1 8 if s == len(g):
9 return g[0]
В строке №2 с помощью цикла
for…in… происходит перебор индексов символов в шифротексте, начиная с нулевого и заканчивая индексом, полученным в результате разности количества символов шифротекста
len(ciphertext)
и количества символов вероятного слова
len(word).
Значение индекса
i
в каждой итерации данного цикла будет использовано далее как начало очередного фрагмента шифротекста, сравниваемого с вероятным словом.
В строке №3 происходит генерация списка разностей между позициями в алфавите (таблице кодировки) каждой пары символов шифротекста и вероятного слова. Например, согласно схеме на рис. Х для первых пяти символов начиная с первого символа (но индекс у него равен 0), так как нумерация по умолчанию начинается с 0) шифротекста будет сгенерирован следующий список разностей – [5,5,5,5,5]. В таблице Х показано каким образом данный список был получен.
Символ шифротекста
Индекс символа шифротекста
Символ вероятного слова
Индекс символа вероятного слова
Разность ц
24 с
19 5