Autenticação de dois fatores
O Telegram usa o protocolo Secure Remote Password versão 6a para implementar 2FA.
Exemplo de implementação: tdlib .
Verificando a senha com SRP
Para fazer o login em uma conta protegida por uma senha 2FA ou para realizar outras ações (como alterar o proprietário do canal), você precisará verificar se o usuário sabe a senha atual da conta 2FA.
Para fazer isso, primeiro o cliente precisa obter os parâmetros SRP e o algoritmo KDF a ser usado para verificar a validade da senha por meio do método account.getPassword . Por enquanto, apenas o algoritmo passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow é compatível, então vamos apenas explicar isso.
Então, depois que o usuário fornece uma senha, o cliente deve gerar um objeto InputCheckPasswordSRP usando SRP e um algoritmo KDF específico conforme mostrado abaixo e passá-lo para o método apropriado (por exemplo, auth.checkPassword em caso de autorização).
Esta extensão do protocolo SRP usa o PBKDF2 baseado em senha com 100.000 iterações usando sha512 ( PBKDF2HMACSHA512iter100000
). PBKDF2 é usado para refazer o hash adicionalmente o x
parâmetro, obtido usando um método semelhante ao descrito em RFC 2945 (em H(s | H ( I | password | I) | s)
vez de H(s | H ( I | ":" | password)
) (veja abaixo).
Aqui, |
denota concatenação e +
denota o operador aritmético +
. Em todos os casos em que a concatenação de números passados para funções de hash é feita, os números devem ser usados na forma big-endian, preenchidos para 2.048 bits; toda matemática é módulo p
. Em vez de I
, salt1
será usado (consulte o protocolo SRP ). Em vez de s
, salt2
será usado (consulte o protocolo SRP ).
A principal função de hashing H
é sha256:
H(data) := sha256(data)
A função de hash de sal SH
é definida da seguinte forma:
SH(data, salt) := H(salt | data | salt)
A função de hash de senha principal é definida da seguinte maneira:
PH1(password, salt1, salt2) := SH(SH(password, salt1), salt2)
A função secundária de hash de senha é definida da seguinte maneira:
PH2(password, salt1, salt2) := SH(pbkdf2(sha512, PH1(password, salt1, salt2), salt1, 100000), salt2)
Do lado do cliente, os parâmetros a seguir são extraídos do objeto passwordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow , contido no objeto account.password .
g := algo.g
p := algo.p
Espera-se que o cliente verifique se p é um primo seguro de 2048 bits (o que significa que p e (p-1) / 2 são primos e que2^2047 < p < 2^2048
) e que g gera um subgrupo cíclico de ordem primo (p-1) / 2 , ou seja, é um resíduo quadrático mod p . Como g é sempre igual a 2, 3, 4, 5, 6 ou 7, isso é feito facilmente usando a lei de reciprocidade quadrática, resultando em uma condição simples em p mod 4g - a saber, p mod 8 = 7 para g = 2 ; p mod 3 = 2 para g = 3 ; nenhuma condição extra para g = 4 ; p mod 5 = 1 ou 4 parag = 5 ; p mod 24 = 19 ou 23 para g = 6 ; e p mod 7 = 3, 5 ou 6 para g = 7 . Depois que g e p forem verificados pelo cliente, faz sentido armazenar o resultado em cache, para evitar a repetição de cálculos longos no futuro. Este cache pode ser compartilhado com um usado para geração de chave de autorização .Se o cliente tiver um gerador de número aleatório inadequado, faz sentido usar o secure_random de account.password como seed adicional.
password := (user-provided password)
salt1 := algo.salt1
salt2 := algo.salt2
g_b := srp_B
srp_B
esrp_id
são extraídos do objeto account.password .
O k
parâmetro é gerado, tanto no cliente quanto no servidor:
k := H(p | g)
O parâmetro compartilhado u
é gerado: o cliente faz isso, e o servidor faz o mesmo com o g_a
enviaremos mais tarde (veja abaixo)
u := H(g_a | g_b)
Os parâmetros finais são gerados apenas do lado do cliente:
x := PH2(password, salt1, salt2)
v := pow(g, x) mod p
O servidor já o fez v
, desde quando definimos a senha.
Um parâmetro compartilhado final é gerado, para a mercadoria:
k_v := (k * v) mod p
Finalmente, o processo de troca de chaves começa em ambas as partes.
O cliente calcula um número de 2048 bits um (usando entropia suficiente ou o servidor de aleatória ; veja-se acima) e gera:
g_a := pow(g, a) mod p
.
O servidor calcula um número b de 2048 bits usando entropia suficiente e gera o g_b
parâmetro que nos foi enviado (veja acima).
g_b := (k_v + (pow(g, b) mod p)) mod p
Finalmente, as chaves de sessão SRP são geradas:
Lado do cliente:
t := (g_b - k_v) mod p
(módulo positivo, se o resultado for negativo, incremento emp
)s_a := pow(t, a + u * x) mod p
k_a := H(s_a)
Lado do servidor:
s_b := pow(g_a * (pow(v, u) mod p), b) mod p
k_b := H(s_b)
Desde a:
g_b := (k_v + (pow(g, b) mod p)) mod p
t := (g_b - k_v) mod p
t := ((k_v + (pow(g, b) mod p)) - k_v) mod p
t := pow(g, b) mod p
s_a := pow(t, a + u * x) mod p
s_a := pow(pow(g, b) mod p, a + u * x) mod p
E:
g_a := pow(g, a) mod p
v := pow(g, x) mod p
s_b := pow(g_a * (pow(v, u) mod p), b) mod p
s_b := pow((pow(g, a) mod p) * (pow(pow(g, x) mod p, u) mod p), b) mod p
s_b := pow(pow(g, a + x * u) mod p, b) mod p
s_b := pow(pow(g, b) mod p, a + u * x) mod p
s_a := pow(pow(g, b) mod p, a + u * x) mod p
Isso significa:
s_b === s_a
k_b === k_a
Finalmente, de acordo com SRP :
M1 := H(H(p) xor H(g) | H(salt1) | H(salt2) | g_a | g_b | k_a)
M1
é passado para inputCheckPasswordSRP , junto com g_a
(como A
parâmetro) e srp_id
extraído do objeto account.password .
O servidor então calcula:
M2 := H(H(p) xor H(g) | H(salt1) | H(salt2) | g_a | g_b | k_b)
Já que dissemos isso:
s_b === s_a
k_b === k_a
Isso significa que, se tudo foi feito corretamente,
M1 === M2
Se a senha não estiver correta, 400 PASSWORD_HASH_INVALID serão retornados.
Definir uma nova senha 2FA
Para definir uma nova senha 2FA, use o método account.updatePasswordSettings .
Se já houver uma senha definida, gere um objeto InputCheckPasswordSRP conforme a verificação de senhas com SRP e insira-o no password
campo do método account.updatePasswordSettings .
Para remover a senha atual, não defina nenhum sinalizador no objeto account.PasswordInputSettings .
Para definir uma nova senha, use os parâmetros SRP e o algoritmo KDF obtido usando account.getPassword ao gerar o password
campo. Em seguida, gere um novo new_password_hash
usando o algoritmo KDF especificado no new_settings
, basta anexar 32 bytes suficientemente aleatórios ao salt1
, primeiro. Proceda como para verificar as senhas com SRP , basta parar na geração do v
parâmetro e usá-lo como new_password_hash
:
v := pow(g, x) mod p
Como de costume, na forma big endian, preenchido para 2.048 bits.
Verificação de e-mail
Ao configurar a autorização de dois fatores, é recomendável configurar um e-mail de recuperação , para permitir a recuperação da senha através do endereço de e-mail do usuário, caso ele se esqueça.
Para configurar um e-mail de recuperação, primeiro ele deve ser verificado. Isso pode ser feito diretamente ao definir a nova senha usando account.updatePasswordSettings , definindo o parâmetro de email e o sinalizador no construtor account.passwordInputSettings . Se o e-mail não for verificado, um erro EMAIL_UNCONFIRMED_X 400 será retornado, onde X é o comprimento do código de verificação que acabou de ser enviado para o e-mail. Use account.confirmPasswordEmail para inserir o código de verificação recebido e habilitar o e-mail de recuperação. Use account.resendPasswordEmail para reenviar o código de verificação. Use account.cancelPasswordEmail para cancelar o código de verificação.
Para obter o e-mail de recuperação atual, use account.getPasswordSettings .
Recuperação de email
Para recuperar uma senha 2FA esquecida, um e-mail deve ser enviado para o endereço especificado anteriormente usando o método auth.requestPasswordRecovery . Em seguida, use auth.recoverPassword com o código recebido para excluir a senha 2FA atual, para definir uma nova, siga estas instruções.