scanner

Nos sistemas atuais é comum ter a funcionalidade de autenticação de usuário, que trazem algumas formas de proteger as senhas cadastradas, se utilizando de tecnologia de criptografia para guardar a informação de forma segura. Essas informações são salvas em bancos de dados que se utilizam de algoritmos de criptografia como: md5, sha1, sha256, sha512, entre outros. Esses algoritmos permitem identificar se a senha fornecida corresponde a que está salva no sistema. Logo abaixo temos um caso de uso para um sistema que autentica o seu usuário:

  1. Usuário informa o seu email e senha;
  2. O sistema verifica se o email está cadastrado;
  3. O sistema gera um hash da senha informada pelo o usuário;
  4. O sistema verifica o hash gerado no passo anterior é igual o hash cadastrado no banco;
  5. O sistema informa se o usuário foi encontrado ou não, com base na comparação dos dados.

O passo três é o que nos interessa nesse processo, pois varios sistemas geram hash para proteger as senhas e dados sensíveis de atos criminosos. Mas se o banco de dados for comprometido e exposto na internet o hash de senha dos usuários ficará aberto para consulta, e com isso poderemos analisar as listas disponíveis.

No passo quatro, geralmente usado nos sistemas atuais, é verificada a senha do usuário, fazendo a comparação das criptografias, caso sejam iguais, o mesmo libera o acesso com uma simples comparação de string.

Segue o caso de uso abaixo:

  • O usuário tem uma senha cadastrada no sistema, cuja o valor é “123456”;
  • O hash dessa senha criptografada com algoritmo de MD5 “e10adc3949ba59abbe56e057f20f883e”.

Analisando os passos acima, se a senha transformada em hash vazar temos um criptografia que não condiz com o valor de “123456”, neste caso podemos fazer o inverso para identiticar qual senha é contida nesse hash. Para isso vamos realizar um ataque de força bruta para identificar se hash está contido na lista que iremos criar. Faremos uma lista que contém as seguintes senhas:


package main

import "fmt"

func main(){
  // lista de senha
  lista := []string{"123", "1234", "321", "4321", "12345", "54321", "123456", "654321"}
  fmt.Println(lista)
}

Na lista acima as senha não estão criptografada e com isso partimos para a segunda parte que seria criptografar os dados, segue a abaixo:

package main

import (
    "fmt"
    // pacote para criptografia em md5
    "crypto/md5"
)

// função para criptografar o texto limpo
func createHash(key string) string {
  // iniciando o modulo de md5
	hasher := md5.New()
  // transformando a string para byte e escrevendo o hash
	hasher.Write([]byte(key))
  // retornando o hash em md5
	return hex.EncodeToString(hasher.Sum(nil))
}


func main() {
  // senha que gostaria de identificar
  senha := "e10adc3949ba59abbe56e057f20f883e"
  // lista de senhas
  lista := []string{"123", "1234", "321", "4321", "12345", "54321", "123456", "654321"}
  // iterando nas senhas
  for _, v := range lista {
    // verificando se o hash da senha é igual o que estou querendo descobrir
    if createHash(v) == senha {
      // caso o hash seja valido
      fmt.Println("senha é: ", v)
      // parando a iteração
      break
    }
  }
}

Analisando o código acima temos um problema, que é ter uma lista bem definida para que possamos achar a senha informada. E sabemos que isso é bem complicado porque existem inumeras combinações de senhas que teriamos que gerar para comparar com o nosso hash. Pensando nessa problemática temos varios sites que fazem esse trabalho, como: md5hashing, hashes, dcode e hashtoolkit.

Mas a intenção não é usar uma ferramenta que já existe, e sim criar uma do zero. E para isso temos que elaborar um arquivo que tenha uma lista composta com várias senhas, e essas senhas devem ser mais assertiva e não randômicas, pois é mais demorado e oneroso.

Para compor esse arquivo existem senhas comuns, ou que foram expostas na internet, e com isso podemos reutiliza-las aqui para identificar o nosso hash. Esses são alguns sites que podemos encontrar senhas vazadas na internet em um arquivo de texto,segue os links abaixo:

Analisando a lista acima podemos iniciar o nosso software para encontrar a senha que está em hash de md5 para fazer a comparação. Neste caso, temos o seguinte código abaixo:


package main

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"os"
)

func createHash(key string) string {
	// iniciando o modulo de md5
	hasher := md5.New()
	// transformando a string para byte e escrevendo o hash
	hasher.Write([]byte(key))
	// retornando o hash em md5
	return hex.EncodeToString(hasher.Sum(nil))
}

func main() {
	// lendo o diciário das senhas
	file, err := os.Open("dictionary.txt")

	// a senha para comparação
	password := "e10adc3949ba59abbe56e057f20f883e"

	// comparação se houver erro na leitura do dicionário
	if err != nil {
		panic(err)
	}

	// lendo o arquivo em
	fileScanner := bufio.NewScanner(file)

	// iterando
	for fileScanner.Scan() {
		// comparando a senha com o hash gerado
		if createHash(fileScanner.Text()) == password {
			// caso caia aqui senha encontrada
			fmt.Println("a senha é: ", fileScanner.Text())
		}
	}

	// fechando a conexão com o arquivo.
	defer file.Close()

}

Esse código lê o arquivo chamado de dictionary.text com todas as senhas baixadas dos sites aqui informados, esse arquivo pode ser gigante e assim teremos uma nova problemática de performance. Neste caso podemos dividir esse arquivo em varios outros e fazer a leitura usando concorrencia com goroutine e canais, como no exemplo abaixo:


package main

import (
	"bufio"
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"os"
)

func createHash(key string) string {
	// iniciando o modulo de md5
	hasher := md5.New()
	// transformando a string para byte e escrevendo o hash
	hasher.Write([]byte(key))
	// retornando o hash em md5
	return hex.EncodeToString(hasher.Sum(nil))
}

func goRoutines(password, v string, passwordFound chan<- string) {

	// abrindo o arquivo informado
	file, err := os.Open(v)

	// caso não consiga encontrar o arquivo
	if err != nil {
		// informando o erro
		panic(err)
	}

	// lendo o arquivo aberto
	fileScanner := bufio.NewScanner(file)

	// iterando as linhas dos arquivos
	for fileScanner.Scan() {
		// validando o hash é igual a senha passado
		if createHash(fileScanner.Text()) == password {
			// pegando a senha encontrada e passando para o canal
			passwordFound <- fileScanner.Text()
			// parando a iteração
			break
		}
	}
	// fechando o arquivo
	defer file.Close()

}

func main() {
	// a senha para comparação
	password := "e10adc3949ba59abbe56e057f20f883e"

	// lista de arquivos
	listFiles := []string{"dictionary1.txt", "dictionary2.txt"}

	// criando um canal caso a senha seja encontrada
	passwordFound := make(chan string)

	// iterando na lista de arquivos
	for _, v := range listFiles {
		// criando goroutines para comparação de cada lista
		go goRoutines(password, v, passwordFound)
	}

	// informando a senha que está valida.
	fmt.Println("Senha encontrada: ", <-passwordFound)
}

No código acima temos uma hash decrypt com algoritmo de MD5 que verifica cada item da nossa lista de forma concorrente, com o propósito de identificar a senha. E para melhorias futuras podemos utilizar outras criptografias e até descobrir qual criptografia está no hash.

Espero ter ajudado, até próxima!

Referências

https://www.linkedin.com/pulse/goroutines-e-concorr%C3%AAncia-jefferson-otoni-lima/?originalSubdomain=pt

https://pkg.go.dev/crypto/md5