Eu realmente gosto do nível de segurança fornecido pelo U2F, mas junto com a segurança, você precisa considerar um plano de recuperação. Perder o acesso às suas contas mais importantes, se algo acontecer com o token U2F principal, é um problema sério. Ao mesmo tempo, gostaria de evitar o uso de um backup que comprometa a segurança fornecida pelo U2F.
Métodos de backup populares
Até o momento, é uma boa prática manter um segundo token U2F independente para backup; esse token deve ser adicionado manualmente a cada serviço e armazenado em um local "seguro". Outra prática comum é usar o método não U2F como backup (OTP, códigos de recuperação). Honestamente, esses dois métodos deixam muito a desejar.
Token U2F independente
Isso significa que toda vez que eu me registrar em algum serviço novo, preciso adicionar meus dois tokens. Esse fato levanta uma série de problemas:
- Um token de backup deve ser facilmente acessível. Apesar de não o levar comigo em um chaveiro, devo poder acessá-lo rapidamente, de modo que mal consigo encontrar algo melhor do que mantê-lo em casa. Quão real é seguro, mesmo que um cofre seja usado, você pode conversar por um longo tempo;
- Quando preciso me registrar em um serviço enquanto estiver fora de casa, não consigo adicionar um token de backup. Portanto, você deve tentar se lembrar de que precisa adicioná-lo mais tarde e, até que isso aconteça, não há backup. Na pior das hipóteses, posso esquecê-lo completamente;
- Quando estou em casa, meus dois tokens estão no mesmo lugar. Esse método de backup está longe de ser o ideal: ambos os tokens podem estar indisponíveis devido a um incidente (ser destruído ou roubado);
- O fato de o token de backup ser armazenado em casa é completamente óbvio. Se alguém realmente deseja acessar meu token, ele já sabe onde procurar;
- Método não universal: nem todos os serviços permitem adicionar mais de uma chave à sua conta.
Na minha opinião, essa "prática exemplar" não é muito confiável e bastante onerosa. Vejamos outra prática comum.
Método não U2F como backup
OTP:
- Usar o OTP como backup é melhor do que usá-lo como o principal método 2FA, mas o fato de ter o OTP de alguma forma abre um vetor de ataque adicional;
- Os telefones quebram, são perdidos e roubados, e se após a perda houver uma chance de que ele esteja nas mãos de estranhos, será necessário recuperar manualmente esse backup em todas as contas;
- Eu sempre carrego um telefone e um token U2F comigo; portanto, esse método de backup está longe de ser ideal: a probabilidade de perder os dois imediatamente é muito maior do que se o backup fosse armazenado separadamente. Mas esse item pode ser ligeiramente compensado usando, por exemplo, Authy, que armazena o backup criptografado em seu servidor;
- Método não universal: infelizmente, há um número suficiente de serviços que oferecem apenas aplicativos personalizados e não suportam o TOTP padrão.
Códigos de recuperação:
- Os códigos de recuperação devem ser armazenados em um local seguro. Novamente, esse "lugar seguro" provavelmente será minha casa, com quase os mesmos problemas que um token U2F separado;
- Novamente, um método não universal: cada serviço tem sua própria abordagem para fazer backup
Então, para resumir, todos esses métodos são não universais, onerosos e não muito seguros.
O melhor método de backup
Agora, depois de ter criticado suficientemente o estado atual, finalmente direi o que realmente quero. Eu realmente quero ter dois tokens U2F: primário e de backup, mas eles devem ser configurados de uma certa maneira:
- Quando eu registro o token principal em qualquer dispositivo, o token de backup se torna operacional automaticamente para este serviço;
- Assim que eu uso um token de backup em qualquer serviço, o token principal é inválido para este serviço.
Antes de discutirmos a viabilidade técnica disso no U2F, explicarei por que é ótimo e como o uso.
Por que isso é ótimo?
Se examinarmos as críticas ao token de backup independente descrito acima, podemos ver que todas as deficiências desse método são eliminadas:
- O token de backup não deve mais ser facilmente acessível. Exemplos extremos podem ser: colocar uma ficha dentro de uma parede de tijolos ou enterrar um metro e meio em um jardim ou em outro lugar. Sem brincadeira, eu estou pronto para ir em frente;
- Independentemente de onde eu esteja, se eu me inscrever em algum serviço, não preciso fazer nada para adicionar um token de backup a esse serviço. Eu apenas uso meu token principal e estou em paz, sabendo que tenho um backup;
- Para pessoas de fora, não está totalmente claro onde meu token de backup está localizado. Mesmo sabendo que existe, tentar encontrá-lo dificilmente faz sentido;
- É seguro o suficiente. Mesmo que algo ruim aconteça com meu token principal, é altamente improvável que o mesmo incidente afete o token de backup;
- Isso é universal. Esse método de backup funcionará em qualquer serviço que suporte U2F, independentemente do que mais esse serviço suporta.
E se algo ruim realmente acontecer com o token principal, faça o seguinte:
- Eu desenterro / deixo claro um token de backup;
- Autenticar em todos os meus serviços com U2F, cancelando o token principal;
- Encomendei um novo par de tokens e, ao recebê-lo, adicionei um novo token principal em todos os serviços e revoguei o antigo.
Pelo menos para mim, pessoalmente, essa estratégia é um grande compromisso para um alto nível de segurança e um fardo fácil de backup. É mais seguro e confiável do que qualquer outro método.
Implementação
Visão geral do protocolo U2F
Antes de podermos falar sobre implementação, precisamos entender em um certo nível como o U2F funciona. A maioria dos fabricantes o implementa da seguinte maneira (nem todos os itens a seguir estão presentes no padrão; algumas coisas são detalhes da implementação, mas a maioria das implementações existentes, tanto quanto eu sei, funciona exatamente dessa maneira):
device_secret
programado no token U2F, junto com um
counter
32 bits, que só pode ser incrementado. Quando registramos um token U2F em um serviço, acontece o seguinte:
- O navegador envia o
AppID
(de fato, o nome do domínio) para o dispositivo U2F; - O dispositivo gera um número aleatório (
nonce
), combina com ele com o AppID
, passa tudo pelo HMAC-SHA256 usando device_secret
como a chave, e o hash resultante se torna a chave privada para esse serviço específico: service_private_key
; - No
service_private_key
, a chave pública service_public_key
gerada; - O dispositivo pega o
AppID
novamente, combina-o com service_private_key
e passa-o novamente pelo HMAC-SHA256 usando device_secret
como a chave. O resultado ( MAC
), juntamente com o nonce
que foi gerado anteriormente, torna-se key_handle
; - O dispositivo envia
key_handle
e service_public_key
volta ao navegador, e o navegador passa para o serviço, que salva esses dados para futuras autenticações.
A autenticação subsequente prossegue da seguinte maneira:
- O serviço gera um
challenge
(dados gerados aleatoriamente) e o envia ao navegador junto com key_handle
(que consiste em nonce
e MAC
). O navegador passa tudo isso para o dispositivo, juntamente com o AppID
(ou seja, nome de domínio); - O dispositivo, com
nonce
e AppID
, gera service_private_key
da mesma maneira que foi gerado durante o registro; - O dispositivo gera um
MAC
da mesma maneira que durante o registro e, comparando-o com o MAC
recebido do navegador, nonce
que o nonce
não nonce
substituído e, portanto, service_private_key
confiável; - O dispositivo incrementa o
counter
; - O dispositivo assina o
challenge
, AppID
e counter
usando service_private_key
e envia a assinatura ( signature
) e o counter
resultantes counter
navegador, que transfere esses dados ainda mais para o serviço; - O serviço verifica a
signature
usando o service_public_key
que possui após o registro. Além disso, a maioria dos serviços verifica se o counter
maior que o valor anterior (se essa não for a primeira autenticação). O objetivo deste teste é tornar a clonagem de dispositivos U2F inacessível. Como resultado, se a signature
corresponder e o counter
maior que o valor anterior, a autenticação será considerada concluída com êxito e o serviço salvará o novo valor do counter
.
Agora, vamos descrever os detalhes diretamente relacionados à discussão.
Detalhes de interesse
A primeira é que o dispositivo não armazena
service_private_key
para cada serviço: em vez disso, exibe
service_private_key
toda vez usando o HMAC-SHA256. Isso é muito importante para nós: é óbvio que se cada dispositivo armazenasse chaves únicas separadamente para cada serviço, somente esse dispositivo poderia ser autenticado posteriormente.
A propósito, isso não é um requisito do U2F: o U2F não indica como as chaves devem ser armazenadas, e algumas implementações anteriores do U2F armazenaram as chaves de cada serviço separadamente. Essa abordagem tem a desvantagem de que o número de serviços para os quais o dispositivo pode ser usado é limitado. A derivação de service_private_key
elimina essa desvantagem.E segundo, o dispositivo possui um
counter
para impedir a clonagem.
À primeira vista, pode parecer que esse
counter
não nos permita implementar a estratégia de backup discutida (pelo menos me pareceu quando tentei encontrar uma solução), mas, na verdade, isso apenas nos ajuda! Eu vou explicar agora.
Ideia principal
A idéia é a seguinte: na fase de produção, programe dois tokens de forma que ambos tenham o mesmo
device_secret
, mas o token de backup precisa de alguma correção: em vez de usar o
counter
em sua forma pura (como fazem os tokens comuns), adicione alguma grande constante para
counter
. Por exemplo, metade do intervalo de 32 bits, ou seja, aproximadamente
2 000 000 000
, parece razoável: dificilmente esgotarei tantas autenticações em toda a minha vida.
De fato, é tudo. Simples e eficaz.
Com dois tokens programados dessa maneira, oculto o token de backup em um local
realmente difícil de alcançar e nunca o toco. Se algo terrível acontecer e eu perder o acesso ao token principal, continuo acessando o token de backup e posso usá-lo imediatamente em todos os serviços em que registrei o token principal, porque O backup tem o mesmo
device_secret
e seu
counter
começa com um número realmente grande, que não receberei pelo resto da minha vida.
Além disso, chamo a atenção para o fato de
não propor clonagem de tokens . Dois tokens, embora tenham o mesmo
device_secret
, possuem contadores diferentes e, após a programação do
device_secret
não deve haver maneira de recuperá-lo do dispositivo ou criar um clone de qualquer outra maneira.
Uma nota sobre o contador
Um leitor atento pode perceber que existe o seguinte problema de segurança: e se um invasor obtiver acesso ao token principal e de alguma forma iniciar 2.000.000.000 de autenticações? Em seguida, ele obtém acesso ao serviço, mesmo após o token de backup ter sido usado nesse serviço.
Felizmente, esse problema tem uma solução simples. De qualquer forma, o contador deve ser implementado no hardware (presumivelmente em algum processador de criptografia) e, para uma implementação segura, esse contador de hardware deve ter um intervalo inferior a 32 bits. Por exemplo, no
ATECC508A, os contadores podem contar apenas até 2097151, portanto, definindo a constante adicionada ao contador para qualquer valor maior que o valor máximo do contador, podemos ter certeza de que o token principal nunca pode contar para o contador no token de backup.
Para esclarecer: digamos que nosso token U2F use ATECC508A e denote o contador dentro do ATECC508A como
hw_counter
. Então:
- No token principal, usamos para cálculos:
hw_counter
; - No token de backup, usamos para cálculos:
hw_counter + 2000000000
.
Observe que não modificamos o
hw_counter
real dentro do processador de criptografia; ainda contará de 0 a 2097151. Em vez disso, toda vez que precisarmos obter o valor do contador, lemos
hw_counter
de ATECC508A, em seguida, adicionamos nossa constante e a retornamos (para cálculos adicionais para U2F).
Portanto, o intervalo de valores do contador no token principal será [0, 2097151], enquanto o intervalo de valores do contador no token de backup será [2000000000, 2002097151]. O fato de esses intervalos não se sobrepor garantem o cancelamento do token principal ao usar um backup (se o serviço usa
counter
; os principais serviços que eu verifiquei o usam).
Implementação real
Nenhum dos fabricantes de tokens U2F que eu conheço suporta hoje a personalização necessária. Felizmente, porém, existe uma implementação de código aberto do token
U2F :
SoloKeys .
Escrevi meu artigo original (em inglês) há um ano, e essa parte é um pouco datada: o SoloKeys estava no estágio de prototipagem e usei a iteração anterior do projeto:
u2f-zero . Portanto, não vou traduzir esta parte agora, já que a única maneira de obter um dispositivo u2f-zero é soldá-lo você mesmo, e dificilmente é recomendável fazer isso (embora haja instruções no github).
No entanto, todos os detalhes da modificação necessária de u2f-zero são fornecidos no
artigo original .
Quando minhas mãos alcançarem os solokeys, escreverei instruções para sua modificação.
De uma forma ou de outra, é a única maneira que conheço hoje para obter um token U2F funcional com um backup confiável. A verificação de vários serviços (pelo menos google e github) mostrou que funciona: registrando o token principal no serviço, também podemos usar o backup e, após o primeiro uso do backup, o token principal para de funcionar. Awwwwwww. <3
Advertência
Apesar de essa estratégia de backup ser legal, não tenho tanta certeza de sua implementação específica por meio do u2f-zero ou do solokey. Esse caminho é a única maneira de conseguir o que você quer, então eu fui por esse caminho; mas supondo que o invasor esteja obtendo acesso físico ao dispositivo U2F, não tenho certeza de que invadir o dispositivo (ou seja, obter o
device_secret
dele) será tão difícil quanto seria no caso da Yubikey ou de outros grandes fabricantes. Os autores da solokey afirmam que "o nível de segurança é o mesmo de uma chave de carro moderna", mas não realizei nenhum exame para confirmar isso.
No entanto, para ser sincero, não estou realmente preocupado com isso. Se um invasor simplesmente rouba um token sem a intenção de devolvê-lo, a complexidade de quebrá-lo não importa, porque um invasor pode simplesmente usar esse token para acessar uma conta e, por exemplo, simplesmente revogá-lo e adicionar outro. No entanto, para isso, também devo ter outros problemas de segurança sérios. O token U2F é apenas o segundo fator.
Portanto, o único cenário em que o solokey pode ser menos seguro do que qualquer outra coisa é quando um invasor tenta acessar o dispositivo por um curto período de tempo, obtém o
device_secret
e devolve o dispositivo, invisivelmente para mim. Para fazer isso, ele precisa ler o conteúdo do microcontrolador flash (ou RAM na hora certa), e isso não é muito trivial.
Levando em conta todos os fatores, acredito que para mim, pessoalmente, ter um backup confiável é muito mais importante do que ter uma implementação de hardware ultra-segura de um dispositivo U2F. A probabilidade de problemas com uma implementação tão segura e a falta de um bom backup é maior que a probabilidade de problemas com o u2f-zero (solokey) e o backup.
Conclusão
A estratégia de backup considerada supera as alternativas em todas as dimensões: é universal, mais segura e mais confiável do que qualquer outro método.
Ficarei feliz se pelo menos um dos principais fabricantes implementar isso em seus produtos, mas ainda não há certeza. Um cara do suporte do Yubico, James A., até me disse que para implementar o backup conforme necessário, não é possível com a maneira como o U2F é projetado e, depois de definir os detalhes da implementação, ele parou de responder.
Felizmente, isso não foi tão impossível quanto Yubico acredita.
Meu artigo original em inglês: Backup confiável, seguro e universal para token U2F . Porque o autor do artigo original sou eu mesmo e, com sua permissão, não coloquei este artigo na categoria "tradução" .