Monday, September 26, 2022
HomeWordPress DevelopmentStudy Rust by implementing a SHA-1 hash cracker

Study Rust by implementing a SHA-1 hash cracker


Initially revealed on my weblog: https://kerkour.com/learning-rust-sha1-hash-cracker

The second has come to get your arms soiled: let’s write your first Rust program. As for all of the code examples on this course, you could find the whole code within the accompanying Git repository: https://github.com/skerkour/black-hat-rust

$ cargo new sha1_cracker
Enter fullscreen mode

Exit fullscreen mode

Will create a brand new challenge within the folder sha1_cracker.

Be aware that by default, cargo will create a binary (software) challenge. You possibly can create a library challenge with the --lib flag: cargo new my_lib --lib.

How a hash function works

SHA-1 is a hash perform utilized by lots of outdated web sites to retailer the passwords of the customers. In concept, a hashed password cannot be recovered from its hash. Thus by storing the hash of their database, an internet site can assert {that a} given consumer has the information of its password with out storing the password in cleartext, by evaluating the hashes. So if the web site’s database is breached, there isn’t a solution to get well the passwords and entry the customers’ information.

Actuality is sort of completely different. We could say a state of affairs the place we simply breached such an internet site, and we now wish to get well the credentials of the customers in an effort to acquire entry to their accounts. That is the place a “hash cracker” is beneficial. A hash cracker is a program that may strive many various hashes in an effort to discover the unique password.

Because of this when creating an internet site, you must use a hash perform particularly designed for this use case, akin to argon2id, which require far more useful resource to bruteforce than SHA-1, for instance.

This easy program will assist us be taught Rust’s fundamentals:

  • Tips on how to use Command Line Interface (CLI) arguments
  • Tips on how to learn information
  • Tips on how to use an exterior library
  • Fundamental error dealing with
  • Assets administration

This publish is an excerpt from my e book Black Hat Rust

Like in virtually all programming languages, the entrypoint of a Rust program is its principal perform.

ch_01/sha1_cracker/src/principal.rs

fn principal() {
    // ...
}
Enter fullscreen mode

Exit fullscreen mode

Studying command line arguments is as simple as:

ch_01/sha1_cracker/src/principal.rs

use std::env;

fn principal() {
    let args: Vec<String> = env::args().accumulate();
}
Enter fullscreen mode

Exit fullscreen mode

The place std::env imports the module env from the usual library and env::args() calls the args perform from this module and returns an iterator which could be “collected” right into a Vec<String>, a Vector of String objects. A Vector is an array kind that may be resized.

It’s then simple to verify for the variety of arguments and show an error message if it doesn’t match what is predicted.

ch_01/sha1_cracker/src/principal.rs

use std::env;

fn principal() {
    let args: Vec<String> = env::args().accumulate();

    if args.len() != 3 {
        println!("Utilization:");
        println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
        return;
    }
}
Enter fullscreen mode

Exit fullscreen mode

As you could have seen, the syntax of println! with an exclamation mark is unusual. Certainly, println! just isn’t a basic perform however a macro. As it is a advanced subject, I redirect you to the devoted chapter of the E book: https://doc.rust-lang.org/e book/ch19-06-macros.html.

println! is a macro and never a perform as a result of Rust would not assist (but?) variadic generics. It has the benefit of being compile-time evaluated and checked and thus forestall vulnerabilities akin to format string vulnerabilities.



Error dealing with

How ought to our program behave when encountering an error? And easy methods to inform the consumer of it?
That is what we name error dealing with.

Among the many dozen programming languages that I’ve expertise with, Rust is with none doubts my favourite one concerning error dealing with on account of its explicitness, security, and conciseness.

For our easy program, we’ll Field errors: we’ll enable our program to return any kind that implements the std::error::Error trait. What’s a trait? Extra on that later.

ch_01/sha1_cracker/src/principal.rs

use std::{
    env,
    error::Error,
};

const SHA1_HEX_STRING_LENGTH: usize = 40;

fn principal() -> Consequence<(), Field<dyn Error>> {
    let args: Vec<String> = env::args().accumulate();

    if args.len() != 3 {
        println!("Utilization:");
        println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
        return Okay(());
    }

    let hash_to_crack = args[2].trim();
    if hash_to_crack.len() != SHA1_HEX_STRING_LENGTH {
        return Err("sha1 hash just isn't legitimate".into());
    }

    Okay(())
}
Enter fullscreen mode

Exit fullscreen mode



Studying information

Because it takes an excessive amount of time to check all attainable mixtures of letters, numbers, and particular characters, we have to cut back the variety of SHA-1 hashes generated. For that, we use a particular type of dictionary, often known as a wordlist, which incorporates the most typical password present in breached web sites.

Studying a file in Rust could be achieved with the usual library like that:

ch_01/sha1_cracker/src/principal.rs

use std::{
    env,
    error::Error,
    fs::File,
    io::{BufRead, BufReader},
};

