Implement incremental cracking

This commit is contained in:
William Brawner 2018-04-07 09:06:12 -05:00
parent 43cf53c0ca
commit ef15a10a4e
28 changed files with 833 additions and 189 deletions

View file

@ -3,7 +3,10 @@
[![Test Coverage](https://api.codeclimate.com/v1/badges/a35dff49221e36abf189/test_coverage)](https://codeclimate.com/github/wbrawner/keecrack/test_coverage) [![Test Coverage](https://api.codeclimate.com/v1/badges/a35dff49221e36abf189/test_coverage)](https://codeclimate.com/github/wbrawner/keecrack/test_coverage)
KeeCrack is a Java program used for brute-forcing KeePass database file master passwords. This should go without saying KeeCrack is a Java program used for brute-forcing KeePass database file master passwords. This should go without saying
but use of this application is prohibited without the express consent of the owner of the database file. but use of this application is prohibited without the express consent of the owner of the database file. KeeCrack works
by taking a KeePass database file, an optional key file, and a word list, then attempts to open the database with the
give key file/password pair until it finds a successful password. KeeCrack does not do incremental word list generation
at this time, though you can
## Usage ## Usage
@ -17,6 +20,13 @@ KeeCrack makes use of Gradle, so to build it yourself, you can just run
./gradlew jfxJar ./gradlew jfxJar
This will produce a JAR output, though you can also create platform-specific binaries with the following:
./gradlew jfxNative
For more information on building for your OS, please see the README for the
[javafx-gradle-plugin](https://github .com/FibreFoX/javafx-gradle-plugin#requirements)
## Contributing ## Contributing
If you'd like to contribute, please fork the repository, make your changes, squash your commits, and send a pull request If you'd like to contribute, please fork the repository, make your changes, squash your commits, and send a pull request

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
buildscript { buildscript {
repositories { repositories {
jcenter() jcenter()

Binary file not shown.

View file

@ -1,6 +1,6 @@
#Sat Apr 07 16:39:20 CDT 2018 #Sun Apr 15 14:52:35 CDT 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-4.0-bin.zip

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
dependencies { dependencies {
compile project(':keecrack-lib') compile project(':keecrack-lib')
compile project(':keecrack-gui') compile project(':keecrack-gui')

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.cli; package com.wbrawner.keecrack.cli;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;
@ -10,13 +25,22 @@ import net.sourceforge.argparse4j.inf.ArgumentParserException;
import net.sourceforge.argparse4j.inf.Namespace; import net.sourceforge.argparse4j.inf.Namespace;
import java.io.File; import java.io.File;
import java.text.SimpleDateFormat;
import java.time.Duration; import java.time.Duration;
import java.util.Date;
import java.util.Locale; import java.util.Locale;
public class Main { public class Main {
private static final String LOG_SEPARATOR = " - ";
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("[yyyy-MM-dd HH:mm:ss.SSS]");
private static boolean isVerbose = false; private static boolean isVerbose = false;
private static boolean isIncremental = false;
public static void main(String[] args) { public static void main(String[] args) {
if (args.length == 0) {
com.wbrawner.keecrack.gui.Main.main(new String[]{});
return;
}
ArgumentParser parser = ArgumentParsers.newFor("KeeCrack") ArgumentParser parser = ArgumentParsers.newFor("KeeCrack")
.build() .build()
.description("Brute force KeePass database files"); .description("Brute force KeePass database files");
@ -24,13 +48,15 @@ public class Main {
.help("Increase logging output") .help("Increase logging output")
.action(Arguments.storeTrue()) .action(Arguments.storeTrue())
.dest("verbose"); .dest("verbose");
parser.addArgument("--gui") parser.addArgument("--incremental", "-i")
.action(Arguments.storeTrue()) .action(Arguments.storeTrue())
.help("launch the graphical interface (ignores other options)") .help("Use pattern-based (incremental) guesses instead of a list of words from a file")
.dest("gui"); .dest("incremental");
parser.addArgument("--word-list", "-w") parser.addArgument("--word-list", "-w")
.help("a file containing newline-separated words to use as the passwords") .help("a file containing newline-separated words to use as the passwords, or the pattern to generate " +
"words from if the --incremental flag is set")
.dest("wordlist") .dest("wordlist")
.required(true)
.metavar("WORD-LIST-FILE"); .metavar("WORD-LIST-FILE");
parser.addArgument("--key-file", "-k") parser.addArgument("--key-file", "-k")
.help("the key file to use with the database") .help("the key file to use with the database")
@ -44,6 +70,7 @@ public class Main {
try { try {
Namespace res = parser.parseArgs(args); Namespace res = parser.parseArgs(args);
isVerbose = res.getBoolean("verbose"); isVerbose = res.getBoolean("verbose");
isIncremental = res.getBoolean("incremental");
KeeCrack keeCrack = KeeCrack.getInstance(); KeeCrack keeCrack = KeeCrack.getInstance();
keeCrack.setCrackingView(new CLICrackingView()); keeCrack.setCrackingView(new CLICrackingView());
@ -57,25 +84,38 @@ public class Main {
keeCrack.setKeyFile(new File(keyfilePath)); keeCrack.setKeyFile(new File(keyfilePath));
} }
String wordlistPath = res.getString("wordlist"); String wordlist = res.getString("wordlist");
if (wordlistPath != null) ; if (wordlist != null) {
keeCrack.setWordlistFile(new File(wordlistPath)); if (isIncremental) {
keeCrack.setWordListPattern(wordlist);
if (res.getBoolean("gui")) { } else {
com.wbrawner.keecrack.gui.Main.main(new String[]{}); keeCrack.setWordListFile(new File(wordlist));
} else { }
keeCrack.attack();
} }
keeCrack.attack();
} catch (ArgumentParserException e) { } catch (ArgumentParserException e) {
parser.handleError(e); parser.handleError(e);
} }
} }
private static void print(String message) {
System.out.print(dateFormat.format(new Date()));
System.out.print(LOG_SEPARATOR);
System.out.println(message);
}
private static void error(String message) {
System.err.print(dateFormat.format(new Date()));
System.err.print(LOG_SEPARATOR);
System.err.println(message);
}
static class CLICrackingView implements CrackingView { static class CLICrackingView implements CrackingView {
@Override @Override
public void onPasswordGuess(String password) { public void onPasswordGuess(String password) {
if (isVerbose) if (isVerbose)
System.out.println("Guessing password: " + password); print("Guessing password: " + password);
} }
@Override @Override
@ -95,17 +135,17 @@ public class Main {
timeElapsed.toString().toLowerCase().substring(2), timeElapsed.toString().toLowerCase().substring(2),
password password
); );
System.out.println(message); print(message);
} }
@Override @Override
public void onError(Code code) { public void onError(Code code) {
String message = ""; String message = "";
switch (code) { switch (code) {
case ERROR_MISSING_DATABASE_FILE: case ERROR_INVALID_DATABASE_FILE:
message = "Please specify a database file that you have read access to"; message = "Please specify a database file that you have read access to";
break; break;
case ERROR_MISSING_WORD_LIST_FILE: case ERROR_INVALID_WORD_LIST:
message = "Please specify a word list file that you have read access to"; message = "Please specify a word list file that you have read access to";
break; break;
case ERROR_CRACKING_INTERRUPTED: case ERROR_CRACKING_INTERRUPTED:
@ -115,7 +155,7 @@ public class Main {
message = "An error occurred while trying to read one of the files"; message = "An error occurred while trying to read one of the files";
break; break;
} }
System.err.println(message); error(message);
System.exit(code.ordinal()); System.exit(code.ordinal());
} }
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
dependencies { dependencies {
compile project(':keecrack-lib') compile project(':keecrack-lib')
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.gui; package com.wbrawner.keecrack.gui;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.gui; package com.wbrawner.keecrack.gui;
import javafx.application.Application; import javafx.application.Application;
@ -10,15 +25,15 @@ import java.io.IOException;
public class Main extends Application { public class Main extends Application {
public static void main(String[] args) {
launch(args);
}
@Override @Override
public void start(Stage primaryStage) throws IOException { public void start(Stage primaryStage) throws IOException {
Parent root = FXMLLoader.load(getClass().getResource("/fxml/main.fxml")); Parent root = FXMLLoader.load(getClass().getResource("/fxml/main.fxml"));
primaryStage.setTitle("Keecrack"); primaryStage.setTitle("Keecrack");
primaryStage.setScene(new Scene(root, 400, 400)); primaryStage.setScene(new Scene(root, 400, 300));
primaryStage.show(); primaryStage.show();
} }
public static void main(String[] args) {
launch(args);
}
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.gui; package com.wbrawner.keecrack.gui;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;
@ -6,10 +21,13 @@ import com.wbrawner.keecrack.lib.view.FormView;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.Cursor;
import javafx.scene.Parent; import javafx.scene.Parent;
import javafx.scene.Scene; import javafx.scene.Scene;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.ToggleGroup;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
@ -21,14 +39,25 @@ import java.util.ResourceBundle;
public class MainController implements Initializable, FormView { public class MainController implements Initializable, FormView {
@FXML private TextField database; private static KeeCrack keeCrack;
@FXML private TextField key; @FXML
@FXML private TextField wordlist; private TextField database;
@FXML private Button crackButton; @FXML
private TextField key;
@FXML
private TextField wordlist;
@FXML
private Button crackButton;
@FXML
private ToggleGroup wordlistType;
@FXML
private RadioButton wordlistFile;
@FXML
private RadioButton wordlistPattern;
@Override @Override
public void initialize(URL location, ResourceBundle resources) { public void initialize(URL location, ResourceBundle resources) {
KeeCrack keeCrack = KeeCrack.getInstance(); keeCrack = KeeCrack.getInstance();
keeCrack.setFormView(this); keeCrack.setFormView(this);
database.setOnMouseClicked(event -> { database.setOnMouseClicked(event -> {
if (KeeCrack.getInstance().isCracking()) { if (KeeCrack.getInstance().isCracking()) {
@ -48,14 +77,7 @@ public class MainController implements Initializable, FormView {
keeCrack.setKeyFile(keyFile); keeCrack.setKeyFile(keyFile);
}); });
wordlist.setOnMouseClicked(event -> { updateWordListHandler();
if (KeeCrack.getInstance().isCracking()) {
return;
}
File wordlistFile = getFile("Text Files", "txt");
keeCrack.setWordlistFile(wordlistFile);
});
crackButton.setOnMouseClicked(event -> { crackButton.setOnMouseClicked(event -> {
try { try {
@ -79,19 +101,21 @@ public class MainController implements Initializable, FormView {
return null; return null;
}); });
Parent root = loader.load(); Parent root = loader.load();
stage.setScene(new Scene(root, 200, 200)); stage.setScene(new Scene(root, 400, 350));
stage.showAndWait(); stage.showAndWait();
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
}); });
wordlistType.selectedToggleProperty().addListener((observableValue, oldToggle, newToggle) ->
updateWordListHandler());
if (keeCrack.getDatabaseFile() != null) if (keeCrack.getDatabaseFile() != null)
onDatabaseFileSet(keeCrack.getDatabaseFile().getName()); onDatabaseFileSet(keeCrack.getDatabaseFile().getName());
if (keeCrack.getKeyFile() != null) if (keeCrack.getKeyFile() != null)
onKeyFileSet(keeCrack.getKeyFile().getName()); onKeyFileSet(keeCrack.getKeyFile().getName());
if (keeCrack.getWordlistFile() != null) if (keeCrack.getWordListName() != null)
onWordListFileSet(keeCrack.getWordlistFile().getName()); onWordListSet(keeCrack.getWordListName());
} }
@ -107,6 +131,26 @@ public class MainController implements Initializable, FormView {
return fileChooser.showOpenDialog(stage); return fileChooser.showOpenDialog(stage);
} }
private void updateWordListHandler() {
wordlist.clear();
if (wordlistFile.isSelected()) {
wordlist.setEditable(false);
wordlist.setCursor(Cursor.HAND);
wordlist.setOnMouseClicked(event -> {
if (KeeCrack.getInstance().isCracking()) {
return;
}
File wordlistFile = getFile("Text Files", "txt");
keeCrack.setWordListFile(wordlistFile);
});
} else if (wordlistPattern.isSelected()) {
wordlist.setEditable(true);
wordlist.setCursor(Cursor.TEXT);
wordlist.setOnMouseExited(mouseEvent -> keeCrack.setWordListPattern(wordlist.getText()));
wordlist.setOnMouseClicked(null);
}
}
@Override @Override
public void onDatabaseFileSet(String name) { public void onDatabaseFileSet(String name) {
database.setText(name); database.setText(name);
@ -118,7 +162,7 @@ public class MainController implements Initializable, FormView {
} }
@Override @Override
public void onWordListFileSet(String name) { public void onWordListSet(String name) {
wordlist.setText(name); wordlist.setText(name);
} }

View file

@ -1,18 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<?import javafx.scene.control.Label?> <?import javafx.scene.control.Label?>
<?import javafx.scene.control.ProgressIndicator?> <?import javafx.scene.control.ProgressIndicator?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" <AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8"
fx:controller="com.wbrawner.keecrack.gui.CrackingController"> fx:controller="com.wbrawner.keecrack.gui.CrackingController">
<children> <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0"
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <ProgressIndicator fx:id="progress"/>
<ProgressIndicator fx:id="progress" /> <Label fx:id="timeElapsed"/>
<Label fx:id="timeElapsed" /> <Label fx:id="passwordLabel" text="Trying password: "/>
<Label fx:id="passwordLabel" text="Trying password: " /> <Label fx:id="password"/>
<Label fx:id="password" /> </VBox>
</children>
</VBox>
</children>
</AnchorPane> </AnchorPane>

View file

@ -1,61 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!--
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-->
<?import javafx.geometry.Insets?> <?import javafx.geometry.*?>
<?import javafx.scene.control.*?> <?import javafx.scene.control.*?>
<?import javafx.scene.Cursor?> <?import javafx.scene.Cursor?>
<?import javafx.scene.layout.*?> <?import javafx.scene.layout.*?>
<AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8" <AnchorPane xmlns:fx="http://javafx.com/fxml/1" prefHeight="400.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/8"
fx:controller="com.wbrawner.keecrack.gui.MainController"> fx:controller="com.wbrawner.keecrack.gui.MainController">
<children> <VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0"
<VBox alignment="CENTER" prefHeight="200.0" prefWidth="100.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0"> AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0">
<children> <GridPane alignment="CENTER">
<GridPane alignment="CENTER" prefHeight="90.0" prefWidth="539.0"> <columnConstraints>
<columnConstraints> <ColumnConstraints halignment="RIGHT" minWidth="100.0" prefWidth="20.0"/>
<ColumnConstraints halignment="RIGHT" minWidth="100.0" prefWidth="20.0" /> <ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0"/>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" /> </columnConstraints>
</columnConstraints> <rowConstraints>
<rowConstraints> <RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> vgrow="ALWAYS"/>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> <RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" /> vgrow="ALWAYS"/>
</rowConstraints> <RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
<children> vgrow="ALWAYS"/>
<Label alignment="CENTER_RIGHT" text="Database" /> <RowConstraints maxHeight="-Infinity" minHeight="20.0" percentHeight="25.0" prefHeight="60.0"
<TextField fx:id="database" editable="false" promptText="Select" GridPane.columnIndex="1"> vgrow="ALWAYS"/>
<cursor> </rowConstraints>
<Cursor fx:constant="HAND" /> <padding>
</cursor> <Insets left="20.0" right="20.0"/>
<GridPane.margin> </padding>
<Insets left="20.0" /> <Label alignment="CENTER_RIGHT" text="Database"/>
</GridPane.margin> <TextField fx:id="database" editable="false" promptText="Select" GridPane.columnIndex="1">
</TextField> <cursor>
<Label text="Key File" GridPane.rowIndex="1" /> <Cursor fx:constant="HAND"/>
<TextField fx:id="key" editable="false" promptText="Select (Optional)" GridPane.columnIndex="1" </cursor>
GridPane.rowIndex="1"> <GridPane.margin>
<cursor> <Insets left="20.0"/>
<Cursor fx:constant="HAND" /> </GridPane.margin>
</cursor> </TextField>
<GridPane.margin> <Label text="Key File" GridPane.rowIndex="1"/>
<Insets left="20.0" /> <TextField fx:id="key" editable="false" promptText="Select (Optional)" GridPane.columnIndex="1"
</GridPane.margin> GridPane.rowIndex="1">
</TextField> <cursor>
<TextField fx:id="wordlist" editable="false" promptText="Select" GridPane.columnIndex="1" <Cursor fx:constant="HAND"/>
GridPane.rowIndex="2"> </cursor>
<cursor> <GridPane.margin>
<Cursor fx:constant="HAND" /> <Insets left="20.0"/>
</cursor> </GridPane.margin>
<GridPane.margin> </TextField>
<Insets left="20.0" /> <Label alignment="CENTER_RIGHT" text="Word List Type" GridPane.rowIndex="2"/>
</GridPane.margin> <HBox alignment="CENTER_LEFT" prefHeight="100.0" prefWidth="200.0" GridPane.columnIndex="1"
</TextField> GridPane.rowIndex="2">
<Label text="Word List" GridPane.rowIndex="2" /> <RadioButton fx:id="wordlistPattern" text="From Pattern">
</children> <HBox.margin>
<padding> <Insets left="20.0"/>
<Insets left="20.0" right="20.0" /> </HBox.margin>
</padding> <toggleGroup>
</GridPane> <ToggleGroup fx:id="wordlistType"/>
<Button fx:id="crackButton" mnemonicParsing="false" prefHeight="25.0" prefWidth="111.0" text="Crack" /> </toggleGroup>
</children> </RadioButton>
</VBox> <RadioButton fx:id="wordlistFile" selected="true" text="From File"
</children> toggleGroup="wordlistType">
<HBox.margin>
<Insets left="20.0"/>
</HBox.margin>
</RadioButton>
</HBox>
<TextField fx:id="wordlist" editable="false" promptText="Select" GridPane.columnIndex="1"
GridPane.rowIndex="3">
<cursor>
<Cursor fx:constant="HAND"/>
</cursor>
<GridPane.margin>
<Insets left="20.0"/>
</GridPane.margin>
</TextField>
<Label text="Word List" GridPane.rowIndex="3"/>
</GridPane>
<Button fx:id="crackButton" mnemonicParsing="false" prefHeight="25.0" prefWidth="111.0" text="Crack"/>
</VBox>
</AnchorPane> </AnchorPane>

View file

@ -1,13 +1,30 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
apply plugin: 'jacoco' apply plugin: 'jacoco'
dependencies { dependencies {
implementation group: 'commons-io', name: 'commons-io', version: '2.6'
implementation group: 'com.github.mifmif', name: 'generex', version: '1.0.2'
implementation group: 'org.linguafranca.pwdb', name: 'KeePassJava2-kdbx', version: '2.1.4' implementation group: 'org.linguafranca.pwdb', name: 'KeePassJava2-kdbx', version: '2.1.4'
} }
jacocoTestReport { jacocoTestReport {
reports { reports {
xml.enabled true xml.enabled = true
html.enabled true html.enabled = true
html.destination file("${buildDir}/jacoco") html.destination file("${buildDir}/jacoco")
} }
} }
@ -18,4 +35,4 @@ jar {
'Main-Class': 'com.wbrawner.keecrack.lib.KeeCrack' 'Main-Class': 'com.wbrawner.keecrack.lib.KeeCrack'
) )
} }
} }

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib; package com.wbrawner.keecrack.lib;
/** /**
@ -7,6 +22,7 @@ public enum Code {
ERROR_FILE_READ, ERROR_FILE_READ,
ERROR_CRACKING_INTERRUPTED, ERROR_CRACKING_INTERRUPTED,
ERROR_CRACKING_IN_PROGRESS, ERROR_CRACKING_IN_PROGRESS,
ERROR_MISSING_DATABASE_FILE, ERROR_INVALID_DATABASE_FILE,
ERROR_MISSING_WORD_LIST_FILE ERROR_INVALID_WORD_LIST
} }

View file

@ -1,34 +1,70 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib; package com.wbrawner.keecrack.lib;
import com.wbrawner.keecrack.lib.view.CrackingView; import com.wbrawner.keecrack.lib.view.CrackingView;
import com.wbrawner.keecrack.lib.view.FormView; import com.wbrawner.keecrack.lib.view.FormView;
import com.wbrawner.keecrack.lib.wordlist.WordList;
import org.linguafranca.pwdb.kdbx.KdbxCreds; import org.linguafranca.pwdb.kdbx.KdbxCreds;
import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxHeader; import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxHeader;
import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxSerializer; import org.linguafranca.pwdb.kdbx.stream_3_1.KdbxSerializer;
import java.io.*; import java.io.ByteArrayInputStream;
import java.lang.ref.WeakReference; import java.io.File;
import java.io.InputStream;
import java.time.Duration; import java.time.Duration;
import java.time.Instant; import java.time.Instant;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
/**
* The main class responsible for handling the brute forcing of the KeePass database. You do not contruct the
* KeeCrack instance directly, but rather call {@link #getInstance()}. To begin, you should set the form view and
* cracking view with {@link #setFormView(FormView)} and {@link #setCrackingView(CrackingView)} respectively. These
* views will be responsible for displaying information like error messages and status updates. The cracking will
* work without these, though it's highly recommended to set them prior to beginning. The database and wordlist must
* be set, while the key file is also optional. If either of the required parameters are missing, the
* {@link #attack()} operation will abort, sending either {@link Code#ERROR_INVALID_DATABASE_FILE} or
* {@link Code#ERROR_INVALID_WORD_LIST}, respectively. The word list can either be a pattern, in which case
* incremental guessing will take place, or a file, in which case each line of the file will be considered a password
* to guess. Use {@link #setWordListPattern(String)} or {@link #setWordListFile(File)} respectively to achieve the
* desired guess strategy. If you need to interrupt the attack for any reason, you can call {@link #abort} and the
* cracking will stop on the next guess, sending {@link Code#ERROR_CRACKING_INTERRUPTED} to the views, provided they
* are set. For each guess, {@link CrackingView#onPasswordGuess(String)} is called, in case you'd like to do
* something with the passwords that have already been attempted. Upon a successful password guess, the
* {@link CrackingView#onResult(String, int, Duration)} method will be called with the first parameter as the correct
* password. If the password cannot be guessed with the words provided, then the same method will be called but the
* first parameter will be null.
*/
public class KeeCrack { public class KeeCrack {
private static final AtomicReference<KeeCrack> singleton = new AtomicReference<>(null); private static final AtomicReference<KeeCrack> singleton = new AtomicReference<>(null);
private final Object keyFileLock = new Object(); private final Object keyFileLock = new Object();
private WeakReference<FormView> formView = new WeakReference<>(null);
private WeakReference<CrackingView> crackingView = new WeakReference<>(null);
private final AtomicBoolean isCracking = new AtomicBoolean(false); private final AtomicBoolean isCracking = new AtomicBoolean(false);
private File databaseFile;
private File keyFile;
private File wordlistFile;
/** /**
* This is used to abort cracking * This is used to abort cracking
*/ */
private final AtomicBoolean abort = new AtomicBoolean(false); private final AtomicBoolean abort = new AtomicBoolean(false);
private final AtomicReference<FormView> formView = new AtomicReference<>(null);
private final AtomicReference<CrackingView> crackingView = new AtomicReference<>(null);
private File databaseFile;
private File keyFile;
private byte[] databaseBytes;
private byte[] keyBytes;
private WordList wordList;
private int guessCount = 0; private int guessCount = 0;
private KeeCrack() { private KeeCrack() {
@ -49,7 +85,7 @@ public class KeeCrack {
public void reset() { public void reset() {
setDatabaseFile(null); setDatabaseFile(null);
setKeyFile(null); setKeyFile(null);
setWordlistFile(null); setWordListFile(null);
setCrackingView(null); setCrackingView(null);
setFormView(null); setFormView(null);
} }
@ -63,12 +99,12 @@ public class KeeCrack {
*/ */
public void attack() { public void attack() {
if (databaseFile == null || !databaseFile.exists() || !databaseFile.canRead()) { if (databaseFile == null || !databaseFile.exists() || !databaseFile.canRead()) {
sendErrorCode(Code.ERROR_MISSING_DATABASE_FILE); sendErrorCode(Code.ERROR_INVALID_DATABASE_FILE);
return; return;
} }
if (wordlistFile == null || !wordlistFile.exists() || !wordlistFile.canRead()) { if (wordList == null) {
sendErrorCode(Code.ERROR_MISSING_WORD_LIST_FILE); sendErrorCode(Code.ERROR_INVALID_WORD_LIST);
return; return;
} }
@ -81,56 +117,73 @@ public class KeeCrack {
guessCount = 0; guessCount = 0;
Instant startTime = Instant.now(); Instant startTime = Instant.now();
try (BufferedReader wordReader = new BufferedReader(new FileReader(wordlistFile))) { String line = null;
String line = null; boolean haveCorrectPassword = false;
boolean haveCorrectPassword = false; prepareByteArrays();
while (!haveCorrectPassword && (line = wordReader.readLine()) != null) { while (!haveCorrectPassword && wordList.hasNext()) {
if (abort.get()) { if (abort.get()) {
sendErrorCode(Code.ERROR_CRACKING_INTERRUPTED); sendErrorCode(Code.ERROR_CRACKING_INTERRUPTED);
abort.set(false); isCracking.set(false);
return; abort.set(false);
} return;
CrackingView view = crackingView.get();
if (view != null)
view.onPasswordGuess(line);
haveCorrectPassword = guessPassword(line);
} }
line = wordList.nextWord();
try {
//noinspection ConstantConditions
crackingView.get().onPasswordGuess(line);
} catch (NullPointerException ignored) {
}
haveCorrectPassword = guessPassword(line);
}
CrackingView view = crackingView.get(); Duration duration = Duration.between(startTime, Instant.now());
if (view != null) { String password = null;
Duration duration = Duration.between(startTime, Instant.now()); if (haveCorrectPassword) {
String password = null; password = line;
if (haveCorrectPassword) { }
password = line; try {
} //noinspection ConstantConditions
view.onResult(password, guessCount, duration); crackingView.get().onResult(password, guessCount, duration);
} catch (NullPointerException ignored) {
}
isCracking.set(false);
}
@SuppressWarnings("ResultOfMethodCallIgnored")
private void prepareByteArrays() {
databaseBytes = Utils.getFileBytes(databaseFile);
synchronized (keyFileLock) {
if (keyFile == null) {
return;
} }
} catch (IOException e) { keyBytes = Utils.getFileBytes(keyFile);
e.printStackTrace();
sendErrorCode(Code.ERROR_FILE_READ);
} finally {
isCracking.set(false);
} }
} }
private boolean guessPassword(String password) { private boolean guessPassword(String password) {
guessCount++; guessCount++;
try (InputStream inputStream = new FileInputStream(databaseFile)) { InputStream databaseInput = null;
InputStream keyFileInput = null;
try {
databaseInput = new ByteArrayInputStream(databaseBytes);
KdbxHeader databaseHeader = new KdbxHeader(); KdbxHeader databaseHeader = new KdbxHeader();
KdbxCreds credentials; KdbxCreds credentials;
synchronized (keyFileLock) { if (keyBytes == null || keyBytes.length == 0) {
if (keyFile == null) { credentials = new KdbxCreds(password.getBytes());
credentials = new KdbxCreds(password.getBytes()); } else {
} else { keyFileInput = new ByteArrayInputStream(keyBytes);
credentials = new KdbxCreds(password.getBytes(), new FileInputStream(keyFile)); credentials = new KdbxCreds(password.getBytes(), keyFileInput);
}
} }
KdbxSerializer.createUnencryptedInputStream(credentials, databaseHeader, inputStream); KdbxSerializer.createUnencryptedInputStream(credentials, databaseHeader, databaseInput);
return true; return true;
} catch (IllegalStateException ignored) { } catch (IllegalStateException ignored) {
// This happens when an incorrect guess occurs. Expected behavior, so we ignore it // This happens when an incorrect guess occurs. Expected behavior, so we ignore it
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} finally {
Utils.closeQuietly(databaseInput);
Utils.closeQuietly(keyFileInput);
} }
return false; return false;
} }
@ -178,26 +231,63 @@ public class KeeCrack {
} }
} }
public File getWordlistFile() { public void setWordListFile(File wordlistFile) {
return wordlistFile; String response = null;
} if (wordlistFile == null) {
this.wordList = null;
public void setWordlistFile(File wordlistFile) { } else {
this.wordlistFile = wordlistFile; if (!wordlistFile.exists() || !wordlistFile.canRead()) {
try {
//noinspection ConstantConditions
crackingView.get().onError(Code.ERROR_INVALID_WORD_LIST);
} catch (NullPointerException ignored) {
}
this.wordList = null;
return;
}
this.wordList = new WordList(wordlistFile);
response = wordlistFile.getName();
}
try { try {
String response = (wordlistFile == null) ? null : wordlistFile.getName();
//noinspection ConstantConditions //noinspection ConstantConditions
formView.get().onWordListFileSet(response); formView.get().onWordListSet(response);
} catch (NullPointerException ignored) { } catch (NullPointerException ignored) {
} }
} }
public void setWordListPattern(String pattern) {
if (pattern == null) {
this.wordList = null;
} else {
try {
this.wordList = new WordList(pattern);
} catch (IllegalArgumentException ignored) {
// This can be thrown if the user has entered an invalid regular expression
}
}
try {
//noinspection ConstantConditions
formView.get().onWordListSet(pattern);
} catch (NullPointerException ignored) {
}
}
WordList getWordList() {
return this.wordList;
}
public String getWordListName() {
if (this.wordList == null)
return null;
return this.wordList.getName();
}
public void setFormView(FormView formView) { public void setFormView(FormView formView) {
this.formView = new WeakReference<>(formView); this.formView.set(formView);
} }
public void setCrackingView(CrackingView crackingView) { public void setCrackingView(CrackingView crackingView) {
this.crackingView = new WeakReference<>(crackingView); this.crackingView.set(crackingView);
} }
public boolean isCracking() { public boolean isCracking() {

View file

@ -0,0 +1,44 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib;
import java.io.*;
class Utils {
static void closeQuietly(Closeable closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (IOException ignored) {
}
}
@SuppressWarnings("ResultOfMethodCallIgnored")
static byte[] getFileBytes(File file) {
byte[] fileBytes;
try (InputStream inputStream = new FileInputStream(file)) {
fileBytes = new byte[inputStream.available()];
inputStream.read(fileBytes);
return fileBytes;
} catch (IOException e) {
e.printStackTrace();
return new byte[]{};
}
}
}

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib.view; package com.wbrawner.keecrack.lib.view;
import com.wbrawner.keecrack.lib.Code; import com.wbrawner.keecrack.lib.Code;

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib.view; package com.wbrawner.keecrack.lib.view;
import java.time.Duration; import java.time.Duration;
@ -5,14 +20,16 @@ import java.time.Duration;
public interface CrackingView extends BaseView { public interface CrackingView extends BaseView {
/** /**
* Called prior to each guess * Called prior to each guess
*
* @param password The password that will be guessed * @param password The password that will be guessed
*/ */
void onPasswordGuess(final String password); void onPasswordGuess(final String password);
/** /**
* Called when the password has been successfully guessed, or there are no more passwords to guess * Called when the password has been successfully guessed, or there are no more passwords to guess
* @param password The password, if successfully guessed, or null if no passwords were successful *
* @param guessCount The number of passwords guessed * @param password The password, if successfully guessed, or null if no passwords were successful
* @param guessCount The number of passwords guessed
* @param timeElapsed The time taken to guess the password * @param timeElapsed The time taken to guess the password
*/ */
void onResult(final String password, final int guessCount, final Duration timeElapsed); void onResult(final String password, final int guessCount, final Duration timeElapsed);

View file

@ -1,7 +1,24 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib.view; package com.wbrawner.keecrack.lib.view;
public interface FormView extends BaseView { public interface FormView extends BaseView {
void onDatabaseFileSet(String name); void onDatabaseFileSet(String name);
void onKeyFileSet(String name); void onKeyFileSet(String name);
void onWordListFileSet(String name);
void onWordListSet(String name);
} }

View file

@ -0,0 +1,57 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib.wordlist;
import com.mifmif.common.regex.Generex;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
public class WordList {
private WordListIterator iterator;
private String name;
public WordList(File wordFile) {
java.util.Iterator<String> wordListIterator = null;
this.name = wordFile.getName();
try {
wordListIterator = FileUtils.lineIterator(wordFile);
} catch (IOException e) {
e.printStackTrace();
}
iterator = new WordListIterator(wordListIterator);
}
public WordList(String pattern) {
Generex generex = new Generex(pattern);
this.name = pattern;
iterator = new WordListIterator(generex.iterator());
}
public boolean hasNext() {
return iterator.hasNext();
}
public String nextWord() {
return iterator.next();
}
public String getName() {
return this.name;
}
}

View file

@ -0,0 +1,54 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib.wordlist;
/**
* Since Generex uses their own Iterator interface, that complicates things a bit for us, so this class serves as
* nothing more than a wrapper around both interfaces to prevent complication in other areas of the code;
*/
public class WordListIterator {
private com.mifmif.common.regex.util.Iterator generexIterator;
private java.util.Iterator<String> standardIterator;
WordListIterator(com.mifmif.common.regex.util.Iterator generexIterator) {
this.generexIterator = generexIterator;
}
WordListIterator(java.util.Iterator<String> standardIterator) {
this.standardIterator = standardIterator;
}
public boolean hasNext() {
if (generexIterator != null) {
return generexIterator.hasNext();
}
return standardIterator != null && standardIterator.hasNext();
}
public String next() {
if (generexIterator != null) {
return generexIterator.next();
}
if (standardIterator != null) {
return standardIterator.next();
}
return null;
}
}

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib; package com.wbrawner.keecrack.lib;
import com.wbrawner.keecrack.lib.view.CrackingView; import com.wbrawner.keecrack.lib.view.CrackingView;
@ -10,6 +25,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
import static junit.framework.TestCase.assertFalse;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
@ -38,26 +54,30 @@ public class KeeCrackTest {
public void resetTest() { public void resetTest() {
keeCrack.setDatabaseFile(new File("Database")); keeCrack.setDatabaseFile(new File("Database"));
keeCrack.setKeyFile(new File("Keyfile")); keeCrack.setKeyFile(new File("Keyfile"));
keeCrack.setWordlistFile(new File("WordList")); keeCrack.setWordListPattern("Some pattern");
assertNotNull(keeCrack.getDatabaseFile()); assertNotNull(keeCrack.getDatabaseFile());
assertNotNull(keeCrack.getKeyFile()); assertNotNull(keeCrack.getKeyFile());
assertNotNull(keeCrack.getWordlistFile()); assertNotNull(keeCrack.getWordList());
keeCrack.reset(); keeCrack.reset();
assertNull(keeCrack.getDatabaseFile()); assertNull(keeCrack.getDatabaseFile());
assertNull(keeCrack.getKeyFile()); assertNull(keeCrack.getKeyFile());
assertNull(keeCrack.getWordlistFile()); assertNull(keeCrack.getWordList());
assertFalse(keeCrack.isCracking());
} }
@Test @Test
public void abortTest() throws IOException { public void abortTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.abort(); keeCrack.abort();
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(1)).onError(Code.ERROR_CRACKING_INTERRUPTED); verify(mockCrackingView, times(1)).onError(Code.ERROR_CRACKING_INTERRUPTED);
} }
/**
* This ensures that both views receive any errors sent
*/
@Test @Test
public void sendErrorTest() { public void sendErrorTest() {
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
@ -67,6 +87,9 @@ public class KeeCrackTest {
verify(mockFormView, times(1)).onError(Code.ERROR_CRACKING_IN_PROGRESS); verify(mockFormView, times(1)).onError(Code.ERROR_CRACKING_IN_PROGRESS);
} }
/**
* This ensures that the form view still receives the error if the cracking view is null
*/
@Test @Test
public void sendErrorWithoutCrackingViewTest() { public void sendErrorWithoutCrackingViewTest() {
keeCrack.setFormView(mockFormView); keeCrack.setFormView(mockFormView);
@ -74,6 +97,9 @@ public class KeeCrackTest {
verify(mockFormView, times(1)).onError(Code.ERROR_CRACKING_IN_PROGRESS); verify(mockFormView, times(1)).onError(Code.ERROR_CRACKING_IN_PROGRESS);
} }
/**
* This ensures that the cracking view still receives the error if the form view is null
*/
@Test @Test
public void sendErrorWithoutFormViewTest() { public void sendErrorWithoutFormViewTest() {
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
@ -83,10 +109,10 @@ public class KeeCrackTest {
@Test @Test
public void errorWithoutDatabaseFileTest() throws IOException { public void errorWithoutDatabaseFileTest() throws IOException {
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(1)).onError(Code.ERROR_MISSING_DATABASE_FILE); verify(mockCrackingView, times(1)).onError(Code.ERROR_INVALID_DATABASE_FILE);
} }
@Test @Test
@ -94,14 +120,14 @@ public class KeeCrackTest {
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(1)).onError(Code.ERROR_MISSING_WORD_LIST_FILE); verify(mockCrackingView, times(1)).onError(Code.ERROR_INVALID_WORD_LIST);
} }
@Test @Test
public void guessCorrectPasswordTest() throws IOException { public void guessCorrectPasswordFromFileTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(1)).onPasswordGuess("123456"); verify(mockCrackingView, times(1)).onPasswordGuess("123456");
@ -109,20 +135,42 @@ public class KeeCrackTest {
} }
@Test @Test
public void guessCorrectPasswordAndKeyTest() throws IOException { public void guessCorrectPasswordFromFileAndKeyTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx"));
keeCrack.setKeyFile(Utils.getKeyFile()); keeCrack.setKeyFile(Utils.getKeyFile());
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(1)).onPasswordGuess("123456"); verify(mockCrackingView, times(1)).onPasswordGuess("123456");
verify(mockCrackingView, times(1)).onResult(eq("123456"), eq(1), any(Duration.class)); verify(mockCrackingView, times(1)).onResult(eq("123456"), eq(1), any(Duration.class));
} }
@Test @Test
public void keepGuessingUntilCorrectPasswordTest() throws IOException { public void guessCorrectPasswordFromPatternTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("0000.kdbx"));
keeCrack.setWordListPattern("[0-9]{4}");
keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack();
verify(mockCrackingView, times(1)).onPasswordGuess(anyString());
verify(mockCrackingView, times(1)).onResult(eq("0000"), eq(1), any(Duration.class));
}
@Test
public void guessCorrectPasswordFromPatternAndKeyTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("0000-key.kdbx"));
keeCrack.setWordListPattern("[0-9]{4}");
keeCrack.setKeyFile(Utils.getKeyFile());
keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack();
verify(mockCrackingView, times(1)).onPasswordGuess(anyString());
verify(mockCrackingView, times(1)).onResult(eq("0000"), eq(1), any(Duration.class));
}
@Test
public void keepGuessingUntilCorrectPasswordFromFileTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("redwings.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("redwings.kdbx"));
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(2)).onPasswordGuess(anyString()); verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
@ -130,20 +178,41 @@ public class KeeCrackTest {
} }
@Test @Test
public void keepGuessingUntilCorrectPasswordAndKeyTest() throws IOException { public void keepGuessingUntilCorrectPasswordFromFileAndKeyTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("redwings-key.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("redwings-key.kdbx"));
keeCrack.setKeyFile(Utils.getKeyFile()); keeCrack.setKeyFile(Utils.getKeyFile());
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(2)).onPasswordGuess(anyString()); verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
verify(mockCrackingView, times(1)).onResult(eq("redwings"), eq(2), any(Duration.class)); verify(mockCrackingView, times(1)).onResult(eq("redwings"), eq(2), any(Duration.class));
} }
@Test
public void keepGuessingUntilCorrectPasswordFromPatternTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("ab.kdbx"));
keeCrack.setWordListPattern("[a-z]{2}");
keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack();
verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
verify(mockCrackingView, times(1)).onResult(eq("ab"), eq(2), any(Duration.class));
}
@Test
public void keepGuessingUntilCorrectPasswordFromPatternAndKeyTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("ab-key.kdbx"));
keeCrack.setWordListPattern("[a-z]{2}");
keeCrack.setKeyFile(Utils.getKeyFile());
keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack();
verify(mockCrackingView, times(2)).onPasswordGuess(anyString());
verify(mockCrackingView, times(1)).onResult(eq("ab"), eq(2), any(Duration.class));
}
@Test @Test
public void failToCrackPasswordTest() throws IOException { public void failToCrackPasswordTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456.kdbx"));
keeCrack.setWordlistFile(Utils.getWordList("invalid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("invalid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(3)).onPasswordGuess(anyString()); verify(mockCrackingView, times(3)).onPasswordGuess(anyString());
@ -154,7 +223,7 @@ public class KeeCrackTest {
public void failToCrackInvalidPasswordAndValidKeyTest() throws IOException { public void failToCrackInvalidPasswordAndValidKeyTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx"));
keeCrack.setKeyFile(Utils.getKeyFile()); keeCrack.setKeyFile(Utils.getKeyFile());
keeCrack.setWordlistFile(Utils.getWordList("invalid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("invalid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(3)).onPasswordGuess(anyString()); verify(mockCrackingView, times(3)).onPasswordGuess(anyString());
@ -165,7 +234,7 @@ public class KeeCrackTest {
public void failToCrackValidPasswordAndInvalidKeyTest() throws IOException { public void failToCrackValidPasswordAndInvalidKeyTest() throws IOException {
keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx")); keeCrack.setDatabaseFile(Utils.getDatabase("123456-key.kdbx"));
keeCrack.setKeyFile(Utils.getInvalidKeyFile()); keeCrack.setKeyFile(Utils.getInvalidKeyFile());
keeCrack.setWordlistFile(Utils.getWordList("valid-words.txt")); keeCrack.setWordListFile(Utils.getWordList("valid-words.txt"));
keeCrack.setCrackingView(mockCrackingView); keeCrack.setCrackingView(mockCrackingView);
keeCrack.attack(); keeCrack.attack();
verify(mockCrackingView, times(3)).onPasswordGuess(anyString()); verify(mockCrackingView, times(3)).onPasswordGuess(anyString());

View file

@ -1,3 +1,18 @@
/*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.wbrawner.keecrack.lib; package com.wbrawner.keecrack.lib;
import java.io.*; import java.io.*;

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1 +1,16 @@
include 'keecrack-lib', 'keecrack-gui', 'keecrack-cli' /*
* Copyright 2018 William Brawner
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
include 'keecrack-lib', 'keecrack-gui', 'keecrack-cli'