1

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).

JimB
  • 104,193
  • 13
  • 262
  • 255
  • 1
    [Concurrency is not Parallelism](https://youtu.be/cN_DpYBzKso), and the latter is what you want to mount a brute force in a reasonable amount of time. – Markus W Mahlberg Jan 13 '18 at 08:05

1 Answers1

0

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.

Grzegorz Żur
  • 47,257
  • 14
  • 109
  • 105
  • The `main` returns after the first value is sent through `out`. It makes sense, but the `stop` goroutine becomes completely useless as it won't block anything. Maybe you should not use it without`go`. – leaf bebop Jan 13 '18 at 08:35
  • If the program does not find password then it would block forever at reading from the `out` channel. Function `stop` will wait untill all consumers are finished and then close the channel to signal there is nothing found. – Grzegorz Żur Jan 13 '18 at 13:51
  • Ah I did not think that. You are right. – leaf bebop Jan 13 '18 at 13:52
  • 1
    Just wanted to say thank you for this. I implemented the concurrent version of my program and there was a +300% improvement in execution time! – Frank Jaeger Jan 25 '18 at 17:43