You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
311 lines
10 KiB
Rust
311 lines
10 KiB
Rust
pub mod lib;
|
|
|
|
use crate::lib::crypt::{decrypt_data, decrypt_with_dictionary, encrypt_data};
|
|
use crate::lib::hash::{create_hmac, sha256};
|
|
use crate::lib::timing::TimeTaker;
|
|
use bdf::chunks::{DataEntry, HashEntry, HashLookupTable};
|
|
use bdf::io::{BDFReader, BDFWriter};
|
|
use benchlib::benching::Bencher;
|
|
use crossbeam_channel::bounded;
|
|
use pbr::ProgressBar;
|
|
use rayon::prelude::*;
|
|
use rayon::str;
|
|
use regex::Regex;
|
|
use rpassword;
|
|
use rpassword::read_password_from_tty;
|
|
use spinners::{Spinner, Spinners};
|
|
use std::collections::HashMap;
|
|
use std::fs;
|
|
use std::fs::File;
|
|
use std::sync::{Arc, Mutex};
|
|
use std::thread;
|
|
use std::time::Duration;
|
|
use structopt::StructOpt;
|
|
|
|
#[derive(StructOpt, Clone)]
|
|
#[structopt(name = "destools", version = "1.0", author = "Julius R.")]
|
|
struct Opts {
|
|
#[structopt(subcommand)]
|
|
subcmd: SubCommand,
|
|
}
|
|
|
|
#[derive(StructOpt, Clone)]
|
|
enum SubCommand {
|
|
/// Encrypt a file with des
|
|
#[structopt(name = "encrypt")]
|
|
Encrypt(Encrypt),
|
|
|
|
/// Decrypt a DES encoded file
|
|
#[structopt(name = "decrypt")]
|
|
Decrypt(Decrypt),
|
|
|
|
/// Create a dictionary rainbow-table from a txt file
|
|
#[structopt(name = "create-dictionary")]
|
|
CreateDictionary(CreateDictionary),
|
|
|
|
/// Runs some benchmarks
|
|
#[structopt(name = "benchmark")]
|
|
Benchmark,
|
|
}
|
|
|
|
#[derive(StructOpt, Clone)]
|
|
struct Encrypt {
|
|
/// The input file
|
|
#[structopt(short = "i", long = "input", default_value = "input.txt")]
|
|
input: String,
|
|
|
|
/// The output file
|
|
#[structopt(short = "o", long = "output", default_value = "output.des")]
|
|
output: String,
|
|
}
|
|
|
|
#[derive(StructOpt, Clone)]
|
|
struct Decrypt {
|
|
/// The input file
|
|
#[structopt(short = "i", long = "input", default_value = "input.des")]
|
|
input: String,
|
|
|
|
/// The output file
|
|
#[structopt(short = "o", long = "output", default_value = "output.txt")]
|
|
output: String,
|
|
|
|
/// A dictionary file containing a list of passwords
|
|
/// The file needs to be in a csv format with calculated password hashes.
|
|
/// The hashes can be calculated with the create-dictionary subcommand from a txt file.
|
|
#[structopt(short = "d", long = "dictionary")]
|
|
dictionary: Option<String>,
|
|
}
|
|
|
|
#[derive(StructOpt, Clone)]
|
|
struct CreateDictionary {
|
|
/// The input dictionary file.
|
|
#[structopt(short = "i", long = "input", default_value = "passwords.txt")]
|
|
input: String,
|
|
|
|
/// The output dictionary file
|
|
#[structopt(short = "o", long = "output", default_value = "dictionary.bdf")]
|
|
output: String,
|
|
|
|
/// The compression level of the dictionary file from 1 to 9
|
|
/// 0 means no compression
|
|
#[structopt(short = "c", long = "compression-level", default_value = "0")]
|
|
compress: u32,
|
|
|
|
/// The number of password entries per chunk.
|
|
#[structopt(long = "entries-per-chunk", default_value = "100000")]
|
|
entries_per_chunk: u32,
|
|
}
|
|
|
|
fn main() {
|
|
let opts: Opts = Opts::from_args();
|
|
match (opts.clone()).subcmd {
|
|
SubCommand::Encrypt(args) => encrypt(&opts, &args),
|
|
SubCommand::Decrypt(args) => decrypt(&opts, &args),
|
|
SubCommand::CreateDictionary(args) => create_dictionary(&opts, &args),
|
|
SubCommand::Benchmark => benchmark(),
|
|
}
|
|
}
|
|
|
|
/// Encrypts a file with des
|
|
fn encrypt(_opts: &Opts, args: &Encrypt) {
|
|
let input: String = (*args.input).parse().unwrap();
|
|
let output: String = (*args.output).parse().unwrap();
|
|
let data: Vec<u8> = fs::read(input).expect("Failed to read input file!");
|
|
|
|
let pass = read_password_from_tty(Some("Password: ")).unwrap();
|
|
let sha256_key = sha256(&pass);
|
|
let key = &sha256_key[0..8];
|
|
let mut data_hmac = create_hmac(&sha256_key, &data).expect("failed to create hmac");
|
|
let mut enc_data = encrypt_data(data.as_slice(), &key);
|
|
enc_data.append(&mut data_hmac);
|
|
fs::write(output, enc_data.as_slice()).expect("Failed to write output file!");
|
|
}
|
|
|
|
/// Decrypts a des encrypted file.
|
|
/// Brute forces if the dictionary argument was passed
|
|
fn decrypt(_opts: &Opts, args: &Decrypt) {
|
|
let mut tt = TimeTaker::new();
|
|
tt.take("start");
|
|
let input: String = (*args.input).parse().unwrap();
|
|
let output: String = (*args.output).parse().unwrap();
|
|
let dictionary = args.dictionary.clone();
|
|
let data = fs::read(input).expect("Failed to read input file!");
|
|
|
|
if let Some(dict) = dictionary {
|
|
tt.take("decryption-start");
|
|
if let Some(dec_data) = decrypt_with_dictionary_file(dict, &data) {
|
|
fs::write(output, &dec_data).expect("Failed to write output file!");
|
|
println!(
|
|
"Decryption took {:.2}s",
|
|
tt.since("decryption-start").unwrap().as_secs_f32()
|
|
);
|
|
println!("Finished {:.2}s!", tt.since("start").unwrap().as_secs_f32());
|
|
} else {
|
|
println!("\nNo password found!");
|
|
println!("Finished {:.2}s!", tt.since("start").unwrap().as_secs_f32());
|
|
}
|
|
} else {
|
|
println!("No checksum file given!");
|
|
}
|
|
}
|
|
|
|
const SHA256: &str = "sha256";
|
|
|
|
/// Creates a dictionary from an input file and writes it to the output file
|
|
fn create_dictionary(_opts: &Opts, args: &CreateDictionary) {
|
|
let mut tt = TimeTaker::new();
|
|
tt.take("start");
|
|
let sp = spinner("Reading input file...");
|
|
let input: String = (*args.input).parse().unwrap();
|
|
// TODO: Some form of removing duplicates (without itertools)
|
|
let fout = File::create(args.output.clone()).unwrap();
|
|
|
|
let content = fs::read_to_string(input).expect("Failed to read content");
|
|
let lines = content.par_lines();
|
|
let entry_count = lines.clone().count() as u64;
|
|
sp.stop();
|
|
|
|
let mut pb = ProgressBar::new(entry_count);
|
|
pb.set_max_refresh_rate(Some(Duration::from_millis(200)));
|
|
|
|
let mut bdf_file = BDFWriter::new(fout, entry_count, args.compress != 0);
|
|
bdf_file.set_compression_level(args.compress);
|
|
bdf_file
|
|
.set_entries_per_chunk(args.entries_per_chunk)
|
|
.expect("Failed to set the entries per chunk.");
|
|
bdf_file
|
|
.add_lookup_entry(HashEntry::new(SHA256.to_string(), 32))
|
|
.expect("Failed to add sha256 lookup entry");
|
|
|
|
let mut threads = Vec::new();
|
|
let (rx, tx) = bounded::<DataEntry>(100_00_000);
|
|
let bdf_arc = Arc::new(Mutex::new(bdf_file));
|
|
let pb_arc = Arc::new(Mutex::new(pb));
|
|
|
|
for _ in 0..(num_cpus::get() as f32 / 4f32).max(1f32) as usize {
|
|
let tx = tx.clone();
|
|
let bdf_arc = Arc::clone(&bdf_arc);
|
|
let pb_arc = Arc::clone(&pb_arc);
|
|
threads.push(thread::spawn(move || {
|
|
for entry in tx {
|
|
if let Err(e) = &bdf_arc.lock().unwrap().add_data_entry(entry) {
|
|
println!("{:?}", e);
|
|
}
|
|
pb_arc.lock().unwrap().inc();
|
|
}
|
|
}));
|
|
}
|
|
|
|
tt.take("creation");
|
|
let re = Regex::new("[\\x00\\x08\\x0B\\x0C\\x0E-\\x1F\\t\\r\\a\\n]").unwrap();
|
|
lines
|
|
.map(|line| -> String { re.replace_all(line, "").to_string() })
|
|
.map(|pw| -> DataEntry {
|
|
let key256 = sha256(&pw);
|
|
let mut data_entry = DataEntry::new(pw);
|
|
data_entry.add_hash_value(SHA256.to_string(), key256);
|
|
|
|
data_entry
|
|
})
|
|
.for_each_with(rx, |rx, data_entry| {
|
|
rx.send(data_entry)
|
|
.expect("Failed to send value to channel.");
|
|
});
|
|
|
|
for handle in threads {
|
|
if let Err(_err) = handle.join() {
|
|
println!("Failed to join!");
|
|
}
|
|
}
|
|
bdf_arc
|
|
.lock()
|
|
.unwrap()
|
|
.finish()
|
|
.expect("failed to finish the writing process");
|
|
pb_arc.lock().unwrap().finish();
|
|
println!(
|
|
"Rainbow table creation took {:.2}s",
|
|
tt.since("creation").unwrap().as_secs_f32()
|
|
);
|
|
println!("Finished {:.2}s!", tt.since("start").unwrap().as_secs_f32());
|
|
}
|
|
|
|
/// Creates a new spinner with a given text
|
|
fn spinner(text: &str) -> Spinner {
|
|
Spinner::new(Spinners::Dots2, text.into())
|
|
}
|
|
|
|
/// Decrypts the file using a bdf dictionary
|
|
/// The files content is read chunk by chunk to reduce the memory impact since dictionary
|
|
/// files tend to be several gigabytes in size
|
|
fn decrypt_with_dictionary_file(filename: String, data: &Vec<u8>) -> Option<Vec<u8>> {
|
|
let sp = spinner("Reading dictionary...");
|
|
let f = File::open(&filename).expect("Failed to open dictionary file.");
|
|
let mut bdf_file = BDFReader::new(f);
|
|
bdf_file.read_start().expect("failed to read the bdf file");
|
|
let mut chunk_count = 0;
|
|
if let Some(meta) = &bdf_file.metadata {
|
|
chunk_count = meta.chunk_count;
|
|
}
|
|
let mut pb = ProgressBar::new(chunk_count as u64);
|
|
let bdf_arc = Arc::new(Mutex::new(bdf_file));
|
|
let mut threads = Vec::new();
|
|
let (rx, tx) = bounded::<Vec<DataEntry>>(100);
|
|
|
|
for _ in 0..(num_cpus::get() as f32 / 4f32).max(1f32) as usize {
|
|
let rx = rx.clone();
|
|
let bdf_arc = Arc::clone(&bdf_arc);
|
|
|
|
threads.push(thread::spawn(move || {
|
|
let mut lookup_table = HashLookupTable::new(HashMap::new());
|
|
if let Some(table) = &bdf_arc.lock().unwrap().lookup_table {
|
|
lookup_table = table.clone();
|
|
}
|
|
while let Ok(next_chunk) = &mut bdf_arc
|
|
.lock()
|
|
.expect("failed to lock bdf_arc to read next chunk")
|
|
.next_chunk()
|
|
{
|
|
if let Ok(entries) = next_chunk.data_entries(&lookup_table) {
|
|
if let Err(_) = rx.send(entries) {}
|
|
}
|
|
}
|
|
}));
|
|
}
|
|
drop(rx);
|
|
sp.stop();
|
|
let mut result_data: Option<Vec<u8>> = None;
|
|
for entries in tx {
|
|
let pw_table: Vec<(&String, &Vec<u8>)> = entries
|
|
.par_iter()
|
|
.map(|entry: &DataEntry| {
|
|
let pw = &entry.plain;
|
|
let key: &Vec<u8> = entry.get_hash_value(SHA256.to_string()).unwrap();
|
|
|
|
(pw, key)
|
|
})
|
|
.collect();
|
|
pb.inc();
|
|
if let Some(dec_data) = decrypt_with_dictionary(&data, pw_table) {
|
|
result_data = Some(dec_data);
|
|
break;
|
|
}
|
|
}
|
|
pb.finish();
|
|
result_data
|
|
}
|
|
|
|
fn benchmark() {
|
|
let mut bencher = Bencher::new();
|
|
let mut test_key = Vec::new();
|
|
let mut encrypted = Vec::new();
|
|
bencher
|
|
.set_iterations(1_000_000)
|
|
.print_settings()
|
|
.bench("sha256", || test_key = sha256("abcdefghijklmnopqrstuvwxyz"))
|
|
.bench("des encrypt", || {
|
|
encrypted = encrypt_data(b"abcdefghijklmnopqrstuvwxyz", &test_key[..8])
|
|
})
|
|
.bench("des decrypt", || decrypt_data(&encrypted, &test_key[..8]));
|
|
}
|