ASP.NET Core - Como assinar digitalmente seu JWT
Quando decidimos utilizar JWT em nossas API's e Frontend SPA, precisamos utilizar um algoritmo ao emitir um token. Há diversas opções para assinar o JWT. Ele deve ser simétrico ou assimétrico. Probabilistico ou deterministico. Veja neste artigo como assinar seu JWT e dicas no uso deles.
Ao gerar um JWT, é necessário informar um algoritmo de criptografia. Veja exemplo:
Além do RSA é possivel utilizar chaves simétricas e ECDSA.
Para saber escolher o algoritmo, é necessário entender onde cada um deles se encaixa.
Algoritmo
Um algoritmo de criptografia pode ser considerado como uma fórmula. É um conjunto de procedimentos matemáticos. E através do uso desse algoritmo, as informações são transformadas em Ciphertext (texto cifrado) e requerem o uso de uma chave para transformar os dados em sua forma original.
JWT com chave simétrica
Ao usar um algoritmo simétrico, uma única chave é usado. Tanto para criptografar os dados quanto na descriptografia. Nesse caso, as duas partes envolvidas numa comunicação precisam ter a chave. Seja para leitura, ou para escrever uma nova.
HMAC
Hash-Based Message Authentication Codes (HMACs) são um grupo de algoritmos que utilizam uma chave simétrica para assinar as mensagens e sua segurança está ligado a função hash utilizada, por exemplo, SHA256.
Quando utilizar uma chave simétrica?
Apenas em cenários onde haverá UMA única API. Pois, as demais API's não irão ter habilidade para validar o JWS, a menos que a chave privada seja compartilhada entre as API's.
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.
Gerando uma chave simétrica HMAC
Ao gerar uma chave simétrica você deve se questionar: Somente uma API vai gerar o JWT ou mais serviços terão esse direito? (Não há problema se no futuro essa estratégia mudar).
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.
Sabendo disto é possível gerar uma chave automaticamente com os componentes do .Net. Dessa forma a única API pode armezenar a chave em algum lugar. E recuperar toda vez que for assinar um JWT. Isso garante segurança, uma vez que o registro for removido, uma nova chave, totalmente aleatória será gerada.
Fazendo desta forma haverá uma burocracia a mais, pois compartilhar a chave, significa compartilhar o JWK. Seja através de um arquivo físico ou pelo Banco de dados.
No exemplo o que merece destaque é a utilização do objeto System.Security.Cryptography.RandomNumberGenerator
para a geração da chave.
Essa implementação acima possui um problema. Caso a aplicação reinicie as chaves irão renovar, invalidando os tokens gerados anteriormente.
Para resolver esse problema o objeto JsonWebKey
tem o objetivo de salvar os parâmetros da criptografia. Assim caso a aplicação seja reiniciada, basta verificar se existe esse objeto e recuperar. Caso contrário, cria uma nova.
Repare que o código utiliza o componente JsonWebKey, que abordamos em outro artigo: Componentes do JWT (JWS, JWE, JWA, JWK, JWKS).
JWT com chave assimétrica
Um algoritmo assimétrico envolve duas chaves. Uma chave pública e outra chave privada. Enquanto uma chave (privada) é usada para assinar digitalmente a mensagem a outra chave (pública) só pode ser usada para verificar a autenticidade da assinatura.
A RFC 7518 define os algoritmos RSA e ECDsa para assinar um JWT. Há diversas variações do RSA e ECDsa. Os exemplos irão utilizar os mais recomendados pela RFC 7518.
RSA
RSA é o acrônimo de Rivest–Shamir–Adleman. É uma criptografia criada em 1977. E revolucionou os métodos utilizados na época. Pois introduziu o conceito de chaves assimétricas.
Até então os modelos de criptografia utilizam a mesma chave para criptografar e descriptografar a mensagem.
O RSA é muito utilizado para criar assinaturas digitais. O proprietário da chave privada é o único capaz de assinar uma mensagem. Enquanto a chave pública permite que qualquer entidade verifique a validade da assinatura.
Elliptic Curves - ECDsa
Há diversas explicações técnicas sobre ECC (Elliptic Curve Cryptography). Links abaixo para curiosos. De maneira pratica o EC é melhor e mais eficiente que o RSA. Especialistas e mais entendidos sobre o assunto, dizem que o ECC revolucionou os algoritmos assimétricos.
Quando utilizar chave assimétrica?
A melhor resposta seria, sempre que possível. Porém em ambientes que possuem uma única API. Que não há uma URI para consultar a chave pública. Não é um servidor OAuth 2.0. Não será um grande diferencial.
Mas considere que se você adotou a estratégia do exemplo anterior. E começou a salvar a chave (JWK) em algum local como Filesystem, banco de dados ou Blob. Passar a utilizar chaves assimétrica vai conferir um nível a mais de segurança.
Se estiver em um ambiente OAuth 2.0. A utilização de chaves assimétricas é obrigatório, devido a natureza da especificação do OAuth 2.0.
Gerando RSASSA-PSS using SHA-256 and MGF1 with SHA-256
Considere o seguinte código:
- O tamanho
2048
é o mínimo especificado pela RFC 7518 - section 3.5.
A utilização do RSA através do .NET é muito simples. Utilizando a mesma técnica do exemplo anterior. É possível salvar os dados da chave criada e assim recarregar a mesma chave quando a aplicação reiniciar.
Simples, né?
Gerando ECDSA using P-256 and SHA-256
Esse é o algoritmo mais recomendado pela RFC para utilizar na assinatura do seu JWT.
Igualmente ao RSA, sua utilização é simples.
A curva Nist P256 é especificado na própria RFC, por isso está sendo utilizada.
Para salvar em disco, ou em outro local, há um pequeno pormenor. O método da classe JsonWebKeyConverter
, utilizado nos exemplos anteriores não está disponível para netstandard2.1. Por causa de um bug na biblioteca Microsoft.IdentityModel.JsonWebTokens
. Já reportado no Github.
No entanto, é muito simples fazer um parse manualmente.
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.
Download
O código do projeto está disponível no meu GitHub
Conclusão
Muitas coisas sobre JWT e OAuth 2.0 são complexos na teoria. Porém essa complexidade é abstraida através dos diversos componentes disponiveis.
Espero que esse artigo te ajude a entender e também melhorar a segurança das suas API's.