Brute-Forcing SHA-256 Hashes in Go: A Comprehensive Guide

Overview

In the world of cybersecurity, brute-forcing hashes can be both an educational and practical exercise, especially for understanding password security and hash functions. In this post, we will explore a Go application that brute-forces a given SHA-256 hash by generating random passwords and checking their hash against a target. We’ll break down the code, explain its functionality, and discuss the implications of this technique in real-world applications.

Understanding the Code Structure

The Go application consists of several key components:

  1. Imports: The code imports necessary packages for cryptography, logging, random number generation, file handling, and concurrency.
  2. Global Variables: Variables like found, which indicates if the correct password has been found, and mu, a mutex for thread-safe access to shared variables.
  3. Initialization: The init function is used to set up the logging format and seed the random number generator.
  4. Password Generation: The generateRandomPassword function creates a random password of a specified length with constraints on the number of digits.
  5. Brute-Force Logic: The bruteForceSHA256 function attempts to generate passwords, hash them, and compare them to the target hash.
  6. File Handling: The application saves the cracked password to a file for future reference.
  7. Concurrency: The program uses goroutines to enhance performance by concurrently attempting to crack the password.

Code Breakdown

Let’s take a closer look at the code and discuss its components in detail.

 1package main
 2
 3import (
 4    "crypto/sha256"
 5    "encoding/hex"
 6    "fmt"
 7    "log"
 8    "math/rand"
 9    "os"
10    "strings"
11    "sync"
12    "time"
13)

The code starts by importing necessary packages. The crypto/sha256 package is used to generate SHA-256 hashes, encoding/hex is for hexadecimal encoding, and sync provides synchronization mechanisms.

Global Variables and Initialization

1var (
2    found = false    // Flag to stop all goroutines once a match is found
3    mu    sync.Mutex // Mutex for safe access to shared variables
4)
5
6func init() {
7    log.SetFlags(0)                  // Simplify log output
8    rand.Seed(time.Now().UnixNano()) // Seed RNG with current time
9}

Here, we define a found flag that indicates whether a valid password has been discovered, and a mutex mu for managing concurrent access to this flag. The init function sets up the logger and seeds the random number generator to ensure different results on each execution.

Password Generation Function

1func generateRandomPassword(length, minDigits, maxDigits int) string {
2    numDigits := rand.Intn(maxDigits-minDigits+1) + minDigits
3    numLetters := length - numDigits
4    // Ensure at least one letter and one digit
5    ...
6}

The generateRandomPassword function is responsible for creating a random password that meets specific criteria, such as length and minimum/maximum digits. It generates the required number of letters and digits, shuffles the resulting string, and fills in any remaining characters if needed.

Brute-Force Function

 1func bruteForceSHA256(targetHash string, attemptsChan chan<- int) {
 2    attempts := 0
 3    for {
 4        mu.Lock()
 5        if found {
 6            mu.Unlock()
 7            return
 8        }
 9        mu.Unlock()
10
11        guess := generateRandomPassword(64, 18, 40) // Fixed length of 64
12        hashedGuess := sha256.Sum256([]byte(guess))
13        hashedGuessHex := hex.EncodeToString(hashedGuess[:])
14        
15        attempts++
16        attemptsChan <- 1 // Increment count by 1
17
18        if hashedGuessHex == targetHash {
19            mu.Lock()
20            found = true
21            mu.Unlock()
22            fmt.Printf("Match found after %d attempts! Password: %s\n", attempts, guess)
23            if err := savePassword(guess); err != nil {
24                log.Printf("Error saving password: %v\n", err)
25            }
26            return
27        }
28    }
29}

The bruteForceSHA256 function runs an infinite loop to generate and test passwords. It locks the mutex to check if a valid password has been found, generates a new password, hashes it, and compares it to the target hash. If a match is found, it saves the password and prints the result.

