A resposta abaixo refere-se principalmente a Cookies assinados , uma implementação do conceito de sessões (conforme usado em aplicativos da web). O Flask oferece cookies normais (não assinados) (via request.cookies
e response.set_cookie()
) e cookies assinados (via flask.session
). A resposta tem duas partes, a primeira descreve como um cookie assinado é gerado e a segunda é apresentada na forma de um controle de qualidade que aborda diferentes aspectos do esquema. A sintaxe usada para os exemplos é Python3, mas os conceitos se aplicam também às versões anteriores.
O que é SECRET_KEY
(ou como criar um cookie assinado)?
A assinatura de cookies é uma medida preventiva contra a violação de cookies. Durante o processo de assinatura de um cookie, ele SECRET_KEY
é usado de maneira semelhante à maneira como um "salt" seria usado para confundir uma senha antes de fazer o hash. Aqui está uma descrição simplificada (descontroladamente) do conceito. O código nos exemplos deve ser ilustrativo. Muitas das etapas foram omitidas e nem todas as funções existem. O objetivo aqui é fornecer uma compreensão da idéia geral, as implementações reais serão um pouco mais envolvidas. Além disso, lembre-se de que o Flask faz a maior parte disso por você em segundo plano. Portanto, além de definir valores para o seu cookie (via API da sessão) e fornecer um SECRET_KEY
, não é apenas desaconselhável reimplementá-lo, mas não há necessidade de fazê-lo:
Assinatura de um pobre homem
Antes de enviar uma resposta ao navegador:
(1) Primeiro a SECRET_KEY
é estabelecido. Ele deve ser conhecido apenas pelo aplicativo e deve ser mantido relativamente constante durante o ciclo de vida do aplicativo, inclusive através da reinicialização do aplicativo.
# choose a salt, a secret string of bytes
>>> SECRET_KEY = 'my super secret key'.encode('utf8')
(2) criar um cookie
>>> cookie = make_cookie(
... name='_profile',
... content='uid=382|membership=regular',
... ...
... expires='July 1 2030...'
... )
>>> print(cookie)
name: _profile
content: uid=382|membership=regular...
...
...
expires: July 1 2030, 1:20:40 AM UTC
(3) para criar uma assinatura, acrescentar (ou acrescentar) SECRET_KEY
à sequência de bytes do cookie e gerar um hash a partir dessa combinação.
# encode and salt the cookie, then hash the result
>>> cookie_bytes = str(cookie).encode('utf8')
>>> signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
>>> print(signature)
7ae0e9e033b5fa53aa....
(4) Agora, afixe a assinatura em uma extremidade do content
campo do cookie original.
# include signature as part of the cookie
>>> cookie.content = cookie.content + '|' + signature
>>> print(cookie)
name: _profile
content: uid=382|membership=regular|7ae0e9... <--- signature
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
e é isso que é enviado ao cliente.
# add cookie to response
>>> response.set_cookie(cookie)
# send to browser -->
Ao receber o cookie do navegador:
(5) Quando o navegador retornar esse cookie de volta ao servidor, retire a assinatura do content
campo do cookie para recuperar o cookie original.
# Upon receiving the cookie from browser
>>> cookie = request.get_cookie()
# pop the signature out of the cookie
>>> (cookie.content, popped_signature) = cookie.content.rsplit('|', 1)
(6) Use o cookie original com o aplicativo SECRET_KEY
para recalcular a assinatura usando o mesmo método da etapa 3.
# recalculate signature using SECRET_KEY and original cookie
>>> cookie_bytes = str(cookie).encode('utf8')
>>> calculated_signature = sha1(cookie_bytes+SECRET_KEY).hexdigest()
(7) Compare o resultado calculado com a assinatura anteriormente retirada do cookie recém-recebido. Se eles corresponderem, sabemos que o cookie não foi mexido. Mas se apenas um espaço tiver sido adicionado ao cookie, as assinaturas não corresponderão.
# if both signatures match, your cookie has not been modified
>>> good_cookie = popped_signature==calculated_signature
(8) Se eles não corresponderem, você poderá responder com várias ações, registrar o evento, descartar o cookie, emitir um novo, redirecionar para uma página de login etc.
>>> if not good_cookie:
... security_log(cookie)
Código de autenticação de mensagem com base em hash (HMAC)
O tipo de assinatura gerada acima que requer uma chave secreta para garantir a integridade de alguns conteúdos é chamado na criptografia de código de autenticação de mensagem ou MAC .
Especifiquei anteriormente que o exemplo acima é uma simplificação excessiva desse conceito e que não era uma boa ideia implementar sua própria assinatura. Isso ocorre porque o algoritmo usado para assinar cookies no Flask é chamado HMAC e é um pouco mais envolvido do que o simples passo a passo acima. A idéia geral é a mesma, mas devido a razões além do escopo desta discussão, as séries de cálculos são um pouco mais complexas. Se você ainda estiver interessado em criar um bricolage, como geralmente é o caso, o Python possui alguns módulos para ajudá-lo a começar :) aqui está um bloco de partida:
import hmac
import hashlib
def create_signature(secret_key, msg, digestmod=None):
if digestmod is None:
digestmod = hashlib.sha1
mac = hmac.new(secret_key, msg=msg, digestmod=digestmod)
return mac.digest()
A documentação para hmac e hashlib .
A "desmistificação" de SECRET_KEY
:)
O que é uma "assinatura" neste contexto?
É um método para garantir que algum conteúdo não tenha sido modificado por ninguém que não seja uma pessoa ou entidade autorizada a fazê-lo.
Uma das formas mais simples de assinatura é a " soma de verificação ", que simplesmente verifica se dois dados são iguais. Por exemplo, ao instalar o software da fonte, é importante primeiro confirmar que sua cópia do código fonte é idêntica à do autor. Uma abordagem comum para fazer isso é executar a fonte através de uma função de hash criptográfico e comparar a saída com a soma de verificação publicada na página inicial do projeto.
Digamos, por exemplo, que você está prestes a baixar a fonte de um projeto em um arquivo compactado em gzip de um espelho da web. A soma de verificação SHA1 publicada na página da web do projeto é 'eb84e8da7ca23e9f83 ....'
# so you get the code from the mirror
download https://mirror.example-codedump.com/source_code.tar.gz
# you calculate the hash as instructed
sha1(source_code.tar.gz)
> eb84e8da7c....
Ambos os hashes são iguais, você sabe que possui uma cópia idêntica.
O que é um cookie?
Uma extensa discussão sobre cookies iria além do escopo desta questão. Fornecemos uma visão geral aqui, pois um entendimento mínimo pode ser útil para entender melhor como e por que SECRET_KEY
é útil. É altamente recomendável que você faça algumas leituras pessoais sobre os cookies HTTP.
Uma prática comum em aplicativos da web é usar o cliente (navegador da web) como um cache leve. Os cookies são uma implementação desta prática. Um cookie é geralmente alguns dados adicionados pelo servidor a uma resposta HTTP por meio de seus cabeçalhos. Ele é mantido pelo navegador que posteriormente o envia de volta ao servidor ao emitir solicitações, também por meio de cabeçalhos HTTP. Os dados contidos em um cookie podem ser usados para emular o que é chamado de estado, a ilusão de que o servidor está mantendo uma conexão contínua com o cliente. Somente neste caso, em vez de uma ligação para manter a conexão "ativa", você simplesmente terá instantâneos do estado do aplicativo após o processamento da solicitação de um cliente. Esses instantâneos são transportados entre o cliente e o servidor. Ao receber uma solicitação, o servidor primeiro lê o conteúdo do cookie para restabelecer o contexto de sua conversa com o cliente. Em seguida, ele lida com a solicitação dentro desse contexto e, antes de retornar a resposta ao cliente, atualiza o cookie. A ilusão de uma sessão em andamento é assim mantida.
Como é um cookie?
Um cookie típico ficaria assim:
name: _profile
content: uid=382|status=genie
domain: .example.com
path: /
send for: Encrypted connections only
expires: July 1 2030, 1:20:40 AM UTC
Os cookies são fáceis de ler em qualquer navegador moderno. No Firefox, por exemplo, vá para Preferências> Privacidade> Histórico> remova cookies individuais .
O content
campo é o mais relevante para o aplicativo. Outros campos carregam principalmente meta instruções para especificar vários escopos de influência.
Por que usar cookies?
A resposta curta é desempenho. O uso de cookies minimiza a necessidade de procurar informações em vários armazenamentos de dados (caches de memória, arquivos, bancos de dados, etc.), acelerando as coisas no lado do aplicativo do servidor. Lembre-se de que, quanto maior o cookie, maior a carga útil na rede, portanto, o que você salva na pesquisa de banco de dados no servidor pode perder na rede. Considere com cuidado o que incluir em seus cookies.
Por que os cookies precisam ser assinados?
Os cookies são usados para manter todos os tipos de informações, algumas das quais podem ser muito sensíveis. Eles também não são seguros por natureza e exigem que uma série de precauções auxiliares sejam tomadas para serem consideradas seguras de qualquer forma para ambas as partes, cliente e servidor. Os cookies de assinatura abordam especificamente o problema com o qual eles podem ser alterados na tentativa de enganar aplicativos de servidor. Existem outras medidas para mitigar outros tipos de vulnerabilidades. Convido você a ler mais sobre cookies.
Como um cookie pode ser adulterado?
Os cookies residem no cliente em forma de texto e podem ser editados sem nenhum esforço. Um cookie recebido pelo aplicativo do servidor pode ter sido modificado por vários motivos, alguns dos quais podem não ser inocentes. Imagine um aplicativo da web que mantém informações de permissão sobre seus usuários em cookies e concede privilégios com base nessas informações. Se o cookie não for à prova de mexer, qualquer um poderá modificá-lo para elevar seu status de "role = visitor" para "role = admin" e o aplicativo não será o mais inteligente.
Por que é SECRET_KEY
necessário assinar cookies?
A verificação de cookies é um pouco diferente da verificação do código-fonte da maneira descrita anteriormente. No caso do código fonte, o autor original é o administrador e o proprietário da impressão digital de referência (a soma de verificação), que será mantida pública. O que você não confia é no código-fonte, mas confia na assinatura pública. Portanto, para verificar sua cópia da fonte, você simplesmente deseja que seu hash calculado corresponda ao hash público.
No caso de um cookie, no entanto, o aplicativo não controla a assinatura, ele controla sua assinatura SECRET_KEY
. A SECRET_KEY
é a impressão digital de referência. Os cookies viajam com uma assinatura que afirmam ser legítima. Legitimidade aqui significa que a assinatura foi emitida pelo proprietário do cookie, ou seja, o aplicativo e, nesse caso, é a alegação de que você não confia e precisa verificar a validade da assinatura. Para fazer isso, você precisa incluir um elemento na assinatura que é conhecido apenas por você, que é o SECRET_KEY
. Alguém pode alterar um cookie, mas como não possui o ingrediente secreto para calcular corretamente uma assinatura válida, não pode falsificá-la. Conforme declarado um pouco antes, esse tipo de impressão digital, onde além da soma de verificação, também é fornecida uma chave secreta,
E as Sessões?
As sessões em sua implementação clássica são cookies que carregam apenas um ID no content
campo, o session_id
. O objetivo das sessões é exatamente o mesmo que os cookies assinados, ou seja, para impedir a violação de cookies. As sessões clássicas têm uma abordagem diferente. Ao receber um cookie de sessão, o servidor usa o ID para procurar os dados da sessão em seu próprio armazenamento local, que pode ser um banco de dados, um arquivo ou, às vezes, um cache na memória. O cookie da sessão geralmente está definido para expirar quando o navegador é fechado. Por causa da etapa de pesquisa de armazenamento local, essa implementação de sessões geralmente gera um impacto no desempenho. Os cookies assinados estão se tornando uma alternativa preferida e é assim que as sessões do Flask são implementadas. Em outras palavras, as sessões do balão sãocookies assinados e para usar cookies assinados no Flask, basta usar sua Session
API.
Por que não criptografar também os cookies?
Às vezes, o conteúdo dos cookies pode ser criptografado antes de ser assinado . Isso é feito se eles forem considerados muito sensíveis para serem visíveis no navegador (a criptografia oculta o conteúdo). Simplesmente assinar cookies, no entanto, atende a uma necessidade diferente, onde existe o desejo de manter um certo grau de visibilidade e usabilidade dos cookies no navegador, enquanto impede que eles se intrometam.
O que acontece se eu mudar o SECRET_KEY
?
Ao alterar a opção, SECRET_KEY
você está invalidando todos os cookies assinados com a chave anterior. Quando o aplicativo recebe uma solicitação com um cookie que foi assinado com um anterior SECRET_KEY
, tentará calcular a assinatura com a nova SECRET_KEY
e as duas assinaturas não corresponderão, esse cookie e todos os seus dados serão rejeitados, será como se o navegador está se conectando ao servidor pela primeira vez. Os usuários serão desconectados e seus cookies antigos serão esquecidos, juntamente com qualquer coisa armazenada dentro deles. Observe que isso é diferente da maneira como um cookie expirado é tratado. Um cookie expirado pode ter sua concessão prorrogada se sua assinatura sair. Uma assinatura inválida implica apenas um cookie inválido simples.
Portanto, a menos que você queira invalidar todos os cookies assinados, tente manter SECRET_KEY
o mesmo por longos períodos.
O que é bom SECRET_KEY
?
Uma chave secreta deve ser difícil de adivinhar. A documentação em Sessões tem uma boa receita para geração aleatória de chaves:
>>> import os
>>> os.urandom(24)
'\xfd{H\xe5<\x95\xf9\xe3\x96.5\xd1\x01O<!\xd5\xa2\xa0\x9fR"\xa1\xa8'
Você copia a chave e cola-a no seu arquivo de configuração como o valor de SECRET_KEY
.
Além de usar uma chave gerada aleatoriamente, você pode usar uma variedade complexa de palavras, números e símbolos, talvez organizados em uma frase conhecida apenas por você, codificada em formato de byte.
Você não definir o SECRET_KEY
diretamente com uma função que gera uma chave diferente a cada vez que é chamado. Por exemplo, não faça isso:
# this is not good
SECRET_KEY = random_key_generator()
Cada vez que seu aplicativo é reiniciado, ele recebe uma nova chave, invalidando a anterior.
Em vez disso, abra um shell python interativo e chame a função para gerar a chave, copie e cole-a na configuração.