Шифрование сообщений в Python. От простого к сложному. Шифр Цезаря
Мне, лично, давно была интересна тема шифрования информации, однако, каждый раз погрузившись в эту тему, я осознавал насколько это сложно и понял, что лучше начать с чего-то более простого. Я, лично, планирую написать некоторое количество статей на эту тему, в которых я покажу вам различные алгоритмы шифрования и их реализацию в Python, продемонстрирую и разберу свой проект, созданный в этом направлении. Итак, начнем.
Для начала, я бы хотел рассказать вам какие уже известные алгоритмы мы рассмотрим, в моих статьях. Список вам представлен ниже:
- Шифр Цезаря
- Шифр Виженера
- Шифр замены
- Омофонический шифр
- RSA шифрование
Шифр Цезаря
Итак, после небольшого введения в цикл, я предлагаю все-таки перейти к основной теме сегодняшней статьи, а именно к Шифру Цезаря.
Что это такое?
Шифр Цезаря — это простой тип подстановочного шифра, где каждая буква обычного текста заменяется буквой с фиксированным числом позиций вниз по алфавиту. Принцип его действия можно увидеть в следующей иллюстрации:
Какими особенностями он обладает?
У Шифра Цезаря, как у алгоритма шифрования, я могу выделить две основные особенности. Первая особенность — это простота и доступность метода шифрования, который, возможно поможет вам погрузится в эту тему, вторая особенность — это, собственно говоря, сам метод шифрования.
Программная реализация
В интернете существует огромное множество уроков, связанных с криптографией в питоне, однако, я написал максимально простой и интуитивно понятный код, структуру которого я вам продемонстрирую.
Начнем, пожалуй, с создания алфавита. Для этого вы можете скопировать приведенную ниже строку или написать все руками.
alfavit = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ' # Создаем алфавит
Далее, нам нужно обозначить программе шаг, то есть смещение при шифровании. Так, например, если мы напишем букву «а» в сообщении, тот при шаге «2», программа выведет нам букву «в».
Итак, создаем переменную smeshenie, которая будет вручную задаваться пользователем, и message, куда будет помещаться наше сообщение, и, с помощью метода upper(), возводим все символы в нашем сообщении в верхний регистр, чтобы у нас не было ошибок. Потом создаем просто пустую переменную itog, куда мы буем выводить зашифрованное сообщение. Для этого пишем следующее:
smeshenie = int(input('Шаг шифровки: ')) #Создаем переменную с шагом шифровки message = input("Сообщение для шифровки: ").upper() #создаем переменнную, куда запишем наше сообщение itog = '' #создаем переменную для вывода итогового сообщения
Итак, теперь переходим к самому алгоритму шифровки. Первым делом создаем цикл for , где мы определим место букв, задействованных в сообщении, в нашем списке alfavit, после чего определяем их новые места (далее я постараюсь насытить код с пояснениями):
for i in message: mesto = alfavit.find(i) #Вычисляем места символов в списке new_mesto = mesto + smeshenie #Сдвигаем символы на указанный в переменной smeshenie шаг
Далее, мы создаем внутри нашего цикла условие if , в нем мы записываем в список itog мы записываем наше сообщение уже в зашифрованном виде и выводим его:
for i in message: mesto = alfavit.find(i) new_mesto = mesto + smeshenie if i in alfavit: itog += alfavit[new_mesto] # Задаем значения в итог else: itog += i print (itog)
Модернизация
Вот мы и написали программу, однако она имеет очень большой недостаток: «При использовании последних букв(русских), программа выведет вам английские буквы. Давайте это исправим.
Для начала создадим переменную lang, в которой будем задавать язык нашего шифра, а так же разделим английский и русский алфавиты.
alfavit_EU = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' alfavit_RU = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ' smeshenie = int(input('Шаг шифровки: ')) message = input("Сообщение для шифровки: ").upper() itog = '' lang = input('Выберите язык RU/EU: ') #Добавляем возможность выбора языка
Теперь нам надо создать условие, которое проверит выбранный язык и применит его, то есть обратится к нужному нам алфавиту. Для этого пишем само условие и добавляем алгоритм шифрования, с помощью которого будет выполнено шифрование:
if lang == 'RU': for i in message: mesto = alfavit_RU.find(i) # Алгоритм для шифрования сообщения на русском new_mesto = mesto + smeshenie if i in alfavit_RU: itog += alfavit_RU[new_mesto] else: itog += i else: for i in message: mesto = alfavit_EU.find(i) # Алгоритм для шифрования сообщения на английском new_mesto = mesto + smeshenie if i in alfavit_EU: itog += alfavit_EU[new_mesto] else: itog += i
Дешифровка сообщения
Возможно это прозвучит несколько смешно, но мы смогли только зашифровать сообщение, а насчет его дешифровки мы особо не задумывались, но теперь дело дошло и до неё.
По сути, дешифровка — это алгоритм обратный шифровке. Давайте немного переделаем наш код (итоговый вид вы можете увидеть выше).
Для начала, я предлагаю сделать «косметическую» часть нашей переделки. Для этого перемещаемся в самое начало кода:
alfavit = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ' smeshenie = int(input('Шаг шифровки: ')) message = input("Сообщение для ДЕшифровки: ").upper() #заменяем слово шифровка, на дешифровка itog = ''
Остальное можно оставить так же, но если у вас есть желание, то можете поменять названия переменных.
По большому счету, самые ‘большие’ изменения у нас произойдут в той части кода, где у нас находится алгоритм, где нам нужно просто поменять знак «+» на знак «-«. Итак, переходим к самому циклу:
if lang == 'RU': for i in message: mesto = alfavit_RU.find(i) new_mesto = mesto + smeshenie # Меняем знак + на знак - if i in alfavit_RU: itog += alfavit_RU[new_mesto] else: itog += i else: for i in message: mesto = alfavit_EU.find(i) # Меняем знак + на знак - new_mesto = mesto + smeshenie if i in alfavit_EU: itog += alfavit_EU[new_mesto] else: itog += i
Итоговый вид программы
Итак, вот мы и написали простейшую программу для шифрования методом Цезаря. Ниже я размещу общий вид программы без моих комментариев, чтобы вы еще раз смогли сравнить свою программу с моей:
alfavit_EU = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' alfavit_RU = 'АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯАБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯ' smeshenie = int(input('Шаг шифровки: ')) message = input("Сообщение для ДЕшифровки: ").upper() itog = '' lang = input('Выберите язык RU/EU: ') if lang == 'RU': for i in message: mesto = alfavit_RU.find(i) new_mesto = mesto + smeshenie if i in alfavit_RU: itog += alfavit_RU[new_mesto] else: itog += i else: for i in message: mesto = alfavit_EU.find(i) new_mesto = mesto + smeshenie if i in alfavit_EU: itog += alfavit_EU[new_mesto] else: itog += i print (itog)
Итог
Вы успешно написали алгоритм шифровки и дешифровки сообщения на Python с помощью метода Цезаря. В следующей статье мы с вами рассмотрим Шифр Виженера, а также разберем его реализацию на Python, а пока я предлагаю вам написать в комментариях варианты модернизации программы(код или просо предложения и пожелания). Я обязательно учту ваше мнение.
- Python
- Программирование
Алгоритмы шифрования
Эта первая практическая работа, которая посвящена базовым конструкциям языка Python. Также в этой работе описан общий подход к выполнению заданий.
Все исходники к работам можно найти в шаблоне репозитория курса. Если вы еще не настроили рабочее окружение, то перейдите в раздел «Настройка рабочего окружения».
Прежде чем приступить к выполнению работы
Перед тем как начать выполнять задания не забудьте перейти в рабочую директорию и активировать ваше виртуальное окружение:
$ gocs102 && cd homework01 $ workon cs102
При выполнении работ мы будем придерживаться простого подхода к ветвлению под названием GitHub flow (есть и другие подходы, например, gitflow). Приступая к новой практической работе создавайте ветку с именем этой работы:
(cs102) $ git checkout -b homework01 master Switched to a new branch 'homework01'
Чтобы отобразить список локальных веток можно воспользоваться командой git branch :
(cs102) $ git branch * homework01 master
Символ * указывает на какой ветке вы находитесь. Для переключения между ветками используйте команду git checkout имя_ветки .
Шифр Цезаря
Шифр Цезаря является одним из самых простых методов шифрования. Для кодирования сообщения все буквы алфавита сдвигают на три символа вперед:
A -> D, B -> E, C -> F, и так далее
Сдвиг трёх последних букв алфавита:
X -> A, Y -> B, Z -> C
Используя шифр Цезаря, слово PYTHON будет закодировано следующим образом:
PYTHON |||||| SBWKRQ
Вам необходимо написать тело для следующих двух функций в файле caesar.py :
def encrypt_caesar(plaintext: str, shift: int = 3) -> str: """ Encrypts plaintext using a Caesar cipher. >>> encrypt_caesar("PYTHON") 'SBWKRQ' >>> encrypt_caesar("python") 'sbwkrq' >>> encrypt_caesar("Python3.6") 'Sbwkrq3.6' >>> encrypt_caesar("") '' """ ciphertext = "" # PUT YOUR CODE HERE return ciphertext def decrypt_caesar(ciphertext: str, shift: int = 3) -> str: """ Decrypts a ciphertext using a Caesar cipher. >>> decrypt_caesar("SBWKRQ") 'PYTHON' >>> decrypt_caesar("sbwkrq") 'python' >>> decrypt_caesar("Sbwkrq3.6") 'Python3.6' >>> decrypt_caesar("") '' """ plaintext = "" # PUT YOUR CODE HERE return plaintext
Обратите внимание, что вторым аргументом функции является сдвиг ( shift ), например, при сдвиге равном нулю сообщение останется без изменений ( A -> A, B -> B, . ).
Воспользуйтесь встроенными функциями ord() и chr() . Функция ord() позволяет получить код указанного символа, а chr() работает наоборот — возвращает символ по его коду.
О кодировках можно почитать тут и тут.
В результате переменные ciphertext и plaintext должны содержать зашифрованное и расшифрованное сообщения соответственно.
Проверить работу функций можно с помощью примеров, приведенных в доктестах (текст внутри функции, который заключен в тройные кавычки и похож на работу с интерпретатором в интерактивном режиме). Запустить доктесты можно с помощью следующей команды (при условии, что файл с программой называется caesar.py ):
(cs102) $ python -m doctest -v caesar.py
Доктесты обычно играют роль примеров и не используются в качестве полноценного фреймворка для автоматического тестирования. Поэтому мы будем использовать стандартную библиотеку unittest для тестирования наших приложений (наиболее популярной альтернативой является pytest). Для запуска тестов можно воспользоваться следующей командой:
(cs102) $ python -m unittest -v tests.test_caesar
или для запуска всех тестов:
(cs102) $ python -m unittest discover
Также обратите свое внимание на официальное руководство по стилю pep8 (некоторые пояснения по оформлению кода можно найти здесь).
Если вы добились успешного прохождения тестов, не забудьте сделать коммит, который зафиксирует ваши изменения, например:
(cs102) $ git add homework01/caesar.py (cs102) $ git commit -m "Реализована функция encrypt_caesar()"
(cs102) $ git add homework01/caesar.py (cs102) $ git commit -m "Реализована функция decrypt_caesar()"
Вы можете воспользоваться приложением Source Tree для наглядного отслеживания вносимых изменений.
Также не забывайте периодически отправлять ваши изменения на сервер:
(cs102) $ git push origin homework01
В качестве дополнительного упражнения вам предлагается реализовать алгоритм взлома шифра Цезаря, основанный на использовании словаря. Функция caesar_breaker должна вернуть наиболее подходящий сдвиг для зашифрованного сообщения:
def caesar_breaker(ciphertext: str, dictionary: tp.Set[str]) -> int: """ >>> d = >>> caesar_breaker("python", d) 0 >>> caesar_breaker("sbwkrq", d) 3 """ best_shift = 0 # PUT YOUR CODE HERE return best_shift
Шифр Виженера
Шифр Виженера очень похож на шифр Цезаря, за тем исключением, что каждый символ сообщения сдвигается на определяемое ключом значение. Ключ — это слово, каждый символ которого указывает на сколько позиций должен быть сдвинут соответствующий символ в шифруемом сообщении. Так, A означает сдвиг на 0 символов, B на 1 и т.д.
Если длина ключа меньше длины слова, подлежащего шифрованию, то ключ повторяется необходимое число раз, например:
Простой текст: ATTACKATDAWN Ключ: LEMONLEMONLE Зашифрованный текст: LXFOPVEFRNHR
Ваша задача написать тело для следующих двух функций в файле vigenere.py так, чтобы переменные ciphertext и plaintext содержали зашифрованное и расшифрованное сообщения соответственно:
def encrypt_vigenere(plaintext: str, keyword: str) -> str: """ Encrypts plaintext using a Vigenere cipher. >>> encrypt_vigenere("PYTHON", "A") 'PYTHON' >>> encrypt_vigenere("python", "a") 'python' >>> encrypt_vigenere("ATTACKATDAWN", "LEMON") 'LXFOPVEFRNHR' """ ciphertext = "" # PUT YOUR CODE HERE return ciphertext def decrypt_vigenere(ciphertext: str, keyword: str) -> str: """ Decrypts a ciphertext using a Vigenere cipher. >>> decrypt_vigenere("PYTHON", "A") 'PYTHON' >>> decrypt_vigenere("python", "a") 'python' >>> decrypt_vigenere("LXFOPVEFRNHR", "LEMON") 'ATTACKATDAWN' """ plaintext = "" # PUT YOUR CODE HERE return plaintext
Обратите внимание, что символы A и a в ключе не оказывают никакого влияния на шифруемое сообщение. Если же в качестве ключа мы будем использовать C или c , то получим шифр Цезаря.
По окончании работы над каждой функцией не забудьте запустить тесты и сделать соответствующие коммиты, как в примере с шифром Цезаря.
RSA шифрование
Одним из современных методов шифрования является алгоритм шифрования RSA, названный так по первым буквам фамилий его авторов (Rivest, Shamir и Adleman).
Мы не будем вдаваться в подробности работы этого алгоритма, но следующего объяснения должно быть достаточно для понимания принципов шифрования с открытым ключом:
Show your kid a padlock. This is a kind of lock that locks when you click it (i.e it doesn’t require a key) but requires the key to open the lock.
So, I can send these padlocks to all my friends who want to communicate with me. I will send them only the lock but will keep the key with me.
My friends can write me messages, put it in a box, lock it with my padlock (by clicking it) and send it to me, even over high risk networks. If the box is intercepted, it’s contents will not be compromised since I still have the key with me.
When the box reaches me, I can open my padlock with my key and read the contents. This way, I can send padlocks (public keys) to people outside which they can use to lock boxes (encrypt messages) without being in danger of the contents being compromised as the padlock key (the private key) is always with me and never exchanged over the network.
Работу алгоритма можно разбить на три шага:
- Генерация ключей
- Шифрование
- Расшифровка
От вас в этом задании требуется выполнить только шаг генерации ключей, остальные два шага уже представлены в шаблоне работы.
На этапе генерации создаётся два ключа: открытый (public key, с помощью которого кто угодно может зашифровать сообщение и отправить его нам) и закрытый (private key, с помощью которого мы будем расшифровать полученные сообщения). Для генерации пары ключей необходимо выбрать два простых числа p и q . Мы предоставим пользователю возможность выбирать эти числа. От вас требуется написать тело функции is_prime(n) , которая проверяет число на простоту:
def is_prime(n: int) -> bool: """ >>> is_prime(2) True >>> is_prime(11) True >>> is_prime(8) False """ # PUT YOUR CODE HERE pass
Если вы закончили работу над функцией is_prime(n) , то запустите тесты и сделайте коммит:
(cs102) $ git commit -am "Реализована функция is_prime(n)"
Для фиксации изменений мы использовали команду git commit -am , которая является аналогом последовательности команд git add . и git commit -m .
После того как были выбраны два простых числа требуется найти их произведение n = p * q :
def generate_keypair(p: int, q: int) -> Tuple[Tuple[int, int], Tuple[int, int]]: if not (is_prime(p) and is_prime(q)): raise ValueError('Both numbers must be prime.') elif p == q: raise ValueError('p and q cannot be equal') # n = pq # PUT YOUR CODE HERE # phi = (p-1)(q-1) # PUT YOUR CODE HERE # Choose an integer e such that e and phi(n) are coprime e = random.randrange(1, phi) # Use Euclid's Algorithm to verify that e and phi(n) are comprime g = gcd(e, phi) while g != 1: e = random.randrange(1, phi) g = gcd(e, phi) # Use Extended Euclid's Algorithm to generate the private key d = multiplicative_inverse(e, phi) # Return public and private keypair # Public key is (e, n) and private key is (d, n) return ((e, n), (d, n))
Затем вычисляется функция Эйлера по формуе:
Далее выбирается число e , отвечающее следующим критериям:
Определить, являются ли числа взаимно простыми можно с помощью алгоритма Евклида. Для этого необходимо вычислить наибольший общий делитель (НОД) и проверить равен ли он единице. На этом этапе вашей задачей является реализация данного алгоритма:
def gcd(a: int, b: int) -> int: """ >>> gcd(12, 15) 3 >>> gcd(3, 7) 1 """ # PUT YOUR CODE HERE pass
Не забудьте зафиксировать реализацию функции gcd(a, b) :
(cs102) $ git commit -am "Реализована функция поиска НОД"
Заключительным этапом на шаге генерации ключей является вычисление d такого что d * e mod phi = 1 . Для его вычисления используется расширенный (обобщенный) алгоритм Евклида (см. стр. 23 этого учебного пособия с подробными объяснениями).
def multiplicative_inverse(e: int, phi: int) -> int: """ >>> multiplicative_inverse(7, 40) 23 """ # PUT YOUR CODE HERE pass
Таким образом, полученные пары (e,n) и (d,n) являются открытым и закрытым ключами соответственно.
Снова запустите тесты и зафиксируйте изменения:
(cs102) git commit -am "Реализованы функции multiplicative_inverse() и generate_keypair()"
После выполнения всех заданий
После выполнения всех заданий отправьте изменения на сервер:
(cs102) $ git push origin homework01
Затем создайте пул-реквест либо с поммощью веб-формы, либо с помощью gh, как это было описано в предыдущей работе. Не забудьте проверить, что нет ошибок при выполнении шагов на вкладке Checks . Сообщите Коннору, что вы готовы к код ревью:
Если ваш пул-реквест был одобрен, то примените изменения к мастер-ветке ( Merge pull request ), удалите ветку homework01 как в репозитории так и локально:
(cs102) $ git checkout master (cs102) $ git branch -d homework01
Наконец получите изменения для локальной мастер-ветки с сервера:
(cs102) $ git pull
Шифрование и криптография в Python
Сегодня познакомимся с библиотеками PyCrypto и cryptography, научимся шифровать и расшифровывать строки при помощи двух этих библиотек.
Хеширование
Если вам нужно защитить хэши или алгоритм дайджеста сообщений, то для этого прекрасно подойдет модуль стандартной библиотеки Python hashlib. Он включает в себя безопасные алгоритмы хеширования FIPS, такие как SHA1, SHA224, SHA256, SHA384, а также SHA512 и MD5. Python также поддерживает функции хеширования adler32 и crc32, но они содержатся в модуле zlib. Одно из самых популярны применений хеширования это хранение хеша пароля, вместо самого пароля. Конечно, хеш должен быть хорошим, в противном случае он может быть расшифрован.
Другой популярный случай, в котором применяется хеширование – это хеширование файла, с последующей отправкой файла и его хеша по отдельности. Получатель файла может запустить хеш в файле, чтобы убедиться в том, что файл соответствует отправленному хешу. Если это так, значит никто не менял файл, когда он был отправлен. Давайте попробуем создать хеш md5. Но оказывается, чтобы использовать хеш md5, нужно передать его строке байта, вместо обычной. Так что мы попробовали сделать это, после чего вызвали метод дайджеста, чтобы получить наш хеш. Если вы претпочитаете хешированный дайджест, мы можем сделать и это:
import hashlib hashlib.md5.update(b'Python rocks!') result = hashlib.md5.digest() print(result) # b'\x14\x82\xec\x1b#d\xf6N>\x16*+[\x16\xf4w'
Давайте уделим время на то, чтобы разобраться с увиденным. Сначала мы импортировали модуль hashlib и создали экземпляр объекта md5 HASH. Далее, мы вписали небольшой текст в объект хеша и получили трассировку.
print( md5.hexdigest() ) # '1482ec1b2364f64e7d162a2b5b16f477'
На самом деле существует метод быстрого создания хеша, мы рассмотрим его, когда создадим наш хеш sha512:
import hashlib sha = hashlib.sha1(b'Hello Python').hexdigest() print(sha) # '422fbfbc67fe17c86642c5eaaa48f8b670cbed1b'
Как вы видите, мы создали наш экземпляр хеша и вызвали его метод дайджеста одновременно. Далее, мы выводим наш хеш, чтобы на него посмотреть. Лично я использую хеш sha1, так как его хеш достаточно короткий и отлично ложится в страницу. Но в то же время он и не очень безопасный, так что вы вольны выбирать то, что вам удобно.
Вывод ключа
У Python весьма ограниченная поддержка вывода ключа, встроенная в стандартную библиотеку. Фактически, единственный метод, предлагаемый hashlib это pbkdf2_hmac, который является основанной на пароле функцией вывода ключа PKCS#5. Он использует HMAC в качестве своей псевдослучайной функцией. Вы можете использовать что-нибудь на подобии для хеширования вашего пароля, так как он поддерживает соль и итерации. Например, если вы собираетесь использовать SHA-256, вам может понадобиться соль минимум в 16 битов и 100.000 итераций. Являясь быстрой отсылкой, соль — это просто случайные данные, которые вы используете в качестве дополнения в вашем хеше, с целью усложнения расшифровки вашего пароля. В целом, она защищает ваш пароль от словарных атак и рассчитанных заранее радужных таблиц. Давайте посмотрим на пример:
import binascii dk = hashlib.pbkdf2_hmac(hash_name='sha256', password=b'bad_password34', salt=b'bad_salt', iterations=100000) result = binascii.hexlify(dk) print(result) # b'6e97bad21f6200f9087036a71e7ca9fa01a59e1d697f7e0284cd7f9b897d7c02'
Здесь мы создаем хеш SHA256 в пароле при помощи такой-себе соли со 100,000 итераций. Конечно, SHA в буквальном смысле не рекомендуется для создания ключей паролей. Вместо этого, вам лучше использовать что-то вроде scrypt. Еще одним полезным инструментом может быть сторонний пакет bcrypt. Он разработан специально для хеширования паролей.
PyCrypto
Пакет PyCrypto, наверное, самый известный сторонний пакет криптографии для Python. К сожалению, его доработка остановилась в 2012 году. Однако продолжается выпускаться под разные версии Python, вы можете получить PyCrypto для версии 3.5 включительно, если вы не брезгуете использовать двоичный код стороннего производства. К примеру, я нашел несколько бинарных колес Python 3.5 для PyCrypto на Github (https://github.com/sfbahr/PyCrypto-Wheels). К счастью, есть развилка проекта под названием PyCrytodome, которая является неплохой заменой PyCrypto. Для его установки на Linux вы можете использовать следующую команду:
pip install pycryptodome
Для Windows немного отличается.
pip install pycryptodomex
Если вы столкнетесь со сложностями, это, возможно, связанно с тем, что у вас нет необходимых установленных зависимостей, или необходим компилятор под Windows. Вы можете перейти на официальный сайт PyCryptodome для дополнительной информации о установке, или чтобы связаться с поддержкой. Также стоит отметить, что PyCryptodome имеет ряд преимуществ в сравнении с последней версией PyCrypto. Рекомендую потратить немного времени и посетить их сайт, для ознакомления с возможностями PyCryptodome.
Шифрование Строки
Теперь (после того, как вы ознакомились с информацией на сайте, я надеюсь), мы можем перейти к дальнейшим примерам. Для нашего следующего кода мы используем DES для шифровки строки:
from Crypto.Cipher import DES key = b'abcdefgh' def pad(text): while len(text) % 8 != 0: text += b' ' return text des = DES.new(key, DES.MODE_ECB) text = b'Python rocks!' padded_text = pad(text) encrypted_text = des.encrypt(padded_text) print(encrypted_text) # b'>\xfc\x1f\x16x\x87\xb2\x93\x0e\xfcH\x02\xd59VQ'
Этот код слегка запутанный, так что давайте уделим немного времени на его анализ. Во первых, обратите внимание на то, что размер ключа под шифровку DES — 8 байт, по этому мы установили нашу переменную ключа в строку размер букв строки. Шифруемая нами строка должна быть кратна 8 в ширину, так что мы создаем функцию под названием pad, которая может заполнить любую строку пробелами, пока она не станет кратна 8. Далее мы создаем экземпляр DES и текст, который нам нужно зашифровать. Мы также создаем заполненную версию текста. Прикола ради, мы попытаемся зашифровать начальный, незаполненный вариант строки, что приведет нас к ошибке ValueError. Таким образом Python ясно дает нам понять, что нам нужно использовать заполненную строку, так что мы передаем вторую версию. Как вы видите, мы получаем зашифрованную строку! Конечно, пример нельзя назвать полным, если мы не выясним, как расшифровать нашу строку:
data = des.decrypt(encrypted_text) print(data) # Python rocks!
К счастью, это очень легко сделать, так как все что нам нужно, это вызвать метод decrypt в нашем объекте des для получения расшифрованной байтовой строки. Наша следующая задача — научиться шифровать файлы и расшифровывать файлы с PyCrypto при помощи RSA. Но для начала, нам нужно создать ключи RSA.
Создание ключей RSA
Если вам нужно зашифровать ваши данные при помощи RSA, тогда вам также нужно получить доступ к паре ключа RSA public / private, или сгенерировать собственную. В данном примере мы генерируем собственную пару ключей. Так как это весьма легко, мы сделаем это в интерпретаторе Python:
from Crypto.PublicKey import RSA code = 'nooneknows' key = RSA.generate(2048) encrypted_key = key.exportKey( passphrase=code, pkcs=8, protection="scryptAndAES128-CBC" ) with open('my_private_rsa_key.bin', 'wb') as f: f.write(encrypted_key) with open('my_rsa_public.pem', 'wb') as f: f.write(key.publickey().exportKey())
Сначала мы импортируем RSA из Crypto.PublicKey. Затем, мы создаем примитивный код доступа. Далее, мы генерируем ключ RSA на 2048 битов. Теперь мы подходим к интересной части. Для генерации приватного ключа, нам нужно вызвать метод exportKey нашего ключа RSA, и передать ему наш код доступа, который будет использован стандартом PKCS, чья схема шифровки будет использована для защиты нашего приватного ключа. После этого мы записываем файл на диск. Далее, мы создаем наш приватный ключ через метод publickey нашего ключа RSA. Мы использовали короткий путь в этой части кода, связав вызов метода exportKey с методом publickey для записи файла на диск.
Шифровка файла
Теперь у нас в распоряжении есть и приватный и публичный ключи, так что мы можем зашифровать кое-какие данные и вписать их в файл. Вот достаточно простой пример:
from Crypto.PublicKey import RSA from Crypto.Random import get_random_bytes from Crypto.Cipher import AES, PKCS1_OAEP with open('encrypted_data.bin', 'wb') as out_file: recipient_key = RSA.import_key( open('my_rsa_public.pem').read() ) session_key = get_random_bytes(16) cipher_rsa = PKCS1_OAEP.new(recipient_key) out_file.write(cipher_rsa.encrypt(session_key)) cipher_aes = AES.new(session_key, AES.MODE_EAX) data = b'blah blah blah Python blah blah' ciphertext, tag = cipher_aes.encrypt_and_digest(data) out_file.write(cipher_aes.nonce) out_file.write(tag) out_file.write(ciphertext)
Первые три строки покрывают наши импорты из PyCryptodome. Далее мы открываем файл для записи. Далее, мы импортируем наш публичный ключ в переменной и создаем 16-битный ключ сессии. Для этого примера мы будем использовать гибридный метод шифрования, так что мы используем PKCS#1 OAEP (Optimal asymmetric encryption padding). Это позволяет нам записывать данные произвольной длинны в файл. Далее, мы создаем наш шифр AES, создаем кое-какие данные и шифруем их. Это дает нам зашифрованный текст и MAC. Наконец, мы выписываем nonce, MAC (или тег), а также зашифрованный текст. К слову, nonce – это произвольное число, которое используется только в криптографических связях. Обычно это случайные или псевдослучайные числа. Для AES, оно должно быть минимум 16 байтов в ширину. Вы вольны попытаться открыть зашифрованный файл в своем текстовом редакторе. Вы увидите только какое-то безобразие. Теперь попробуем расшифровать наши данные:
from Crypto.PublicKey import RSA from Crypto.Cipher import AES, PKCS1_OAEP code = 'nooneknows' with open('encrypted_data.bin', 'rb') as fobj: private_key = RSA.import_key( open('my_rsa_key.pem').read(), passphrase=code ) enc_session_key, nonce, tag, ciphertext = [ fobj.read(x) for x in (private_key.size_in_bytes(), 16, 16, -1) ] cipher_rsa = PKCS1_OAEP.new(private_key) session_key = cipher_rsa.decrypt(enc_session_key) cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce) data = cipher_aes.decrypt_and_verify(ciphertext, tag) print(data)
Если вы разобрались с предыдущим примером, то этот код должен быть весьма простым для разбора. В данном случае, мы открываем наш зашифрованный файл для чтения в бинарном режиме. Далее, мы импортируем наш приватный ключ. Обратите внимание на то, что когда вы импортируете приватный ключ, вы должны передать ему код доступа. В противном случае возникнет ошибка. Далее мы считываем наш файл. Вы заметите, что сначала мы считываем приватный ключ, затем 16 байтов для nonce, за которыми следуют 16 байтов, которые являются тегом, и наконец, остальную часть файла, который и является нашими данными. Далее нам нужно расшифровать наш ключ сессии, пересоздать наш ключ AES и расшифровать данные. Вы можете использовать PyCryptodome в намного более широком ряде случаев. Однако, нам нужно идти дальше и посмотреть, что еще мы можем сделать для наших криптографических нужд в Python.
Пакет cryptography
Пакет cryptography нацелен на то, чтобы быть «криптографом для людей», равно как и библиотека requests является «HTTP для людей». Суть в том, что вам нужно разработать простые криптографические рецепты которые и безопасны, и простые в использовании. Если нужно, вы можете перейти к низкоуровневым криптографическим примитивам, для которых требуется лишь знать, что вы делаете, в противном случае вы создадите что-то явно бесполезное в контексте защиты. Если вы работаете в Python 3.5 Windows, вы можете установить этот пакет при помощи pip следующим образом:
pip install cryptography
Вы увидите, что cryptography установится совместно с несколькими зависимостями. Предположим, что с установкой все прошло чисто, и мы можем зашифровать какой-нибудь текст. Давайте используем для этого модуль Fernet.
Модуль Fernet реализует простую в использовании схему аутентификации, которая использует симметричный алгоритм шифрования, который гарантирует, что каждое зашифрованное в нем сообщение не может быть использовано или прочитано без определенного вами ключа. Модуль Fernet также поддерживает ключ ротации через MultiFernet. Давайте взглянем на простой пример:
from cryptography.fernet import Fernet cipher_key = Fernet.generate_key() print(cipher_key) # APM1JDVgT8WDGOWBgQv6EIhvxl4vDYvUnVdg-Vjdt0o=
from cryptography.fernet import Fernet cipher = Fernet(cipher_key) text = b'My super secret message' encrypted_text = cipher.encrypt(text) print(encrypted_text) # (b'gAAAAABXOnV86aeUGADA6mTe9xEL92y_m0_TlC9vcqaF6NzHqRKkjEqh4d21PInEP3C9HuiUkS9f' # b'6bdHsSlRiCNWbSkPuRd_62zfEv3eaZjJvLAm3omnya8=')
decrypted_text = cipher.decrypt(encrypted_text) print(decrypted_text) # 'My super secret message'
Для начала, нам нужно импортировать Fernet. Затем мы генерируем ключ. Мы выводим ключ, чтобы увидеть, как он выглядит. Как вы видите, это случайна строка байтов. Если хотите, вы можете попробовать запустить метод generate_key несколько раз. Результат каждый раз новый. Далее мы создаем экземпляр нашего шифра Fernet при помощи нашего ключа. Теперь у нас есть шифр, который мы можем использовать для шифрования и расшифровки нашего сообщения. Следующий шаг, это создание сообщения, достойного шифровки, с последующей его шифровкой при помощи метода encrypt. Я пошел вперед и вывел наш зашифрованный текст так, чтобы вы увидели что вы больше не можете его читать. Для расшифровки нашего супер-засекреченного сообщения, мы просто вызовем метод decrypt в нашем шифре и передадим зашифрованный текст. В результате мы получим текстовую байтовую строку нашего сообщения.
Подведем Итоги
В данной статье мы изрядно прошлись по поверхности вопроса : «Как, и что делать с пакетами PyCryptodome и cryptography?». Мы рассмотрели изрядное количество вариантов применения данных пакетов в шифровании и расшифровке строк и файлов. Убедитесь в том, что уделите время документации, перед тем как начать экспериментировать с изложенной в данной статье информацией.
Криптография для самых маленьких: шифрование с примерами на Python
Каждый человек, который пользуется компьютером или смартфоном, ежедневно сталкивается с криптографией: начиная от работы в интернете по протоколу HTTPS и заканчивая печально известными вирусами-шифровальщиками. Однако далеко не все понимают, как работает криптография. Давайте попробуем в этом разобраться на конкретных примерах.
Одним из разделов криптографии является наука о шифровании. В процессе шифрования происходит обратимое изменение информации с помощью некоего секрета, что делает информацию недоступной для тех, кто секретом не владеет. Базовые принципы шифрования мы и рассмотрим в этой статье.
Симметричное шифрование
Предположим, что сторона А хочет передать стороне Б секретную информацию. Стоп. А, Б — это всё некрасиво и неудобно. Именно поэтому в криптографии принято называть стороны обмена информацией именами Алиса (Alice) и Боб (Bob).
Итак, как Алиса может передать сообщение, чтобы никто, кроме Боба, не смог прочесть его? Необходимо как-то изменить эти данные по заранее согласованному с Бобом алгоритму. Простейшим способом реализации такой задачи является подстановочный шифр — алгоритм, при котором каждая буква сообщения заменяется на другую букву. Например, вместо первой буквы алфавита («А») Боб c Алисой будут использовать третью («В»), вместо второй («Б») — четвертую («Г») и так далее.
В этом случае алгоритмом шифрования является сдвиг букв алфавита, а ключом — цифра 2 (сдвиг на две позиции). Любой, кто знает алгоритм и ключ, сможет расшифровать сообщение Алисы. Кстати, попробуйте и вы расшифровать это сообщение — стретвоокуф. Вам поможет простой пример на Python 3:
# -*- coding: utf-8 -*- ALPHA = u'абвгдеёжзийклмнопрстуфхцчшщьъэюя' def encode(text, step): return text.translate( str.maketrans(ALPHA, ALPHA[step:] + ALPHA[:step])) def decode(text, step): return text.translate( str.maketrans(ALPHA[step:] + ALPHA[:step], ALPHA))
Такие алгоритмы шифрования, при которых Алиса и Боб должны заранее придумать и согласовать одинаковый секрет, называются симметричными, а рассмотренный пример является самым простым алгоритмом этой группы и называется шифром Цезаря. Он считается небезопасным, и его не рекомендуется использовать. Наиболее популярными и достаточно криптостойкими симметричными алгоритмами являются 3DES и AES.
Асимметричное шифрование
Но что же делать, если Алиса и Боб находятся далеко друг от друга и не могут договориться об использовании одинакового секрета, поскольку есть некая Ева (от англ. eavesdropper — подслушивающий), которая так и хочет узнать тайны Алисы и Боба? В этом случае Боб может отправить Алисе замок, ключ от которого есть только у него. Алиса положит письмо в коробку и запрёт её на этот замок. Теперь ни Алиса, ни Ева не смогут открыть коробку и прочесть письмо.
Аналогичный подход используется в асиметричном шифровании, которое также называют криптосистемой с открытым ключом. В примере с Алисой и Бобом секретным ключом Боба будет ключ от замка, а публичным ключом условно можно назвать сам замок. Отправка Алисе замка — это алгоритм согласования ключей.
Наиболее популярным алгоритмом шифрования с открытым ключом является RSA. Вот как выглядит его реализация на языке Python с использованием библиотеки RSA:
# -*- coding: utf-8 -*- import rsa #Боб формирует публичный и секретный ключ (bob_pub, bob_priv) = rsa.newkeys(512) #Алиса формирует сообщение Бобу и кодирует его в UTF8, #поскольку RSA работает только с байтами message = 'hello Bob!'.encode('utf8') #Алиса шифрует сообщение публичным ключом Боба crypto = rsa.encrypt(message, bob_pub) #Боб расшифровывает сообщение своим секретным ключом message = rsa.decrypt(crypto, bob_priv) print(message.decode('utf8'))
Более подробно с алгоритмом RSA можно ознакомиться в другой нашей статье.
Стоит также отметить, что асиметричные алгоритмы работают медленнее, чем симметричные, и очень часто встречается схема, когда по асимметричному алгоритму шифруется симметричный секрет и дальнейший обмен сообщениями происходит по симметричному алгоритму.
Заключение
В этой статье мы рассмотрели лишь один из разделов криптографии — шифрование. Если тема вас заинтересовала, следите за нашими дальнейшими публикациями, а также посмотрите подборку с материалами по криптографии.
Следите за новыми постами по любимым темам
Подпишитесь на интересующие вас теги, чтобы следить за новыми постами и быть в курсе событий.