0
\$\begingroup\$

I made a SHA256 hash bruteforcer as my first program in Rust. It's very specific to what I'm trying to bruteforce and I'll probably never find it.

I translated it from Python to make it faster, but can I make it even faster than this?

// Disable snake case warning
#![allow(non_snake_case)]

extern crate crypto;
extern crate rand;

use self::crypto::digest::Digest;
use self::crypto::sha2::Sha256;
use std::{i64, fs, process};
use std::time::Instant;
use rand::Rng;

fn main() {
   // Local variables
   let listWin = ["black","green","orange","orange","orange","black","black","orange","black","black","black","orange","black","black","orange","orange","green","black","orange","black","black","orange","orange","black","black","black","orange","black","black","orange","orange","orange","orange","black","orange","black","black","orange","orange","black","black","orange","green","black","orange","orange","orange","orange","green","black"];
   let public_seed = "2132110516";
   let round_ = 4645230;

   let now = Instant::now();
   let mut nbOfTry: u32 = 0;
   let mut roundVerif = 0;
   let mut predict;

   loop {
      let randomSeed = &CreateSeed(32);
      predict = PredictColor(randomSeed, public_seed, round_);  

      // Check if the hash is correct by predicting the first 50 round of the day
      while predict == listWin[roundVerif] {
         roundVerif += 1;
         predict = PredictColor(randomSeed, public_seed, round_ + roundVerif as u32); 

         if roundVerif > 45 {
            fs::write("seed.txt", randomSeed).expect("Unable to write file");
            process::exit(1);
         }
      }
      roundVerif = 0;

      nbOfTry += 1;
      if nbOfTry % 1000000 == 0 {
         println!("{}", nbOfTry);
         println!("In {} secondes", now.elapsed().as_secs());
      }
   }
}

//---------------------------------------------------
// CreateSeed()
//---------------------------------------------------
// Description : Creates a random seed 
//
// Input      : Number of byte
// Output     : Random seed with a len of 2*nbByte
//---------------------------------------------------
fn CreateSeed(nbByte: u8) -> (String){
   let mut rng = rand::thread_rng();
   let mut seed = String::new();
   let mut byte: String;

   for _x in 0..nbByte {
      let rand = rng.gen::<u8>();
      byte = format!("{:x}", rand);   

      if rand <= 15 {byte = format!("0{}", byte);}
      seed = format!("{}{}", seed, byte);
   }
   return seed;
}


//---------------------------------------------------
// PredictColor()
//---------------------------------------------------
// Description : Predict the roll 
//
// Input      : server_seed : serveur seed
//              public_seed : public seed
//              round_      : First round of the day
// Output     : --
//---------------------------------------------------
fn PredictColor(server_seed: &str, public_seed: &str, round_: u32) -> (String){
   let toHash;
   let mut hasher = Sha256::new();

   // Hash everything
   toHash = format!("{}-{}-{}",server_seed, public_seed, round_ );
   hasher.input_str(&toHash[..]);
   let hash_ = hasher.result_str();

   // Get the roll number from the hash
   let decNumber = i64::from_str_radix(&hash_[..8], 16);
   let roll = decNumber.unwrap() % 15;

   // Print the color
   if roll == 0 {return "green".to_owned();}
   else if roll >= 1 && roll <= 7 {return "orange".to_owned();}
   else if roll >= 8 && roll <= 14 {return "black".to_owned();}
   else{return "failed".to_owned();}
}
\$\endgroup\$
5
  • 3
    \$\begingroup\$ Why did you decide to disable warnings? Idioms exist for a reason. \$\endgroup\$
    – Shepmaster
    Nov 4, 2019 at 20:33
  • 1
    \$\begingroup\$ See the tag info for sample steps you can take to improve your code already. \$\endgroup\$
    – Shepmaster
    Nov 4, 2019 at 20:34
  • 1
    \$\begingroup\$ How fast is your current code? How fast is the Python version? How are you running your code? Are you using release mode? Have you performed any profiling? How sure do you feel that your algorithm is a good choice? \$\endgroup\$
    – Shepmaster
    Nov 4, 2019 at 20:36
  • 1
    \$\begingroup\$ FWIW, I disagree with the close votes. The code appears to be complete and not pseudocode or similar. \$\endgroup\$
    – Shepmaster
    Nov 4, 2019 at 21:59
  • \$\begingroup\$ It would be good to include the exact versions of the crates you are using. \$\endgroup\$
    – Shepmaster
    Nov 4, 2019 at 21:59

1 Answer 1

6
\$\begingroup\$

Run and address feedback from automated tools to get easy feedback without having to make use of another human's time. Possible tools include:

  • The Rust compiler. Don't turn off warnings.
  • Rustfmt is a tool for automatically formatting Rust code to the community-accepted style.
  • Clippy is a tool for finding common mistakes that may not be compilation errors but are unlikely to be what the programmer intended.

These detect things like:

  • incorrect / inconsistent indentation.
  • unneeded usages of return
  • non-idiomatic Rust naming

Your variables have useless prefixes and suffixes. Don't blindly use Hungarian notation in a strongly typed language (e.g. nb). Dnt ndlssly abbv vrbs (don't needlessly abbreviate variables).

With this initial clearing of the underbrush, we can start to look at the code.

use crypto::{digest::Digest, sha2::Sha256};
use rand::Rng;
use std::{convert::TryInto, fs, process, time::Instant};

