Um app (Senha de Uso Único Baseada em Tempo, do inglês Time-based One-Time Password) e um servidor só concordam sobre códigos se partirem do mesmo segredo e das mesmas configurações. Colocar esse segredo e essas configurações no app é o provisionamento, e ele roda sobre um pequeno padrão de fato: a URI otpauth.
A URI otpauth
O enrollment é carregado em uma URI da forma otpauth://TIPO/LABEL?PARÂMETROS. Uma TOTP típica se parece com:
otpauth://totp/Example:alice@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Example&algorithm=SHA1&digits=6&period=30
As partes são:
- tipo:
totppara baseado em tempo, ouhotppara baseado em contador. - label: a conta a que isto se refere, normalmente
Issuer:conta, para que o app possa exibir um nome significativo. - secret: a chave compartilhada, em base32. Este é o único campo que precisa ser mantido em segredo.
- issuer: o nome do serviço, exibido no app e usado para desambiguar contas.
- algorithm, digits, period: o hash , o comprimento do código e o passo de tempo. Precisam coincidir em ambos os lados ou os códigos nunca vão concordar. Para (Senha de Uso Único Baseada em HMAC, do inglês HMAC-based One-Time Password), um parâmetro counter aparece no lugar de
period.
Por que base32, e por que um QR code
O segredo é base32 em vez de bytes crus ou base64 porque o base32 usa apenas letras maiúsculas e dígitos, evitando caracteres que são ambíguos ou incômodos, o que importa na rara ocasião em que alguém digita a chave à mão em vez de escanear. O (Resposta Rápida, do inglês Quick Response) code é simplesmente essa URI inteira codificada como imagem: escaneá-lo entrega ao app cada campo de uma vez, que é por que a configuração é normalmente "escaneie este código" em vez de copiar uma string. A chave de entrada manual que um site oferece como alternativa é o mesmo segredo base32 da URI, digitado à mão.
A pegadinha do provisionamento
Como algorithm, digits e period são todos parte da URI, uma incompatibilidade é uma falha clássica de configuração: um servidor usando ou um código de 8 dígitos ou um period fora do padrão só interoperará com um app que recebeu esses parâmetros exatos. A maioria dos apps assume os padrões comuns (SHA-1, 6 dígitos, 30 segundos) quando um campo é omitido, então se um servidor silenciosamente usa algo diferente sem colocá-lo na URI, o app gera códigos que nunca validam. Quando o enrollment "tem sucesso" mas todo código é rejeitado, um parâmetro que não chegou à URI de provisionamento é a causa usual.
O QR code é o segredo, numa forma que as pessoas esquecem ser segredo
Uma URI otpauth:// contém o segredo compartilhado em texto claro. A imagem do QR é uma codificação dessa URI, o que torna a figura uma credencial — tão sensível quanto uma senha e muito mais provável de ser tratada com descuido.
As consequências são inteiramente previsíveis e acontecem o tempo todo: capturas de tela de cadastro coladas em chamados, salvas em drives compartilhados ou enviadas por chat para ajudar alguém que não conseguiu escanear. Cada uma entrega um segundo fator permanentemente, porque, diferente de uma senha, ninguém rotaciona um segredo TOTP depois de um incidente que nunca percebeu.
Mostre o segredo uma vez, nunca registre a URI em log, e trate qualquer captura de tela da tela de cadastro como um fator comprometido. A experiência de "deixa que eu te mando o código" é o ataque.