Как проверить токен Fernet без ключа? - PullRequest
1 голос
/ 12 июля 2019

Я использую библиотеку cryptography.fernet для шифрования небольшого текста, и когда я получаю этот текст на своем сервере, я хотел бы проверить, что это действительно правильный зашифрованный текст, прежде чем делать RPC для его расшифровки, так что мой сервер может просто вернуть, что данные неверны. Есть ли какой-нибудь дешевый способ сделать это, просто чтобы избежать случаев спама?

Например, если это мой зашифрованный текст:

>>> k = Fernet.generate_key()
>>> f = Fernet(k)
>>> c = f.encrypt("misingnoglic@gmail.com")
>>> c
'gAAAAABdCC4Z8fqgRu7fCv2e7cvPm46rMwTVmSJK6guR5vrnvjaCICXKI1cI-_qr3Cs_z602a4tS-sMYm_smSOzgwOJ8biVQDqlyyyt-iLcxQNCOmjBywwM='

Есть ли дешевый способ проверки правильности строки, начинающейся с gAAAA? Или что строка 'abcde' будет недействительной?

Спасибо!

1 Ответ

2 голосов
/ 12 июля 2019

Хорошо, взгляните на спецификацию Fernet . Маркер Fernet (так называется строка, о которой вы говорите) имеет следующую структуру (где означает конкатенацию):

token = urlsafe_b64encode(Version ‖ Timestamp ‖ IV ‖ Ciphertext ‖ HMAC)

HMAC, который является последней частью токена, вычисляется из начальной части (Version ‖ Timestamp ‖ IV ‖ Ciphertext) с использованием ключа подписи (который является первой половиной ключа), как документировано спецификация.

Проблема в том, что у вас нет ключа , поэтому единственное, что вы можете сделать, это проверить начальные поля токена после его декодирования из Base64:

  • Version должно быть 0x80, что означает версию 1 (опять же, как указано в спецификации).
  • Timestamp - это временная метка, связанная с токеном: вы можете проверить, находится ли этот номер в определенном диапазоне, чтобы сбросить любой просроченный или деформированный токен.

Итак, вот что вы можете сделать, чтобы «уменьшить спам»:

from base64 import urlsafe_b64decode
from struct import unpack
from datetime import datetime, timedelta

bin_token = urlsafe_b64decode(c) # <-- c is the Fernet token you received
version, timestamp = unpack('>BQ', bin_token[:9])

tok_age = datetime.now() - datetime.fromtimestamp(timestamp)
max_age = timedelta(7) # 7 days

if version != 0x80:
    print 'Invalid token version!'

if tok_age > max_age:
    print 'Token expired!'
elif tok_age < timedelta(0):
    print 'Token timestamp in the future! Invalid token!'

БОНУС: как я уже сказал, вы не можете проверить действительность токена, если у вас нет хотя бы ключа подписи (который является первой половиной ключа). Поэтому, конечно, следующее не относится к вашему сценарию, но давайте предположим, что у вас есть ключ подписи. В таком случае, в дополнение к вышеупомянутой проверке, которую вы все равно должны делать , вы можете сделать следующее, чтобы проверить действительность токена:

from base64 import urlsafe_b64decode
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.hmac import HMAC
from cryptography.hazmat.backends import default_backend

bin_data = urlsafe_b64decode(c)

# Assuming you have this:
signing_key = "???" # should be urlsafe_b64decode(k)[:16]

client_data = bin_data[:-32]
client_hmac = bin_data[-32:]

print 'Client HMAC:', client_hmac.encode('hex')

real_hmac = HMAC(signing_key, hashes.SHA256(), default_backend())
real_hmac.update(client_data)
real_hmac = real_hmac.finalize()

print 'Real HMAC  :', real_hmac.encode('hex')

if client_hmac == real_hmac:
    print 'Token seems valid!'
else:
    print 'Token does NOT seem valid!'
...