const SHA1_HEX_STRING_LENGTH: usize = 40;

fn principal() -> Consequence<(), Field<dyn Error>> {
    let args: Vec<String> = env::args().accumulate();

    if args.len() != 3 {
        println!("Utilization:");
        println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
        return Okay(());
    }

    let hash_to_crack = args[2].trim();
    if hash_to_crack.len() != SHA1_HEX_STRING_LENGTH {
        return Err("sha1 hash just isn't legitimate".into());
    }

    let wordlist_file = File::open(&args[1])?;
    let reader = BufReader::new(&wordlist_file);

    for line in reader.strains() {
        let line = line?.trim().to_string();
        println!("{}", line);
    }

    Okay(())
}
Enter fullscreen mode

Exit fullscreen mode



Crates

Now that the fundamental construction of our program is in place, we have to truly compute the SHA-1 hashes. Fortuitously for us, some gifted builders have already developed this advanced piece of code and shared it on-line, prepared to make use of within the type of an exterior library. In Rust, we name these libraries, or packages, crates. They are often browsed on-line at https://crates.io.

They’re managed with cargo: Rust’s bundle supervisor. Earlier than utilizing a crate in our program, we have to declare its model in Cargo’s manifest file: Cargo.toml.

ch_01/sha1_cracker/Cargo.toml

[package]
title = "sha1_cracker"
model = "0.1.0"
authors = ["Sylvain Kerkour"]


# See extra keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
sha-1 = "0.9"
hex = "0.4"
Enter fullscreen mode

Exit fullscreen mode

We will then import it in our SHA-1 cracker:

ch_01/sha1_cracker/src/principal.rs

use sha1::Digest;
use std::{
    env,
    error::Error,
    fs::File,
    io::{BufRead, BufReader},
};

const SHA1_HEX_STRING_LENGTH: usize = 40;

fn principal() -> Consequence<(), Field<dyn Error>> {
    let args: Vec<String> = env::args().accumulate();

    if args.len() != 3 {
        println!("Utilization:");
        println!("sha1_cracker: <wordlist.txt> <sha1_hash>");
        return Okay(());
    }

    let hash_to_crack = args[2].trim();
    if hash_to_crack.len() != SHA1_HEX_STRING_LENGTH {
        return Err("sha1 hash just isn't legitimate".into());
    }

    let wordlist_file = File::open(&args[1])?;
    let reader = BufReader::new(&wordlist_file);

    for line in reader.strains() {
        let line = line?;
        let common_password = line.trim();
        if hash_to_crack == &hex::encode(sha1::Sha1::digest(common_password.as_bytes())) {
            println!("Password discovered: {}", &common_password);
            return Okay(());
        }
    }
    println!("password not present in wordlist :(");

    Okay(())
}
Enter fullscreen mode

Exit fullscreen mode

Hourray! Our first program is now full. We will take a look at it by working:

$ cargo run -- wordlist.txt 7c6a61c68ef8b9b6b061b28c348bc1ed7921cb53
Enter fullscreen mode

Exit fullscreen mode

Please word that in a real-world state of affairs, we might wish to use optimized hash crackers akin to hashcat or John the Ripper, which, amongst different issues, might use the GPU to considerably pace up the cracking.

One other level could be to first load the wordlist in reminiscence earlier than performing the computations.

This publish is an excerpt from my e book Black Hat Rust



RAII

A element might have caught the eye of essentially the most meticulous of you: we opened the wordlist file, however we by no means closed it!

This sample (or function) is known as RAII: Useful resource Acquisition Is Initialization. In Rust, variables not solely signify elements of the reminiscence of the pc, they might additionally personal sources. At any time when an object goes out of scope, its destructor is known as, and the owned sources are freed.

Thus, you need not name a shut methodology on information or sockets. When the variable is dropped (goes out of scope), the file or socket shall be automagically closed.

In our case, the wordlist_file variable owns the file and has the principal perform as scope. At any time when the primary perform exits, both on account of an error or an early return, the owned file is closed.

Magic, is not it? Because of this, it is very uncommon to leak sources in Rust.



Okay(())

You may also have seen that the final line of our principal perform doesn’t include the return key phrase. It is because Rust is an expression-oriented language. Expressions consider to a worth. Their opposites, statements, are directions that do one thing and finish with a semicolon (;).

So if our program reaches the final line of the principal perform, the principal perform will consider to Okay(()), which implies: “success: the whole lot went based on the plan”.

An equal would have been:

    return Okay(());
Enter fullscreen mode

Exit fullscreen mode

however not:

    Okay(());
Enter fullscreen mode

Exit fullscreen mode

As a result of right here Okay(()); is a press release as a result of semicolon, and the primary perform now not evaluates to its anticipated return kind: Consequence.

Need to be taught extra about Rust, utilized cryptography and offensive safety? Check out my e book Black Hat Rust.

RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

- Advertisment -
Google search engine

Most Popular

Recent Comments