JWT com chave simétrica - O perigo mora em casa
A cada dia que passa o JWT se torna mais popular. Uma opção quase padrão para proteger API's. O problema não é a chave simétrica e sim como ela é utilizada. Você deveria tomar cuidado e rever a implementação de JWT em sua API.
Diversas empresas como Cisco e IBM já divulgaram que cerca de 60% dos ataques as nossas empresas estão relacionados com fúncionarios. Dito isto, você deveria rever a forma como a implementação de JWT com chave simétrica que você está utilizando em sua API.
O problema não é a chave Simétrica. E sim como ela é utilizada. Veja o seguinte trecho de código:
Nesse exemplo a chave está vindo do appsettings.json
. Mas poderia estar armazenado no banco ou até mesmo ser Hard Code.
Veja como é simple atacar
Basta ir no site jwt.io, colar o JWT gerado, e lá no campo verify signature, digitar sua chave privada.
Veja mais detalhes abaixo:
Quando utilizar uma chave simétrica?
Para um monolito, isso não é um problema, mas se você possui um sistema distribuído construído com vários serviços em execução independentemente um do outro, basicamente precisa escolher entre duas opções muito ruins:
- Optar por ter um serviço dedicado para geração e validação de token. Quaisquer serviços que recebem um token de um cliente precisam fazer uma chamada no serviço para que o token seja verificado. Isso rapidamente pode se tornar um gargalo de desempenho.
- Compartilhar a chave em todos os serviços que recebem tokens dos clients. Assim conseguem validar. Sem precisar fazer uma chamada para o serviço de validação. Mas ter a chave em vários locais aumenta consideravelmente o risco de ser comprometida. E, uma vez comprometida, o invasor pode gerar tokens válidos e se passar por qualquer usuário do sistema.
Diante um cenário com sistemas distribuidos considere adotar um servidor OAuth 2.0 com OpenId Connect. Pois esses protocolos irão adicionar conceitos como Open Discovery Document, JWKS, além de utilizar chaves assimétricas. Que de longe, esse conjunto de técnicas são as melhores abordagens para lidar não só com esse problema, mas também outros associados a esse tipo de ambiente.*
Mas se você está diante de um monolito o melhor a ser feito é gerar uma chave simétrica de forma adequada e armazenar com segurança. Ainda assim, se a chave utilizada tiver sido gerada similar ao exemplo acima. Você tem um grande problema.
Se você trabalha num time pequeno esse risco pode ser tolerável. Em times médios e grandes é uma potencial brecha de segurança. Afinal, qualquer time em posse da chave privada pode gerar Tokens e fazer impersonate nas permissões e usuários. Conseguindo acessos privilegiados em suas API's.
O problema no fim é garantir que sua chave será devidamente armazenada. E somente compartilhada por entidades confiáveis.
Como gerar uma chave simétrica
Esses pontos devem ser levados em consideração para gerar uma Chave Simétrica.
- Como gerar a chave
- O tamanho da chave
- Como armazenar o JWK
A chave jamais deverá ser um texto simples. O .NET Core tem componentes desenvolvidos especificamente com esse objetivo.
Veja o exemplo abaixo para gerar uma chave simétrica.
Duas modificações importantes no algoritmo acima:
- A chave tem 64 bytes
- Foi utilizado o componente
System.Security.Cryptography.RandomNumberGenerator
Não é a toa que o componente RandomNumberGenerator faz parte do namespace System.Security.Cryptography. Além disso uma pergunta deve ser feita:
Por que a chave tem 64 bytes?
Por que a pergunta é importante? Ao gerar uma Chave o tamanho dos Bytes importam. Há recomendações sobre o tamanho da chave.
O NIST publicou um documento Recommendation for Applications Using Approved Hash Algorithms, Security Effect of the HMAC Key Section - 5.3.4 onde faz recomendações em relação a Chave e o Algoritmo que será utilizado.
"alg" | Algoritmo | Key Size |
---|---|---|
HS256 | HMAC using SHA-256 | 64 bytes |
HS384 | HMAC using SHA-384 | 128 bytes |
HS512 | HMAC using SHA-512 | 128 bytes |
Assim como o NIST a própria Microsoft reforça o tamanho da chave, links ao final.
Armazenando a chave
Você já deve estar se perguntando:
Gerar uma chave randômica me causaria problemas, pois assim que minha aplicação fosse reiniciada, uma nova chave seria gerada.
Você tem razão. E é nesse ponto que 95% dos exemplos usualmente encontrados na internet falham. O ideal é armazenar o JWK e reutilizá-lo.
Nesse exemplo, apesar de sido uma chave Simétrica, foi utilizado as melhores práticas.
- A Chave foi auto-gerada.
- Armazenado em um local seguro.
Dessa forma você dificulta os usuários mal intencionados de utilizar a chave e gerar JWT's.
Componente Jwks.Manager
Ao invés de fazer cada um desses algoritmos no braço, escolher aonde salvar. Recomendo a utilização do componente Jwks.Manager que irá não só gerar o JWK, como fazer a gestão dele e eventualmente expirar após um tempo pré determinado.
Conclusão
Espero que com esse exemplo te ajude a repensar como assinar o seu JWT e te ajude a rever suas implementações.
Qualquer dúvida, sugestões, comente aqui e vamos bater um papo!
Download
O código do projeto está disponível no meu GitHub
Referências
- HMAC256 Constructor - Key Size recommendation
- HMAC384 Constructor - Key Size recommendation
- HMAC512 Constructor - Key Size recommendation
- RFC 7518
Revisões
Originalmente o texto indicava apenas chaves assimétricas como uma boa decisão para um OAuth 2.0. Podendo gerar um mal entendido, que somente um servidor OAuth 2.0 utiliza chaves assimétricas. Após correções de nosso amigo Henrique César. O texto foi revisado.