Search code examples
loopsgoconcurrencycrypt

Using concurrency in nested for loop? (Brute force)


I'm adapting code from a C program I made in class & I'm trying to convert all the programs I've written in C into Go in order to learn the language. I'm not quite "getting" concurrency yet though. How would I apply concurrency to a nested for loop? The current iteration of my program is SLOW, much slower than if i would have written in C.

Here is my code:

package main

import (
    "fmt"
    "os"
    "unsafe"
)

// #cgo LDFLAGS: -lcrypt
// #define _GNU_SOURCE
// #include <crypt.h>
// #include <stdlib.h>
import "C"

// credit for this solution goes to https://stackoverflow.com/questions/14109915/what-is-gos-equivalent-to-pythons-crypt-crypt
// for showing that you can wrap go in C via CGO
// crypt wraps C library crypt_r
func crypt(key, salt string) string {
    data := C.struct_crypt_data{}
    ckey := C.CString(key)
    csalt := C.CString(salt)
    out := C.GoString(C.crypt_r(ckey, csalt, &data))
    C.free(unsafe.Pointer(ckey))
    C.free(unsafe.Pointer(csalt))
    return out
}

func main() {
    if len(os.Args) != 2 {
        fmt.Println("Usage: ./cracker k")
        return
    }
    cipher := os.Args[1]
    var guess [5]byte
    for i := 65; i < 123; i++ {
        if i >= 91 && i <= 96 {

        } else {
            guess[0] = byte(i)
            if cipher == crypt(string(guess[:1]), "50") {
                fmt.Println(string(guess[:1]))
                return
            }
            fmt.Println(string(guess[:1]))
            for j := 65; j < 123; j++ {
                if j >= 91 && j <= 96 {
                } else {
                    guess[1] = byte(j)
                    if cipher == crypt(string(guess[:2]), "50") {
                        fmt.Println(string(guess[:2]))
                        return
                    }
                    fmt.Println(string(guess[:2]))
                    for k := 65; k < 123; k++ {
                        if k >= 91 && k <= 96 {
                        } else {
                            guess[2] = byte(k)
                            if cipher == crypt(string(guess[:3]), "50") {
                                fmt.Println(string(guess[:3]))
                                return
                            }
                            fmt.Println(string(guess[:3]))
                            for l := 65; l < 123; l++ {
                                if l >= 91 && l <= 96 {
                                } else {
                                    guess[3] = byte(l)
                                    if cipher == crypt(string(guess[:4]), "50") {
                                        fmt.Println(string(guess[:4]))
                                        return
                                    }
                                    fmt.Println(string(guess[:4]))
                                    for m := 65; m < 123; m++ {
                                        if m >= 91 && m <= 96 {
                                        } else {
                                            guess[4] = byte(m)
                                            if cipher == crypt(string(guess[:5]), "50") {
                                                fmt.Println(string(guess[:5]))
                                                return
                                            }
                                            fmt.Println(string(guess[:5]))
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}

The purpose of the program is to brute force a DES hash, it will work on any password up to 5 characters in length (thus, 5 nested for loops).


Solution

  • I will not rewrite your code but I can give you a sample solution so you can fill in blanks.

    package main
    
    import (
        "fmt"
        "runtime"
        "sync"
    )
    
    func produce(in chan string) {
        defer close(in)
        for i := 0; i < 1000; i++ {
            in <- fmt.Sprintf("%d", i)
        }
    }
    
    func consume(in, out chan string, wg *sync.WaitGroup) {
        defer wg.Done()
        for s := range in {
            if s == "500" {
                out <- s
            }
        }
    }
    
    func stop(out chan string, wg *sync.WaitGroup) {
        wg.Wait()
        close(out)
    }
    
    func main() {
        in, out := make(chan string), make(chan string)
        wg := &sync.WaitGroup{}
        go produce(in)
        for i := 0; i < runtime.NumCPU(); i++ {
            wg.Add(1)
            go consume(in, out, wg)
        }
        go stop(out, wg)
        fmt.Println(<-out)
    }
    

    The idea is to produce password proposals in producer and push heavy computation into consumers. There will be as many consumers as CPUs. Producer signals end of work by closing in channel. Consumers pick up the next password proposal form the in channel and try computing the hash. If they succeed they put the password into the out channel.

    Stopping the program is a little bit tricky. We need a stoping routine to wait for either all consumers or for the cracked password.