Добрый день. Я сейчас разрабатываю приложение, которое состоит из angularjs приложения и сервера tornado (python). Все это между собой общается через websocket (вся статика полностью отделена. Все взаимодействие только через сокет).
И вот сейчас пришла пора пилить авторизацию. И возникла довольно серьезная проблема с безопасностью. Дело в том, что этот проект из разряда запилил и забыл. Т.е. запихнул все это дело на Raspberry Pi, засунул подальше и забыл на 3 (а то и больше) года. Соответственно, ssl покупать никто не будет.
Использовать самоподписанный сертификат - не вариант, т.к. в этом случае браузеры будут выдавать предупреждения, что абсолютно неприемлемо. Естественно, остается пилить всю безопасность самому.
Самое главное место в моем протоколе уделено, конечно, авторизации. Ее было решено делать основываясь на
этой статье. В моем случае это видится мне так:
1. Страница с приложением загружается с сервера статики и инициализируется
2. Создается подключение к серверу
3. Как только соединение подтверждено сервером пользователю выводится форма авторизации, в которой он, собственно говоря, логинится
4. Затем введенная информация отправляется в сервис angular и там начинается самое интересное
5. Серверу отправляется сообщение с логином пользователя (протокол похож на JSONRPC с некоторыми отличиями)
6. Сервер ищет этот логин в базе и, в случае если находит, генерирует случайное число. Затем это число шифруется AES на хеше пароля пользователя из базы данных.
7. Клиент (сервис angular), получив этот ответ расшифровывает зашифрованное число (если, конечно, введен правильный пароль) и заносит его в свое поле.
8. Теперь клиент шифрует пароль при помощи этого числа, после чего отправляет результат серверу.
9. Сервер расшифровывает сообщение, хеширует полученный пароль и сравнивает его с хешем из базы. Если все совпало, то он отправляет клиенту подтверждение.
10. После этого сервер помечает этого клиента (а точнее подключение) авторизованым и использует это число как ключ для шифрования/расшифровки всех последующих сообщений в этом соединении.
11. Клиент, получив подтверждение, отображает пользователю все красоты интерфейса. С этого момента он также шифрует все сообщения при помощи того самого числа.
Вот, по сути, и все. Мне это кажется идеальным вариантом. Но у меня остается целая кипа вопросов. Изложу их по порядку:
Насколько я понимаю, в AES все должно быть кратно 16. Т.е. и длина ключа, и длина шифруемого сообщения. Иначе просто нельзя. Ну я тут нашаманил кое-какое решение, но мне оно кажется уж чересчур костыльным:
from Crypto.Cipher import AES
import base64
BS = 16
pad = lambda s: s + (BS - len(s) % BS) * chr(BS - len(s) % BS)
unpad = lambda s : s[0:-ord(s[-1])]
class AESCipher:
def __init__( self, key ):
self.key = key
def encrypt( self, raw ):
raw = pad(raw)
cipher = AES.new(self.key, AES.MODE_ECB)
return base64.b64encode(cipher.encrypt( raw ))
def decrypt( self, enc ):
enc = base64.b64decode(enc)
cipher = AES.new(self.key, AES.MODE_ECB)
return unpad(cipher.decrypt(enc).decode())
Даже если предположить что это будет работать (ну, по сути это уже может успешно шифровать/расшифровывать сообщения собственного производства), то как перетащить все это на клиентскую сторону? Предположим, что я смог протащить все это безобразие через свой протокол на клиентскую сторону. Но как там это расшифровывать непонятно.
Изначально я планировал использовать на клиентской стороне библиотеку Chris'a Veness'a, упомянутую в
статье на хабре. Но она внесла еще большее недоумение в мое нестабильное состояние. В примере использования написано:
var password = 'L0ck it up saf3';
var plaintext = 'pssst ... đon’t tell anyøne!';
var ciphertext = Aes.Ctr.encrypt(plaintext, password, 256);
var origtext = Aes.Ctr.decrypt(ciphertext, password, 256);
То есть при шифровании указывается и ключ и некое количество бит. Да, да. Именно "количество бит", потому что я не понимаю зачем это нужно. Я всегда свято верил что 128, 256 в контексте шифрования - ничто иное как длина ключа. Но ключ то указывается вручную. Получается, что он просто конкатенируется некоторое количество раз до получения нужной длины? Или я реально ничего не понимаю?
Кстати говоря, в википедии вообще
написано:
Размер блока 128 бит, ключ 128/192/256 бит
. Но почему тогда PyCrypto принял 16-битный ключ?
В общем, надеюсь, концепция всего этого понятна. Может кто сможет объяснить мне как реализовать план написанный выше?
P.s. Немного технических подробностей:
* Python 3.2
* Raspbian (Debian)
* Angular 1.2.13
P.p.s Идеальный вариант - найти AES библиотеку под третий питон похожую на библиотеку похожую на
библиотеку Chris'a Veness'a.
P.p.p.....S: Библиотеки
отсюда на описанной выше конфигурации не запустились. Если есть идеи как их адаптировать - буду очень благодарен.
UPD: Продолжение тут:
PyCrypto + CryptoJS