Ao utilizar JWT em nossas APIs é necessário selecionar um algoritmo para emitir os tokens.
Existem várias opções de algoritmos de criptografia para assinar um JWT, pode ser simétrico ou assimétrico, probabilístico ou determinístico. Então vamos falar sobre como fazer?
Ao gerar um JWT, é essencial especificar um algoritmo de criptografia. Veja um exemplo abaixo:
Além do RSA, também é possível utilizar chaves simétricas e ECDSA.
Para escolher o algoritmo adequado, é importante compreender onde cada um se aplica e suas características específicas.
Algoritmo
Um algoritmo de criptografia pode ser comparado a uma fórmula matemática, consiste em um conjunto de procedimentos matemáticos. Através dessa fórmula, temos a capacidade de transformar os dados em texto cifrado (Ciphertext). E só é possivel desfazer o CipherText para obter o conteúdo original através de uma chave que utilizamos no processo.
JWT com chave simétrica
Ao usar um algoritmo simétrico, uma única chave é utilizada tanto para criptografar quanto para descriptografar os dados. Nesse caso os envolvidos no processo de comunicação precisam possuir essa chave, seja para ler as informações ou criar um novo texto criptografado.
HMAC
Hash-Based Message Authentication Codes (HMACs) são um grupo de algoritmos que utilizam uma chave simétrica para assinar mensagens. A segurança desses algoritmos está diretamente relacionada à função hash utilizada, como, por exemplo, SHA256.
Quando utilizar uma chave simétrica?
O uso de chaves simétricas é recomendado apenas em cenários onde haverá uma única API. Isso porque outras APIs não poderão validar o JWT a menos que a chave privada seja compartilhada entre elas.
Em equipes pequenas, esse risco pode ser tolerável. Porém, em equipes médias e grandes, isso representa uma potencial brecha de segurança, pois qualquer equipe que tenha acesso à chave privada pode gerar tokens e se passar por outros usuários, obtendo acessos privilegiados nas APIs.
O desafio, no fim das contas, é garantir que a chave seja devidamente armazenada e compartilhada apenas entre entidades confiáveis.
Gerando uma chave simétrica HMAC
Ao gerar uma chave simétrica, é importante se questionar: apenas uma API irá gerar o JWT ou mais serviços terão esse direito? (Não há problema se essa estratégia mudar no futuro).
Por que essa pergunta é importante? Porque o tamanho dos bytes da chave importa. Há recomendações específicas 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 que fornece orientações sobre o tamanho da chave e o algoritmo a 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 a importância do tamanho da chave. Links para mais informações estão disponíveis ao final.
Com esse conhecimento, é possível gerar uma chave automaticamente usando componentes do .NET. Dessa forma, uma única API pode armazenar a chave em um local seguro e recuperá-la sempre que for necessário assinar um JWT. Isso garante segurança, pois se o registro for removido, uma nova chave aleatória será gerada.
Esse método adiciona uma camada extra de burocracia, pois compartilhar a chave significa compartilhar o JWK, seja por meio de um arquivo físico ou de um banco de dados.
No exemplo acima, quero destacar a utilização do objeto System.Security.Cryptography.RandomNumberGenerator
para a geração da chave.
No entanto, essa implementação possui um problema: caso a aplicação seja reiniciada, as chaves serão renovadas, invalidando os tokens gerados anteriormente.
Para resolver esse problema, o objeto JsonWebKey pode ser utilizado para salvar os parâmetros da criptografia. Assim, caso a aplicação seja reiniciada, basta verificar se esse objeto existe e reutilizar a chave criada anteriormente. Caso contrário, cria uma nova chave.
JWT com chave assimétrica
Um algoritmo assimétrico envolve duas chaves: uma chave pública e uma chave privada. A chave privada é utilizada para assinar digitalmente a mensagem, enquanto a chave pública é usada para verificar a autenticidade dessa assinatura.
A RFC 7518 define os algoritmos RSA e ECDsa para assinatura de JWTs. Existem várias variações desses algoritmos. Os exemplos a seguir utilizam as mais recomendadas pela RFC 7518
RSA
RSA é o acrônimo de Rivest–Shamir–Adleman, um método de criptografia criado em 1977 que revolucionou ao introduzir o conceito de chaves assimétricas. Antes disso, todos os modelos de criptografia utilizavam a mesma chave para criptografar e descriptografar as mensagens.
RSA é amplamente utilizado para criar assinaturas digitais. Somente o proprietário da chave privada pode assinar uma mensagem, enquanto a chave pública permite que qualquer entidade verifique a validade dessa assinatura.
A segurança do RSA recae sobre o fato de não haver nenhuma maneira eficiente de fatorar grandes números. Qualquer método atual recae sobre alguma forma de Tentative and Error.
Elliptic Curves - ECDsa
De maneira pratica as Curvas Elipticas são mais eficiente que o RSA. No entanto isso não significa que é mais segura. Diferente do RSA a segurança das curvas elipticas recae no problema do logaritmo discreto.
Seja RSA ou EC, tentar quebrar a chave é necessário recorrer a métodos de brute force. E quando o assunto é criptografia, qualquer solução de brute force nem é considerado uma opção viável. Já que fazer um brute force hoje em uma RSA com o computador mais rápido do mundo levará alguns milhares de anos.
Quando utilizar chave assimétrica?
A melhor resposta é: Sempre que possível. No entanto, em ambientes com uma única API, a simplicidade de um algoritmo simétrico já é o suficiente.
No entanto, se você já está armazenando a chave (JWK) em um local seguro, como um sistema de arquivos, banco de dados ou blob, a utilização de chaves assimétricas adicionará um nível extra de segurança.
Todo sistema que implementa o protocolo OAuth 2.0 faz o uso de chaves assimétricas, uma vez que devido a natureza de seu propósito não faria nenhum sentido ser diferente disso.
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 - seção 3.5.
O uso do RSA com .NET é bastante simples. Utilizando a mesma técnica do exemplo anterior, é possível salvar os dados da chave criada e recarregar a mesma chave quando a aplicação reiniciar.
Simples, não?
Gerando ECDSA using P-256 and SHA-256
Esse é o algoritmo mais recomendado pela RFC para assinar seu JWT. Assim como o RSA, sua utilização é simples.
A curva NIST P-256 é especificada na própria RFC, por isso está sendo utilizada.
Referências
- HMAC256 Constructor - Key Size recommendation
- HMAC384 Constructor - Key Size recommendation
- HMAC512 Constructor - Key Size recommendation
- RFC 7518
- A (Relatively Easy To Understand) Primer on Elliptic Curve Cryptography
- Serious Cryptography: A Practical Introduction to Modern Encryption