Как вы уже могли понять, безопасность в Веб является слишком объемной темой, чтобы ее можно было рассмотреть здесь. Вследствие этого модуль secret.py, представленный в примере 16.13, старается обойти эту проблему, обеспечивая различные варианты ее решения:
• Если у вас есть возможность получить и установить стороннюю систему PyCrypto, описанную выше, модуль будет использовать инструменты шифрования по алгоритму AES из этого пакета для шифрования пароля при передаче вместе с именем пользователя.
• В противном случае следующей будет попытка использовать пакет rotor, если вам удастся отыскать и установить оригинальный модуль rotor для используемой вами версии Python.
Дополнительные детали смотрите в примере 16.13 — там используются определения функций, вложенные в условные инструкции if, которые позволяют сгенерировать функции для выбранной схемы шифрования во время выполнения.
Пример 16.13. PP4E\Internet\Web\PyMailCgi\cgi-bin\secret.py
############################################################################ PyMailCGI шифрует пароль, когда он пересылается клиенту или от него через сеть вместе с именем пользователя в скрытых полях форм или в параметрах запроса URL; использует функции encode/decode в этом модуле для шифрования пароля — выгрузите на сервер собственную версию этого модуля, чтобы использовать другой механизм шифрования; PyMailCGI не сохраняет пароли на сервере и не отображает его при вводе пользователем в форме ввода, но это не дает 100% защиты — файл этого модуля сам может оказаться уязвим; использование протокола HTTPS может оказаться более удачным и более простым решением, но классы реализации веб-сервера в стандартной библиотеке Python не поддерживают его;
############################################################################
import sys, time
dayofweek = time.localtime(time.time())[6] # для реализации собственных схем forceReadablePassword = False
############################################################################ # схемы преобразования строк
############################################################################
if not forceReadablePassword:
########################################################################
# по умолчанию не делать ничего: вызовы urllib.parse.quote
# или cgi.escape в commonhtml.py выполнят необходимое экранирование
# пароля для встраивания его в URL или HTML; модуль cgi
# автоматически выполнит обратное преобразование;
######################################################################## def stringify(old): return old
def unstringify(old): return old
else:
########################################################################
# преобразование кодированной строки в/из строки цифр,
# чтобы избежать проблем с некоторыми специальными/непечатаемыми
# символами, но сохранить возможность чтения результата
# (хотя и зашифрованного); в некоторых броузерах есть проблемы
# с преобразованными амперсандами и т.д.;
# ####################################################################### separator =
def stringify(old):
new = »
for char in old:
ascii = str(ord(char))
new = new + separator + ascii # ‘-ascii-ascii-ascii’
return new
def unstringify(old):
new = »
for ascii in old.split(separator)[1:]:
new = new + chr(int(ascii))
return new
##################################################
# схемы шифрования: пробует PyCrypto, затем rotor,
# затем простейший/нестандартный алгоритм
##################################################
useCrypto = useRotor = True
try:
import Crypto
except:
useCrypto = False
try:
import rotor
except:
useRotor = False
if useCrypto:
###########################################################
# использовать алгоритм AES из стороннего пакета pycrypto
# предполагается, что в конце строки пароля отсутствует
# символ ‘\0’: используется для дополнения справа
# измените закрытый ключ здесь, если используете этот метод
###########################################################
sys.stderr.write(‘using PyCrypto\n’)
from Crypto.Cipher import AES
mykey = ‘pymailcgi3’.ljust(16, ‘-‘) # ключ должен иметь длину 16, 24
# или 32 байта
def do_encode(pswd):
over = len(pswd) % 16
if over: pswd += ‘\0’ * (16-over) # дополнение: длина должна быть
aesobj = AES.new(mykey, AES.MODE_ECB) # кратна 16
return aesobj.encrypt(pswd)
def do_decode(pswd):
aesobj = AES.new(mykey, AES.MODE_ECB)
pswd = aesobj.decrypt(pswd)
return pswd.rstrip(‘\0’)
elif useRotor:
############################################################
# использовать для шифрования стандартный модуль rotor
# он лучше подходит для шифрования, чем программный код выше
# к сожалению, он больше недоступен в Py 2.4+
############################################################
sys.stderr.write(‘using rotor\n’)
import rotor
mykey = ‘pymailcgi3’
def do_encode(pswd):
robj = rotor.newrotor(mykey) # использовать алгоритм enigma
return robj.encrypt(pswd)
def do_decode(pswd):
robj = rotor.newrotor(mykey)
return robj.decrypt(pswd)
else:
##############################################################
# в крайнем случае использовать собственную схему, основанную
# на искажении символов некоторым обратимым способом
# предупреждение: слишком простой алгоритм — замените своим
##############################################################
sys.stderr.write(‘using simple\n’)
adder = 1
def do_encode(pswd):
pswd = ‘vs’ + pswd + ’48’
res = »
for char in pswd:
res += chr(ord(char) + adder) # увеличить каждый код ASCII
return str(res)
def do_decode(pswd):
pswd = pswd[2:-2]
res = »
for char in pswd:
res += chr(ord(char) — adder)
return res
############################################################################
# функции верхнего уровня
############################################################################
def encode(pswd):
return stringify(do_encode(pswd)) # шифрование плюс
# преобразование строки
def decode(pswd):
return do_decode(unstringify(pswd))
В дополнение к шифрованию в этом модуле реализованы также методы преобразования уже зашифрованных строк, которые преобразуют их содержимое в печатаемые символы и обратно. По умолчанию функции преобразования ничего не делают, и система полностью полагается на то, что зашифрованные пароли будут корректно обработаны функциями экранирования URL и HTML. Дополнительная схема преобразования транслирует зашифрованные строки в строки, содержащие цифровые коды ASCII символов, разделенные дефисами. Такой метод позволяет преобразовать непечатаемые символы в зашифрованной строке в печатаемые.
Для иллюстрации проверим инструменты из этого модуля в интерактивном режиме. В этих тестах переменной forceReadablePassword было присвоено значение True. Функции верхнего уровня encode и decode будут воспроизводить печатаемые символы (для иллюстрации этот тест проводился в Python 2.X с установленным пакетом PyCrypto):
>>> from secret import *
using PyCrypto
>>> data = encode(‘spam@123+’)
>>> data
‘-47-248-2-170-107-242-175-18-227-249-53-130-14-140-163-107’
>>> decode(data)
‘spam@123+’
А ниже шифрование выполняется в два этапа — собственно шифрование и преобразование в печатаемые символы:
>>> raw = do_encode(‘spam@123+’)
>>> raw
‘/\xf8\x02\xaak\xf2\xaf\x12\xe3\xf95\x82\x0e\x8c\xa3k’
>>> text = stringify(raw)
>>> text
‘-47-248-2-170-107-242-175-18-227-249-53-130-14-140-163-107’
>>> len(raw), len(text)
(16, 58)
Ниже показано, как выглядит шифрование без дополнительного преобразования в печатаемые символы:
>>> raw = do_encode(‘spam@123+’)
>>> raw
‘/\xf8\x02\xaak\xf2\xaf\x12\xe3\xf95\x82\x0e\x8c\xa3k’
>>> do_decode(raw)
‘spam@123+’
Использованная литература:
Марк Лутц — Программирование на Python, 4-е издание, II том, 2011