fn main() {
    // Local variables
    let winners = [
        "black", "green", "orange", "orange", "orange", "black", "black", "orange", "black",
        "black", "black", "orange", "black", "black", "orange", "orange", "green", "black",
        "orange", "black", "black", "orange", "orange", "black", "black", "black", "orange",
        "black", "black", "orange", "orange", "orange", "orange", "black", "orange", "black",
        "black", "orange", "orange", "black", "black", "orange", "green", "black", "orange",
        "orange", "orange", "orange", "green", "black",
    ];
    let public_seed = "2132110516";
    let round = 4_645_230;

    let now = Instant::now();

    for tries in 0.. {
        let random_seed = &create_seed(32);
        let mut predict = predict_color(random_seed, public_seed, round).unwrap();
        let mut round_verifications = 0u8;

        // Check if the hash is correct by predicting the first 50 round of the day
        while predict == winners[usize::from(round_verifications)] {
            round_verifications += 1;
            predict = predict_color(random_seed, public_seed, round + u32::from(round_verifications))
                .unwrap();

            if round_verifications > 45 {
                fs::write("seed.txt", random_seed).expect("Unable to write file");
                process::exit(1);
            }
        }

        if tries % 1_000_000 == 0 {
            println!("{}", tries);
            println!("In {} seconds", now.elapsed().as_secs());

            if tries == 5000000 { return }
        }
    }
}

/// Creates a random seed with a length of `2 * bytes`
fn create_seed(bytes: usize) -> String {
    let mut rng = rand::thread_rng();
    let mut seed = String::with_capacity(2 * bytes);

    for _ in 0..bytes {
        use std::fmt::Write;
        write!(&mut seed, "{:02x}", rng.gen::<u8>()).unwrap();
    }
    seed
}

/// Predict the roll
fn predict_color(
    server_seed: &str,
    public_seed: &str,
    first_round_of_the_day: u32,
) -> Result<&'static str, ()> {
    let mut hasher = Sha256::new();

    // Hash everything
    let to_hash = format!("{}-{}-{}", server_seed, public_seed, first_round_of_the_day);
    hasher.input_str(&to_hash);

    let mut hash = [0; 32];
    hasher.result(&mut hash);

    let leading_bytes: &[u8; 4] = hash[..4].try_into().unwrap();
    let number = u32::from_be_bytes(*leading_bytes);

    match number % 15 {
        0 => Ok("green"),
        1..=7 => Ok("orange"),
        8..=14 => Ok("black"),
        _ => Err(()),
    }
}

Running this in release mode:

% time ./target/release/review
0
In 0 seconds
1000000
In 2 seconds
2000000
In 4 seconds
3000000
In 7 seconds
4000000
In 9 seconds
5000000
In 11 seconds

real    12.078  12077581us
user    11.879  11879005us
sys 0.033   33467us
cpu 98%
mem 968 KiB

Compared to your original:

% time ./target/release/review
1000000
In 14 secondes
2000000
In 28 secondes
3000000
In 42 secondes
4000000
In 56 secondes
5000000
In 70 secondes

real    1:10.49 70485131us
user    1:10.16 70162635us
sys 0.137   136869us
cpu 99%
mem 980 KiB

The improved version takes 17% of your original time (a 5.8x speed increase).


Using a random number generator for the seed is probably a bad idea. It's totally reasonable for a RNG to repeat a value, but that won't change your results. You might as well start at 0 and work your way up. This also makes it trivial to stop and resume.

The problem appears to be embarrassingly parallel, so that would also be an avenue to investigate.

\$\endgroup\$
6
  • \$\begingroup\$ Thanks for your really nice answer, I'll have a lot of things to read with all the link you sent. For my function documentation, it's what my school wants me to do, so that's what I'm used to. It's when I'll write librairies in C, the documentation will have to be very clear for the user. I originaly thought of working my way up, but the seed changes everyday, which makes it nearly impossible to crack. The seed looks like that : "a3bbba746fa85f6305ee3873b6d267d9872eceb6c6174e33075d73a5259b4cb6" \$\endgroup\$
    – Nicolas
    Nov 5, 2019 at 18:13
  • \$\begingroup\$ the maximum value is then : "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" In decimal : 2^(64*4) = 115792089237316195423570985008687907853269984665640564039457584007913129639936 I think my calculs are correct. I guess randomly guessing might be better because I won't do half that number in a day. I tried running your code, it looks like you're using 98% of your CPU. When I run it, it uses a maximum of 9% of my cpu. How can I make it run full speed ? Sorry for the bad formating, it's the first time I'm using this site. \$\endgroup\$
    – Nicolas
    Nov 5, 2019 at 18:14
  • \$\begingroup\$ Randomly guessing will never be better though. The RNG might return zero 100 times in a row, computing the same (failed) value again. There's no information you can make use about being "close" that might make jumping around the search space have a benefit. Jumping around randomly won't make you faster (in fact, it's probably slower). \$\endgroup\$
    – Shepmaster
    Nov 5, 2019 at 18:38
  • \$\begingroup\$ The 98% number reported by my shell is of a single core, I believe. Your number might be out of your total CPU; do you have an 8-, 10- or 12-core processor by any chance? \$\endgroup\$
    – Shepmaster
    Nov 5, 2019 at 18:40
  • \$\begingroup\$ I have a AMD Ryzen 5 2600. 6 Cores, 12 Logical processors \$\endgroup\$
    – Nicolas
    Nov 5, 2019 at 18:42

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.