Saving the Cracked Password

 1func savePassword(password string) error {
 2    file, err := os.OpenFile("cracked_password.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
 3    if err != nil {
 4        return fmt.Errorf("failed to open file: %w", err)
 5    }
 6    defer file.Close()
 7
 8    if _, err := file.WriteString(password + "\n"); err != nil {
 9        return fmt.Errorf("failed to write to file: %w", err)
10    }
11
12    fmt.Printf("Decrypted value: %s\n", password)
13    fmt.Println("Cracked password saved to 'cracked_password.txt'")
14    return nil
15}

The savePassword function is responsible for saving the cracked password to a text file. It opens (or creates) the file, writes the password, and handles any errors that may occur during this process.

Main Function

 1func main() {
 2    targetHash := "Your Hash"
 3    numGoroutines := 4 // Number of concurrent goroutines
 4
 5    fmt.Println("Starting SHA-256 brute-force...")
 6
 7    attemptsChan := make(chan int, 100) // Buffered channel
 8    var wg sync.WaitGroup
 9
10    // Start worker goroutines
11    for i := 0; i < numGoroutines; i++ {
12        wg.Add(1)
13        go func() {
14            defer wg.Done()
15            bruteForceSHA256(targetHash, attemptsChan)
16        }()
17    }
18
19    // Monitor attempts
20    go func() {
21        totalAttempts := 0
22        for a := range attemptsChan {
23            totalAttempts += a
24            if totalAttempts%10000 == 0 {
25                fmt.Printf("Total Attempts: %d\n", totalAttempts)
26            }
27        }
28    }()
29
30    wg.Wait()
31    close(attemptsChan) // Close channel when done
32    fmt.Println("Brute-force operation complete.")
33}

In the main function, the program sets the target hash and initializes the number of goroutines. It starts multiple goroutines to run the brute-force function concurrently and monitors the number of attempts. Once the brute-forcing is complete, it closes the channel and prints a completion message.

Implications and Ethical Considerations

While this code serves as an excellent learning tool for understanding password security and hashing, it’s essential to remember the ethical implications of brute-forcing passwords. Brute-forcing should only be performed on systems you own or have explicit permission to test. Unauthorized access to systems can lead to legal consequences and is against ethical hacking practices.

Full Code

  1package main
  2
  3import (
  4	"crypto/sha256"
  5	"encoding/hex"
  6	"fmt"
  7	"log"
  8	"math/rand"
  9	"os"
 10	"strings"
 11	"sync"
 12	"time"
 13)
 14
 15var (
 16	found = false    // Flag to stop all goroutines once a match is found
 17	mu    sync.Mutex // Mutex for safe access to shared variables
 18)
 19
 20func init() {
 21	log.SetFlags(0)                  // Simplify log output
 22	rand.Seed(time.Now().UnixNano()) // Seed RNG with current time
 23}
 24
 25// Generates a random password of fixed length (64) with specified digit constraints
 26func generateRandomPassword(length, minDigits, maxDigits int) string {
 27	numDigits := rand.Intn(maxDigits-minDigits+1) + minDigits
 28	numLetters := length - numDigits
 29
 30	// Ensure at least one letter and one digit
 31	if numDigits > length {
 32		numDigits = length
 33		numLetters = 0
 34	} else if numLetters > length {
 35		numLetters = length
 36		numDigits = 0
 37	}
 38
 39	digits := randomString("0123456789", numDigits)
 40	letters := randomString("abcdefghijklmnopqrstuvwxyz", numLetters)
 41
 42	password := []rune(digits + letters)
 43	rand.Shuffle(len(password), func(i, j int) {
 44		password[i], password[j] = password[j], password[i]
 45	})
 46
 47	// Fill the remaining length with random characters (if any)
 48	if len(password) < length {
 49		additionalChars := randomString("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789", length-len(password))
 50		password = append(password, []rune(additionalChars)...)
 51		rand.Shuffle(len(password), func(i, j int) {
 52			password[i], password[j] = password[j], password[i]
 53		})
 54	}
 55
 56	return string(password)
 57}
 58
 59// Helper function to generate a random string from a given charset
 60func randomString(charset string, length int) string {
 61	sb := strings.Builder{}
 62	for i := 0; i < length; i++ {
 63		sb.WriteByte(charset[rand.Intn(len(charset))])
 64	}
 65	return sb.String()
 66}
 67
 68// Brute-forces a given SHA-256 hash
 69func bruteForceSHA256(targetHash string, attemptsChan chan<- int) {
 70	attempts := 0
 71	for {
 72		mu.Lock()
 73		if found {
 74			mu.Unlock()
 75			return
 76		}
 77		mu.Unlock()
 78
 79		guess := generateRandomPassword(64, 18, 40) // Fixed length of 64
 80		hashedGuess := sha256.Sum256([]byte(guess))
 81		hashedGuessHex := hex.EncodeToString(hashedGuess[:])
 82
 83		attempts++
 84		attemptsChan <- 1 // Increment count by 1
 85
 86		if hashedGuessHex == targetHash {
 87			mu.Lock()
 88			found = true
 89			mu.Unlock()
 90			fmt.Printf("Match found after %d attempts! Password: %s\n", attempts, guess)
 91			if err := savePassword(guess); err != nil {
 92				log.Printf("Error saving password: %v\n", err)
 93			}
 94			return
 95		}
 96	}
 97}
 98
 99// Saves the cracked password to a file
100func savePassword(password string) error {
101	file, err := os.OpenFile("cracked_password.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
102	if err != nil {
103		return fmt.Errorf("failed to open file: %w", err)
104	}
105	defer file.Close()
106
107	if _, err := file.WriteString(password + "\n"); err != nil {
108		return fmt.Errorf("failed to write to file: %w", err)
109	}
110
111	fmt.Printf("Decrypted value: %s\n", password)
112	fmt.Println("Cracked password saved to 'cracked_password.txt'")
113	return nil
114}
115
116func main() {
117	targetHash := "Your Hash"
118	numGoroutines := 4 // Number of concurrent goroutines
119
120	fmt.Println("Starting SHA-256 brute-force...")
121
122	attemptsChan := make(chan int, 100) // Buffered channel
123	var wg sync.WaitGroup
124
125	// Start worker goroutines
126	for i := 0; i < numGoroutines; i++ {
127		wg.Add(1)
128		go func() {
129			defer wg.Done()
130			bruteForceSHA256(targetHash, attemptsChan)
131		}()
132	}
133
134	// Monitor attempts
135	go func() {
136		totalAttempts := 0
137		for a := range attemptsChan {
138			totalAttempts += a
139			if totalAttempts%10000 == 0 {
140				fmt.Printf("Total Attempts: %d\n", totalAttempts)
141			}
142		}
143	}()
144
145	wg.Wait()
146	close(attemptsChan) // Close channel when done
147	fmt.Println("Brute-force operation complete.")
148}

Conclusion

Brute-forcing a SHA-256 hash in Go can be an enlightening experience, showcasing the power of concurrency and random password generation. By understanding how this code works, developers can gain insights into password security and the importance of using strong, unique passwords. This code serves as a foundation for further exploration into cybersecurity and cryptography.

Whether you’re a beginner or an experienced developer, experimenting with this brute-forcing technique can enhance your programming skills and broaden your knowledge of secure coding practices.

Comments

Related