Entendendo TOTP

newton - Sep 2 - - Dev Community

Com o aumento dos ataques cibernéticos, torna-se cada vez mais necessário adotar ferramentas e hábitos que dificultem o acesso de pessoas mal-intencionadas aos seus dados. Uma estratégia amplamente utilizada é a autenticação de dois fatores (2FA). Hoje, vamos explorar mais a fundo uma das diversas alternativas de segundo fator disponíveis no mercado: o TOTP.

O que é TOTP?

TOTP é a sigla para Time-Based One-Time Password Algorithm, um algoritmo utilizado para gerar senhas únicas baseadas no tempo. De acordo com a RFC 6238, ele é uma extensão do algoritmo HMAC-based One-Time Password (HOTP) descrito na RFC 4226, que também gera senhas seguras e únicas, mas é baseado em um contador, e não no tempo.

Como funciona o algoritmo?

Image description
Como mencionamos, o TOTP é uma extensão do HOTP, então, para entendê-lo melhor, vamos primeiro compreender como o HOTP funciona. O HOTP segue os passos abaixo, de forma simplificada, para gerar uma senha de uso único:

  • O servidor gera uma chave secreta para cada usuário.

  • O usuário armazena essa chave em um dispositivo.

  • A cada nova senha gerada, o contador é incrementado.

A fórmula pode ser definida da seguinte maneira:

HOTP(K, C) = Truncate(HMAC-SHA-1(K, C))

onde:

  • K é a chave secreta.
  • C é o contador, que deve estar sincronizado entre o servidor e o cliente.
  • Truncate é uma função que converte o resultado do hash em um número amigável para o usuário.

Como discutimos anteriormente, a principal diferença entre o HOTP e o TOTP é o uso do tempo. A fórmula para o TOTP pode ser definida como:

TOTP = HMAC(K, T)

onde T representa o tempo. O tempo é calculado usando a seguinte fórmula:

T = floor(Current Unix time / step)

eu-sou-a-velocidade
Mas o que é o step? Bem, não somos rápidos como o Relâmpago McQueen para gerar um código e inseri-lo no servidor no mesmo segundo. Portanto, é importante ter uma janela de tempo durante a qual o usuário possa gerar uma senha e inseri-la no servidor. Um valor comum para esse intervalo é de 30 segundos.

A função Truncate é um pouco mais complexa, mas o algoritmo está detalhado na seção 5.4 da RFC 4226. Em termos simplificados, os passos são:

  • Obter um offset, que é definido pelos 4 últimos bits do último byte do hash gerado.
  • Extrair 31 bits a partir do offset.
  • Realizar uma operação de módulo para obter o valor amigável.

Exemplo de implementação em Elixir

defmodule TOTP do
  @duration 30
  @digit_count 6

  import Bitwise

  @spec generate_secret() :: binary()
  def generate_secret do
    :crypto.strong_rand_bytes(20) |> Base.encode32
  end

  @spec generate_totp(binary()) :: binary()
  def generate_totp(key) do
    {:ok, key} = Base.decode32(key)
    key
    |> hmac(calculate_T())
    |> truncate()
  end

  @spec valid?(binary(), any()) :: boolean()
  def valid?(secret, otp, since \\ nil),
    do: otp == generate_totp(secret) and not has_been_used?(since)

  #RFC4226 https://datatracker.ietf.org/doc/html/rfc4226#section-5.4
  defp truncate(hash) do
    # getting the last byte and masking it so we get a valid index
    offset = :binary.last(hash) &&& 0x0F
    # Extract 4 bytes starting from the calculated offset
    <<_::binary-size(offset), extracted_value::integer-size(32), _rest::binary>> = hash

    # Ensure the extracted value is positive
    extracted_positive_value = extracted_value &&& 0x7FFFFFFF

    # taking this number module to get the 06 digits
    otp = rem(extracted_positive_value, :math.pow(10, @digit_count) |> round())

    formatted_otp =
      otp
      |> Integer.to_string()
      |> String.pad_leading(6, "0")

    formatted_otp
  end

  defp hmac(key, t) do
    :crypto.mac(:hmac, :sha, key, t)
  end

  defp calculate_T(), do: <<Integer.floor_div(now_unix(), @duration)::64>>

  defp has_been_used?(nil), do: false

  defp has_been_used?(since),
    do: Integer.floor_div(now_unix(), @duration) <= Integer.floor_div(since, @duration)

  defp now_unix(), do: DateTime.utc_now() |> DateTime.to_unix()
end
Enter fullscreen mode Exit fullscreen mode

Nessa implementação, estamos seguindo os passos mencionados anteriormente. Temos 3 funções principais:

  • TOTP.generate_secret/0: Gera uma chave secreta para o usuário. Essa chave deve ser armazenada de forma segura pelo servidor para validar a solução gerada pelo usuário. Do lado do usuário, a chave deve ser importada em algum aplicativo no dispositivo, como o Google Authenticator. Para evitar que o usuário insira manualmente a chave, uma solução comum é permitir que ele escaneie um QR code.

  • TOTP.generate_totp/1: Recebe a chave do usuário e aplica o algoritmo que aprendemos anteriormente. Calcula o hash com base no tempo e formata o hash para um valor amigável.

  • TOTP.valid?/2: Recebe o código inserido pelo usuário e o confronta com o código gerado pelo servidor.

Conclusão

Podemos observar que o custo para realizar a autenticação de dois fatores utilizando TOTP é baixo comparado a outras formas como SMS, email e afins, que além de terem o custo para o envio da notificação, apresentam a possibilidade de interceptações durante o processo, além de exigir conexão com a internet ou sinal telefônico. Já o TOTP pode ser gerado pelo dispositivo, por exemplo em um celular, mesmo que ele não tenha nenhum desses requisitos.

. .
Terabox Video Player