У некоторых людей возникает необходимость передать небольшие сообщения. Но как это сделать, если вы пользуетесь различными социальными сетями и мессенджерами, в безопасности передачи данных через которые вы сомневаетесь.
Некоторые люди для этого используют сервисы самоуничтожающихся шифрованных записок. Но тут встает вопрос можно ли доверять этим сервисам и действительно ли они уничтожают записки после прочтения.
Для решения этой проблемы мы напишем свой сервис самоуничтожающихся шифрованных записок на языке Python с использованием модуля cryptography и фреймворка Flask и развернем его на облачном сервисе Heroku.
Все файлы для приложения можно скачать на github Приложение в работе можно посмотреть на Heroku
Установка и настройка
Писать будем в виртуальном окружении Virtualenv.
Устанавливаем модуль virtualenv
pip install virtualenv
Создаем папку нашего проекта и в нем активируем виртуальное окружение
mkdir encnotes cd encnotes virtualenv venv source venv/bin/activate (Для nix систем) venvScriptsactivate (Для Windows систем)
В папке проекта создаем текстовый файл requirements.txt в который записываем необходимые для установки модули:
cryptography Flask Flask-Migrate Flask-SQLAlchemy Flask-WTF Flask-Bootstrap Flask-SSLify
И с помощью pip устанавливаем модули, перечисленные в файле requirements.txt
pip install -r requirements.txt
Приложение
Создаем файл encnotes.py. Весь код приложения будет в этом файле. В самом начале импортируем все необходимые модули. Постепенно расскажу для чего они нужны.
import os import random from cryptography.fernet import Fernet from flask import Flask, render_template, url_for from flask_sqlalchemy import SQLAlchemy from flask_migrate import Migrate from flask_wtf import FlaskForm from flask_bootstrap import Bootstrap from flask_sslify import SSLify from wtforms import TextAreaField, SubmitField from wtforms.validators import DataRequired, Length
Один из удобных способов задания настроек программы, это объявить их в классе
class Config(): SECRET_KEY = os.environ.get('SECRET_KEY') SQLALCHEMY_TRACK_MODIFICATIONS = False SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') SITE_URL = 'https://encnote.herokuapp.com'
SECRET_KEY — ключ для создания безопасных форм. Для того чтобы не хранить этот ключ в скрипте, что является плохой практикой, мы сохраним его в переменной среды и будем брать оттуда. Расширение Flask-WTF использует этот ключ для защиты от атак Cross-Site Request Forgery сокращенно CSRF.
SQLALCHEMY_TRACK_MODIFICATIONS — отключает ненужную нам функцию, сигнализирующую
приложению каждый раз, когда в базе должно быть внесено изменение.
SITE_URL — его укажете позднее, когда создадите приложение на Heroku.
Создаем объект приложения как экземпляр класса Flask и загружаем в него настройки из созданного нами ранее класса Config и добавляем объекты импортированных модулей
app = Flask(__name__) app.config.from_object(Config) db = SQLAlchemy(app) migrate = Migrate(app, db) bootstrap = Bootstrap(app) sslify = SSLify(app)
Модель базы данных
Создаем модель для базы данных, в которой будем хранить зашифрованные записки. У нашей модели будет 3 поля:
- id — идентификатор записи, будет создаваться автоматически
- number — номер записки, по которому мы будем находить нашу записку в базе данных.
Этот номер будет генерироваться рандомно в диапазоне от 1000000 до 9999999 - ciptext — сам зашифрованный текст
По этой модели будет создана миграция для базы данных.
class Note(db.Model): __tablename__ = 'notes' id = db.Column(db.Integer, primary_key=True) number = db.Column(db.Integer, unique=True, nullable=False) ciptext = db.Column(db.Text, nullable=False) def __repr__(self): return f'<Note number: {self.number}'
Веб-форма
Для создания веб-формы ввода сообщение, которое будет зашифровано используем расширение Flask-WTF
class TextForm(FlaskForm): text = TextAreaField('Введите текст (максимум 1000 символов)', validators=[DataRequired(), Length(1, 1000)]) submit = SubmitField('Создать')
- text — поле для ввода сообщения. Я применил 2 валидатора. Один для проверки введено ли сообщение, чтобы форма не разрешала отправить пустое сообщение. Второй валидатор для ограничения максимальной длинны строки в 1000 символов. Можете задать свое ограничение на длину строки.
Регистрация функции в контексте оболочки
Для того чтобы было удобнее проверить нашу модель для базы данных я зарегистрировал функцию как функцию в контексте оболочки. Это дает возможность не импортировать каждый раз модель базы данных и саму базу данных в оболочке.
@app.shell_context_processor def make_shell_context(): return {'db': db, 'Note': Note}
Обработчики маршрутов
Маршрут для главной стриницы, на которой будет форма ввода сообщения
@app.route('/', methods=['GET', 'POST']) def index(): form = TextForm() if form.validate_on_submit(): key = Fernet.generate_key() str_key = key.decode('ascii') f = Fernet(key) bin_string = form.text.data.encode('utf-8') cipher_text = f.encrypt(bin_string) str_cipher_text = cipher_text.decode('ascii') rnumber = random.randint(1000000, 9999999) while True: n = Note.query.filter_by(number=rnumber).first() if n: rnumber = random.randint(1000000, 9999999) continue break cipher_note = Note(number=rnumber, ciptext=str_cipher_text) link = f'{app.config["SITE_URL"]}/{rnumber}/{str_key}' db.session.add(cipher_note) db.session.commit() return render_template('complete.html', link=link) return render_template('index.html', form=form)
Создаем форму из класса, который мы написали ранее. Далее обрабатываем запрос POST, если пользователь ввел текст и нажал на кнопку "Зашифровать". Генерируем ключ и сохраняем его в базе данных. Генерируется ключ URL-safe base64, что значит используются только символы кодировки "Ascii" и без слеша и мы можем его использовать в качестве URL. Сообщение шифруется алгоритмом AES-128.
После создания ключа и шифрования нашего сообщения, которое также будет в формате URL-safe base64, нам нужно сгенерировать номер, который будет идентификатором для нашего зашифрованного сообщения. Чтобы проверить нет ли в базе данных сообщения с таким же номером, в цикле while мы выполняем поиск и генерируем новый номер, до тех пор, пока не найдем уникальный номер. После сохранения сообщения в базу данных, рендерим html страницу из шаблона complete.html в которой будет указана ссылка-ключ для расшифровки сообщения. Чтобы не уделять много времени html верстке я воспользовался расширением Flask-Bootstrap, которое использует шаблоны bootstrap.
Маршрут для страницы с вопросом
Этот маршрут открывается, при переходе по сгенерированной ссылке с ключом. Его можно было бы избежать, но многие сервисы обмена сообщений или социальные сети при передаче ссылки, делают ее пред просмотр. Нам такой вариант не подходит т.к. при пред просмотре будет расшифровываться текст и сообщение удаляться из базы.
@app.route('/<rnumber>/<str_key>') def question(rnumber, str_key): link = f'{app.config["SITE_URL"]}/decrypt/{rnumber}/{str_key}' return render_template('question.html', link=link)
После нажатия на кнопку "Расшифровать" мы переходим на третий маршрут, с текстом нашего сообщения
@app.route('/decrypt/<int:rnumber>/<str_key>') def decrypt(rnumber, str_key): cipher_note = Note.query.filter_by(number=rnumber).first_or_404() cipher_text = cipher_note.ciptext.encode('ascii') key = str_key.encode('ascii') try: f = Fernet(key) text = f.decrypt(cipher_text) except (ValueError, InvalidToken): return render_template('error.html') text = text.decode('utf-8') db.session.delete(cipher_note) db.session.commit() return render_template('decrypt.html', text=text)
Находим сообщение в базе и с помощью ключа из ссылки расшифровываем его. На случай введения неправильного ключа сделал обработчик ошибок в блоке try except
.
Развертывание на Heroku
Для развертывания нашего приложения воспользуемся облачным хостинг-провайдером Heroku. Для демонстрации нашего приложения подойдет и бесплатный тариф. Один из минус которого, если в течении 30 минут не будет обращений на сайт, то он засыпает. После этого при заходе на него, нужно немного подождать.
Создайте аккаунт на Heroku и установите командную строку Heroku CLI. Развертывание приложения производиться с помощью средств управления версиями git После установки Heroku CLI входим в систему
$ heroku login
Добавляем наш проект в git
$ git init
Создаем приложение в Heroku. Вам нужно придумать уникальное имя для вашего приложения, вместо моего encnote
heroku apps:create encnote Creating ? encnote... done https://encnote.herokuapp.com/ | https://git.heroku.com/encnote.git
Для нашего приложения нам понадобиться база данных, сделаем ее в Heroku
heroku addons:add heroku-postgresql:hobby-dev Creating heroku-postgresql:hobby-dev on ? encnote... free Database has been created and is available ! This database is empty. If upgrading, you can transfer ! data from another database with pg:copy Created postgresql-graceful-12123 as DATABASE_URL Use heroku addons:docs heroku-postgresql to view documentation
Heroku поместил адрес базы данных в переменную среды DATABASE_URL
, и нам не придется делать это самостоятельно. Также нам нужно создать миграции для нашей базы данных, для этого сначала укажем переменную среды в нашей системе, указывающую на наше приложение Flask
Для nix систем export FLASK_APP=encnotes.py
Для Windows set FLASK_APP=encnotes.py
Также укажем расположение для временной базы данных, подставье свой адрес
$ export DATABASE_URL=sqlite:////home/user/Python/encnote/app.db (Для nix систем) $ set DATABASE_URL=sqlite:///D:Pythonencnotesapp.db (Для Windows систем)
После этого выполняем инициализацию базы данных и создание миграций
flask db init flask db migrate
В папке нашего приложения создастся папка migrate, с необходимыми миграциями. Файл app.db можно удалить.
Создадим файл .gitignore и пропишем в нем файлы, которые git должен игнорировать при загрузке на сервис Heroku
venv __pycache__
Поскольку веб-сервер flask не достаточно надежен для использования в развертывании, мы будем использовать gunicorn. Чтобы наше приложение могло подключаться к базе данных Postgres добавим psycopg2. Для автоматического редиректа с http на https, чтобы данные передавались безопасным способом мы добавили и активировали расширение Flask-SSLify.
Добавьте следующие модули в файл requirements.txt
gunicorn psycopg2
Для того чтобы Heroku знал как управлять приложением, нужно создать файл Procfile в каталоге приложения. В этот файл запишем:
web: flask db upgrade; gunicorn encnotes:app
С помощью этой строки мы даем команды Heroku. Сначала запустить апгрейд базы данных, затем запустить веб сервер.
Нам нужно задать переменные среды, секретный ключ для форм и имя файла с нашим приложением. В SECRET_KEY укажите свое значение.
heroku config:set SECRET_KEY=super-secret-work232 heroku config:set FLASK_APP=encnotes.py
Переменную DATABASE_URL с адресом базы данных создал Heroku, когда мы ввели команду
heroku addons:add heroku-postgresql:hobby-dev
Теперь мы все подготовили для развертывания. Добавляем наши файлы в git, делаем коммит и отправляем на heroku
git add . git commit -m "Heroku deploy" git push heroku master
Все, приложение готово! В данной статье я показал как легко можно делать небольшие
веб приложения использую фреймворк Flask и простой в использовании python модуль
для шифрования cryptography.