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:
- Imports: The code imports necessary packages for cryptography, logging, random number generation, file handling, and concurrency.
- Global Variables: Variables like
found
, which indicates if the correct password has been found, andmu
, a mutex for thread-safe access to shared variables. - Initialization: The
init
function is used to set up the logging format and seed the random number generator. - Password Generation: The
generateRandomPassword
function creates a random password of a specified length with constraints on the number of digits. - Brute-Force Logic: The
bruteForceSHA256
function attempts to generate passwords, hash them, and compare them to the target hash. - File Handling: The application saves the cracked password to a file for future reference.
- 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